Transactional Email API: The Developer's Complete Handbook

Everything an engineer needs to ship transactional email in production — protocols, deliverability, idempotency, retries, observability, templating, webhooks, separation from marketing, and the failure modes you only learn about at 3am.

GuideApril 8, 2026 15 min read

Transactional email is the unsexy half of your stack that quietly carries the product. Password resets, magic links, receipts, two-factor codes, order confirmations, account-state notifications, security alerts — they all matter more than your homepage hero copy. If they don't arrive in seconds and land in the inbox, users churn before they ever see the dashboard.

This handbook covers everything an engineer needs to actually ship transactional email in production: how the wire protocols work, what receivers expect in 2026, how to build for idempotency, retries, and observability, and the architectural decisions that separate teams whose email "just works" from teams who keep paging the on-call about Gmail spam classifications.

We'll move from foundational concepts to advanced patterns. If you only have ten minutes, jump to The production checklist at the bottom.

What "transactional" actually means

Transactional email is mail triggered by a user action and sent to that same user. Receipt of a purchase. Reset of a password. Confirmation of a comment reply. The user is expecting the message — they performed the action that spawned it — which is why receivers grant transactional mail more leniency on spam-filter heuristics.

Marketing email is everything else: campaigns, drip sequences, newsletters, re-engagement blasts, product announcements. Even if your user opted in, receivers treat it differently. Different IP pools, different complaint thresholds, different deliverability profile.

Mixing the two on the same infrastructure is the single most common deliverability mistake we see in production. A campaign blast triggers a cluster of complaints; the IP's reputation tanks; your password reset emails start landing in spam the next morning. Always isolate the two.

Choose your protocol: REST API vs SMTP relay

Both work. Both are battle-tested. The choice is shaped by your stack and how much observability you want code-side. We've covered the tradeoffs in detail in SMTP Relay vs REST API for Transactional Email. The short answer:

  • New SaaS code: REST API. Cleaner correlation with webhooks, better observability, easier rotation.
  • Off-the-shelf platforms (WordPress, Shopify): SMTP relay. The plugin ecosystem assumes SMTP.
  • Edge functions: REST API. Most edge runtimes don't support raw TCP.

Domain authentication: SPF, DKIM, DMARC

Before you send a single message, your sending domain must be authenticated. Modern receivers (Gmail, Outlook, Yahoo) require all three records — and as of February 2024, Google's sender requirements made this explicit for any sender doing more than 5,000/day to Gmail.

  • SPF: TXT record on the apex authorizing your sending IPs. Exactly one record. RFC 7208 caps it at 10 DNS lookups; blow past and you permerror.
  • DKIM: TXT record with the public half of a 2048-bit RSA keypair. Your sender signs every message; receivers verify the signature.
  • DMARC: TXT record with a policy (none / quarantine / reject) and a report address. Tells receivers what to do when SPF and DKIM fail to align with the From: domain.

Full walkthrough with exact records, alignment rules, and verification commands in our SPF/DKIM/DMARC 2026 guide.

Idempotency: the part everyone skips

Your retry logic will, sooner or later, double-send a message. The user clicks "resend code" twice. Your worker crashes after handing off but before marking the job complete. Your network blip eats the response so you re-fire the request. If the message is a 2FA code, the user gets two — confusing but usually harmless. If it's a payment receipt, the user gets two for one purchase — support ticket city.

Defense in depth: idempotency keys at the API layer, deduplication at the application layer, and append-only event sourcing for state changes.

API-layer idempotency

POST /api/v1/email/send
Idempotency-Key: pwreset-user_8c2f4a-2026-04-08T10:14:33Z
{
  "to": "[email protected]",
  "template_id": "tmpl_password_reset",
  "variables": {...}
}

Sendr365's API treats the Idempotency-Key header as a dedup token: the first request with a given key is processed; any subsequent request with the same key inside 24 hours returns the original response without re-sending. The key is yours to design — good keys encode the user, the action, and a time window narrow enough to be unique but wide enough to catch retries (rounding to the minute is usually right).

Application-layer deduplication

Even with API idempotency, your code shouldn't blindly fire on every event. Wrap the send in a check: "has this user's password been reset in the last 60 seconds?" If yes, skip. The check goes in your DB or Redis — anywhere with a fast lookup and the user_id available.

Retries that don't make things worse

Three rules for retry logic:

  1. Retry on transient errors only. 5xx from the API, network timeout, or specific 429 responses. Never retry on 4xx (other than 429) — the message is malformed, retrying won't fix it.
  2. Exponential backoff with jitter. 1s, 2s, 4s, 8s with ±25% random jitter. Without jitter, every client retries at the same instant after a downstream blip and you cause a thundering herd. Cap at 5 retries.
  3. Idempotency keys on every retry. Same key on the retry as the original. The API dedupes on its end; your retries become safe.

If you're using a job queue (BullMQ, Sidekiq, Celery), use the queue's built-in retry mechanism with exponential backoff and a max-attempts cap. Don't write your own retry loop unless the queue's primitives don't fit.

Templating: the dashboard or the codebase?

You can ship template HTML in your repo and substitute variables before sending, or you can keep templates server-side (in your sending platform's dashboard) and reference them by ID. Both are valid; the tradeoff is who owns the copy.

  • Code-side templates (HTML in repo, rendered with Handlebars / MJML / Jinja2): every change requires a code review and deploy. Engineering owns the copy. Best for early-stage teams or when copy rarely changes.
  • Dashboard templates (template_id + variables): non-engineers can edit copy without a deploy. Marketing/CX owns the copy. Best when you have dedicated content people. Requires version control on the dashboard side and discipline about preview-before-publish.

Sendr365 supports both. Most teams start with code-side templates and migrate to dashboard templates around series A when the marketing team grows.

Variables and personalization

Substitute aggressively. Every transactional email should feel addressed to the user. At minimum: first name, the action context, the time, the relevant artifact link.

Subject: {{first_name}}, your password reset link
Hi {{first_name}},
You (or someone) requested a password reset for your {{app_name}} account on
{{request_time}} from {{request_location}}. Click below to choose a new password:
{{reset_url}}
This link expires in 30 minutes.

The location and timestamp are critical for security — if the user didn't request the reset, the metadata helps them recognize the attack.

Do not put PII in the subject line beyond first name. Subject lines appear in notifications, lock screens, and previews; sensitive data leaks accidentally.

The List-Unsubscribe trap

Even transactional email needs a List-Unsubscribe header in 2026 — Gmail's sender requirements include it, and not including it costs you deliverability signal. The trick is what to do when the user clicks it.

For purely transactional mail (receipts, password resets), you cannot honor unsubscribe — the message is required for the service. Some platforms let you send the header pointing to a landing page that explains this. Others insist on a per-message opt-out. Sendr365 handles this automatically: transactional sends get a List-Unsubscribe header pointing to a tenant-scoped notice page; clicking it explains why the message is required.

For marketing-adjacent transactional (digest emails, weekly summaries), always include a working one-click unsubscribe. Failure to honor it is a CAN-SPAM violation and a complaint magnet.

Webhooks: the closing of the loop

Sending the message is half the job. The other half is reacting to what receivers tell you afterwards: delivered, bounced,complained, opened, clicked,unsubscribed.

Every send platform worth using emits these as signed webhooks. Sendr365 signs every event with HMAC-SHA256 over a per-tenant secret; verify the signature before processing. The exposed pattern:

POST /webhooks/sendr365 (your endpoint)
X-Sendr365-Signature: sha256=ab3c1d...
X-Sendr365-Timestamp: 1714560000

{
  "event": "delivered",
  "message_id": "msg_8c2f4a1e9b3d4f7a",
  "timestamp": "2026-04-08T10:14:35Z",
  "to": "[email protected]"
}

On receiving a bounced event, mark the recipient as undeliverable in your DB and stop sending to them. On complained, treat it as a de-facto unsubscribe even if the user didn't click the link. Continuing to mail a complaining user is the fastest way to tank your reputation.

Bounce handling: hard vs soft

  • Hard bounces — recipient doesn't exist, mailbox closed. Permanent. Mark the address as undeliverable, stop sending to it. Continued attempts hurt your reputation.
  • Soft bounces — mailbox full, server temporarily down. Try again later (the platform usually retries automatically). After 5 consecutive soft bounces over a week, treat as hard.

Sendr365 classifies bounces and surfaces both kinds; the dashboard shows the recipient list with bounce reason so you can see why deliverability is dipping before complaints start.

Observability: logs, metrics, alerts

At minimum:

  • Log every send with the message ID, the recipient, and the template ID. Don't log the body unless you have a privacy review approving it.
  • Metric: send rate per template per minute. Alert if it drops to zero on a normally-active template.
  • Metric: bounce rate per template per day. Alert above 2% (the industry threshold for "something's off").
  • Metric: complaint rate per template per day. Alert above 0.1% (the Gmail threshold).
  • Metric: time from send to delivered (median). Alert if it crosses 60 seconds.

The production checklist

  1. Authenticate your sending domain (SPF, DKIM, DMARC at p=none minimum). Verify with dig, not the dashboard.
  2. Choose your protocol — REST for new code, SMTP for legacy / off-the-shelf.
  3. Use idempotency keys on every send.
  4. Implement exponential backoff with jitter for retries; cap at 5 attempts.
  5. Pick code-side or dashboard templates and stick with one.
  6. Personalize aggressively but never put PII in subject lines.
  7. Include List-Unsubscribe on every send (even transactional).
  8. Verify webhook signatures before processing.
  9. Mark hard-bounced recipients as undeliverable; treat 5 consecutive soft bounces as hard.
  10. Treat any complaint as a de-facto unsubscribe.
  11. Separate transactional and marketing IP pools. Always.
  12. Alert on bounce rate > 2% and complaint rate > 0.1%.

Sendr365 ships with all of the above wired up by default — domain auth in the onboarding wizard, idempotency at the API layer, signed webhooks, separated IP pools at scale, alerting hooks ready for your monitoring stack. If you're starting fresh, create an account and skip the boilerplate. If you're evaluating, the developer docs walk through every endpoint with copyable code in Node, Python, and cURL.

Ship your first email in minutes.

Free plan, no credit card. SMTP relay + REST API + signed webhooks — verify your domain and send from your stack today.