Sparkpost
Send transactional and marketing email with SparkPost (now MessageBird). Use this
You are an expert in SparkPost (MessageBird Email API) for transactional and marketing email. You understand the Transmissions API, stored templates, recipient lists, subaccounts, webhook events, and how to maintain deliverability at enterprise scale. ## Key Points - **Sending marketing email with `transactional: true`** -- This bypasses opt-out suppression handling, violates CAN-SPAM/GDPR, and risks account suspension. Set the flag accurately for every send. - **Using the US API endpoint for an EU account** -- EU accounts must use `api.eu.sparkpost.com`. Hitting the US endpoint returns authentication errors with no indication that the region is wrong. - **Mark transactional vs marketing explicitly.** Set `options.transactional: true` - **Use stored templates.** Keep email content in SparkPost templates so marketing - **Respect suppression lists.** SparkPost auto-suppresses hard bounces and spam - **Use subaccounts for multi-tenant apps.** Each tenant gets isolated sending - **Set up a custom bounce domain and tracking domain.** Aligns authentication and - **Monitor bounce classifications.** SparkPost provides 100 bounce classes. Focus on - **Ignoring the EU endpoint.** If your SparkPost account is in the EU data center, - **Sending marketing mail with transactional flag.** This bypasses opt-out - **Not handling 429 rate limits.** SparkPost returns 429 when you exceed sending - **Webhook endpoint not idempotent.** SparkPost may redeliver webhook batches. ## Quick Example ```bash npm install @sparkpost/node-sparkpost ``` ``` SPARKPOST_API_KEY=your-api-key SPARKPOST_WEBHOOK_AUTH_TOKEN=your-webhook-token ```
skilldb get email-services-skills/SparkpostFull skill: 277 linesSparkPost — Email Service
You are an expert in SparkPost (MessageBird Email API) for transactional and marketing email. You understand the Transmissions API, stored templates, recipient lists, subaccounts, webhook events, and how to maintain deliverability at enterprise scale.
Core Philosophy
SparkPost is built for scale. Where other email services handle thousands of messages, SparkPost handles billions, with infrastructure designed for high-throughput sending, granular deliverability analytics, and multi-tenant isolation through subaccounts. Choosing SparkPost means you expect your email volume to grow significantly and you need a platform that will not become a bottleneck.
The transactional flag is a contract, not a label. Setting options.transactional: true tells SparkPost to treat the message differently for suppression handling, compliance, and deliverability reporting. Transactional messages bypass marketing unsubscribe suppression because they are expected by the recipient (receipts, alerts, password resets). Sending marketing content with the transactional flag violates this contract, bypasses consent mechanisms, and can lead to account suspension.
Suppression lists are sacred. SparkPost automatically suppresses hard bounces and spam complaints to protect your sender reputation. Removing an address from the suppression list should only happen when the recipient has explicitly re-opted in through a confirmed action. Programmatically clearing suppressions to "retry" sending to bad addresses is the fastest way to destroy your deliverability and get your account flagged.
Anti-Patterns
- Sending marketing email with
transactional: true-- This bypasses opt-out suppression handling, violates CAN-SPAM/GDPR, and risks account suspension. Set the flag accurately for every send. - Clearing suppression lists to retry bad addresses -- Suppressed addresses are suppressed for a reason. Removing them without genuine re-opt-in poisons your sender reputation and increases bounce rates.
- Using the US API endpoint for an EU account -- EU accounts must use
api.eu.sparkpost.com. Hitting the US endpoint returns authentication errors with no indication that the region is wrong. - Sending without a custom bounce domain and tracking domain -- Default SparkPost domains in email headers reduce brand trust and can fail strict DMARC policies. Configure custom domains for full alignment.
- Not deduplicating webhook events -- SparkPost may redeliver webhook batches. Processing the same event twice can cause duplicate database writes, duplicate notifications, or incorrect metrics. Deduplicate by event_id.
Overview
SparkPost is a high-volume email delivery platform (now part of MessageBird) that powers email for major senders. It provides a REST API and SMTP relay for sending, stored templates with Handlebars-like substitution, granular engagement and deliverability analytics, suppression lists, and real-time event webhooks. SparkPost is particularly strong at scale — handling billions of messages — and offers subaccounts for multi-tenant isolation.
Setup & Configuration
Installation
npm install @sparkpost/node-sparkpost
Basic client setup
import SparkPost from "@sparkpost/node-sparkpost";
const client = new SparkPost(process.env.SPARKPOST_API_KEY, {
origin: "https://api.sparkpost.com:443",
// For EU data center: "https://api.eu.sparkpost.com:443"
});
Environment variables
SPARKPOST_API_KEY=your-api-key
SPARKPOST_WEBHOOK_AUTH_TOKEN=your-webhook-token
Domain authentication
Verify your sending domain by adding the DKIM TXT record SparkPost provides. SPF alignment happens automatically through SparkPost's return-path domain. Set up a custom bounce domain and custom tracking domain for full brand alignment:
const response = await client.sendingDomains.create({
domain: "mail.example.com",
generate_dkim: true,
});
// Add the returned DKIM record to DNS, then verify:
await client.sendingDomains.verify("mail.example.com", {
dkim_verify: true,
});
Core Patterns
Send a single transactional email
async function sendTransactional(to: string, subject: string, html: string) {
const result = await client.transmissions.send({
content: {
from: "app@mail.example.com",
subject,
html,
},
recipients: [{ address: { email: to } }],
options: {
transactional: true, // marks as transactional for suppression handling
open_tracking: false,
click_tracking: false,
},
});
return result.results; // { total_rejected_recipients, total_accepted_recipients, id }
}
Send with a stored template and substitution data
async function sendFromTemplate(
to: string,
templateId: string,
data: Record<string, unknown>
) {
return client.transmissions.send({
content: {
template_id: templateId,
},
recipients: [
{
address: { email: to },
substitution_data: data,
},
],
options: { transactional: true },
});
}
// Usage
await sendFromTemplate("user@example.com", "welcome-email", {
firstName: "Alice",
actionUrl: "https://app.example.com/onboard",
});
Batch send with recipient list
async function sendBatch(
recipients: Array<{ email: string; name: string; data: Record<string, unknown> }>,
templateId: string
) {
return client.transmissions.send({
content: { template_id: templateId },
recipients: recipients.map((r) => ({
address: { email: r.email, name: r.name },
substitution_data: r.data,
})),
options: { transactional: false },
});
}
Process webhook events
import { Router } from "express";
const router = Router();
interface SparkPostEvent {
msys: {
message_event?: {
type: string;
rcpt_to: string;
transmission_id: string;
timestamp: string;
bounce_class?: number;
raw_reason?: string;
};
track_event?: {
type: string;
rcpt_to: string;
target_link_url?: string;
};
};
}
router.post("/webhooks/sparkpost", (req, res) => {
const authToken = req.headers["x-messagesystems-webhook-token"];
if (authToken !== process.env.SPARKPOST_WEBHOOK_AUTH_TOKEN) {
return res.status(401).end();
}
const events: SparkPostEvent[] = req.body;
for (const event of events) {
const msgEvent = event.msys.message_event;
const trackEvent = event.msys.track_event;
if (msgEvent) {
switch (msgEvent.type) {
case "delivery":
// Email delivered
break;
case "bounce":
// Handle bounce — check bounce_class for hard vs soft
if (msgEvent.bounce_class && msgEvent.bounce_class <= 25) {
// Hard bounce: suppress this address
}
break;
case "spam_complaint":
// Suppress sender immediately
break;
}
}
if (trackEvent) {
switch (trackEvent.type) {
case "open":
case "click":
// Log engagement
break;
}
}
}
res.status(200).end();
});
Subaccounts for multi-tenant isolation
// Create a subaccount for a tenant
const subaccount = await client.subaccounts.create({
name: "Tenant ABC",
key_label: "tenant-abc-key",
key_grants: ["smtp/inject", "transmissions/modify", "message_events/view"],
});
// subaccount.results.subaccount_id — use as x-msys-subaccount header
// Send on behalf of a subaccount
await client.transmissions.send(
{
content: { template_id: "tenant-welcome" },
recipients: [{ address: { email: "user@example.com" } }],
},
{ headers: { "x-msys-subaccount": subaccount.results.subaccount_id } }
);
Best Practices
- Mark transactional vs marketing explicitly. Set
options.transactional: truefor transactional sends. SparkPost uses this flag for suppression list behavior and deliverability reporting. - Use stored templates. Keep email content in SparkPost templates so marketing teams can update copy without code deploys. Use substitution_data for dynamic values.
- Respect suppression lists. SparkPost auto-suppresses hard bounces and spam complaints. Query the suppression list API before re-adding addresses. Never remove suppressions unless the recipient explicitly re-opts in.
- Use subaccounts for multi-tenant apps. Each tenant gets isolated sending reputation, separate API keys, and independent suppression lists. This prevents one bad tenant from poisoning deliverability for all.
- Set up a custom bounce domain and tracking domain. Aligns authentication and prevents brand confusion in email headers. Improves deliverability with strict DMARC policies.
- Monitor bounce classifications. SparkPost provides 100 bounce classes. Focus on classes 10 (invalid recipient), 25 (admin failure), and 30 (generic bounce) for list hygiene decisions.
Common Pitfalls
- Ignoring the EU endpoint. If your SparkPost account is in the EU data center,
you must use
api.eu.sparkpost.com. Using the US endpoint returns auth errors. - Sending marketing mail with transactional flag. This bypasses opt-out suppression handling and violates CAN-SPAM / GDPR. Always set the flag accurately.
- Not handling 429 rate limits. SparkPost returns 429 when you exceed sending rate. Implement exponential backoff. For very high volume, request a rate limit increase via support.
- Webhook endpoint not idempotent. SparkPost may redeliver webhook batches. Deduplicate using the event_id field to avoid processing the same event twice.
- Overlooking inline CSS. SparkPost does not auto-inline CSS. Use a CSS inliner (like Juice) before uploading HTML templates, or emails render incorrectly in many clients.
Install this skill directly: skilldb add email-services-skills
Related Skills
AWS Ses
Send email at scale with Amazon SES (Simple Email Service). Use this skill when
Brevo
Send transactional and marketing email with Brevo (formerly Sendinblue). Use this
Courier
Send transactional notifications including email with Courier. Use this skill when
Customerio
Send transactional and marketing email with Customer.io. Use this skill when the
Email Deliverability
Optimize email deliverability across any provider. Use this skill when the project
Loops
Send transactional and marketing email with Loops. Use this skill when the project