Common Recipes
Production-grade patterns that save you reinventing the wheel.
Idempotent transactional sends
Replays of the same logical send (e.g. payment-receipt for the same
charge ID) shouldn’t trigger duplicate emails. Stamp a stable
X-Idempotency-Key header derived from your business key, and
de-dup on your side before calling /send:
const sentKey = await redis.get(`email:sent:${chargeId}`);
if (sentKey) return; // already emailed
const res = await client.emails.send({...});
await redis.set(`email:sent:${chargeId}`, res.results[0].messageId, { EX: 86400 });We don’t currently honor an Idempotency-Key header server-side
(roadmap), so the dedup happens at your layer.
Per-recipient personalization at scale
For bulk sends with per-recipient variables, use /send-bulk:
await client.emails.sendBulk({
from: "hello@yourcompany.com",
templateName: "newsletter-june",
defaultTemplateData: { campaign: "june-2026" },
destinations: users.map(u => ({
to: [u.email],
replacementData: {
first_name: u.firstName,
unsubscribe_url: `https://yoursite.com/u/${u.token}`,
},
})),
});50 destinations × 50 recipients × 500 total per request. For larger volumes, chunk into multiple calls — there’s no per-account rate limit on bulk-send concurrency, only the per-account per-second peak rate.
Dynamic from-name
Set the from field as "Display Name <hello@yourcompany.com>" and
recipients see “Display Name” in their inbox:
{
"from": "Sarah from Acme <sarah@acme.com>",
...
}Domain still has to be verified. Display name is unverified — recipients see whatever you put there.
Custom MAIL FROM domain (return-path)
Sets the bounce return-path to a custom subdomain so DMARC alignment
includes both DKIM and SPF. Lands on roadmap. Until then, the
return-path is bounces@mail.splashifypro.com and DMARC alignment
relies on DKIM only (p=quarantine is fine; p=reject may need
DMARC relaxed alignment).
Per-customer attribution
If you’re sending on behalf of multiple downstream customers, create a configuration set per customer and reference it on every send:
await client.emails.send({
from: "alerts@yourcompany.com",
to: ["customer@example.com"],
configurationSetName: "customer_acme_corp",
...
});Stats roll up per config set — GET /stats?config_set_id=.... Each
config set can also have its own webhook destination so events for
Acme go to one URL and events for Globex go to another.
Hard-bounce auto-list-cleaning
Hard bounces are added to your suppression list automatically. To
keep your application’s email list in sync, listen for the
Bounce webhook event:
app.post("/webhooks/splashify", async (req, res) => {
const sig = req.header("x-splashify-signature");
if (!verifyHMAC(req.rawBody, sig, process.env.SPLASHIFY_WEBHOOK_SECRET)) {
return res.status(401).end();
}
if (req.body.eventType === "Bounce" && req.body.bounce.bounceType === "Permanent") {
const email = req.body.bounce.bouncedRecipients[0].emailAddress;
await db.users.updateOne({ email }, { $set: { emailBouncedHard: true } });
}
res.status(200).end();
});Now your signup form / re-engagement campaigns can skip these addresses up-front instead of burning send quota.
Retry on 5xx
Network blips and brief upstream issues should retry; auth/quota errors shouldn’t. Pseudocode:
async function sendWithRetry(payload, attempts = 3) {
for (let i = 0; i < attempts; i++) {
const res = await client.emails.send(payload);
if (res.success) return res;
const code = res.error;
// Don't retry these — won't get better with time.
if (["INVALID_REQUEST", "FROM_NOT_VERIFIED", "INSUFFICIENT_BALANCE", "MISSING_AUTH"].includes(code)) {
throw new Error(`${code}: ${res.message}`);
}
// Backoff on the rest.
await sleep(1000 * Math.pow(2, i));
}
throw new Error("send_failed_after_retries");
}Webhooks that survive your deploys
Stamp every event in your local DB before processing — duplicate webhook deliveries (which happen on retries) are idempotent:
const eventID = req.header("x-splashify-delivery-id");
const inserted = await db.events.insertOne({
_id: eventID,
...req.body,
}, { ignoreDuplicates: true });
if (!inserted.insertedCount) return res.status(200).end(); // dedup
// ... processX-Splashify-Delivery-ID is a fresh UUID per delivery attempt —
even a retried event keeps the same ID.
Cold-start IP warm-up
New production accounts inherit warm sending infrastructure with established reputation across major mailbox providers. You don’t need to manually warm up.
Dedicated IPs (roadmap) require warm-up — typically 2 weeks of gradually increasing volume. We’ll publish a warm-up calculator when dedicated IPs ship.