Transactional email is invisible when it works. The practices below are the operational baseline that keeps it that way. None of them are exotic. All of them are skipped by most teams until they hit a deliverability incident.
last updated 2026-05-079 sections
section 01
Authenticate before sending
SPF, DKIM, and DMARC are not optional. Publish SPF as a DNS TXT record. Enable DKIM in the provider and publish the public key. Publish DMARC at p=none with an rua report address you read. Tighten to p=quarantine, then p=reject, after two to four weeks of clean reports. Below 5,000/day to Gmail or Yahoo this is recommended; above it, mandatory since February 2024.
okVerify the sending domain before the first production send.
okPublish SPF with the provider include or mechanism from official docs.
okEnable DKIM signing and confirm at least one selector passes.
okPublish DMARC at p=none with a monitored rua address.
okReview DMARC reports before moving to quarantine or reject.
section 02
Separate streams
Send transactional and marketing through separate streams or, ideally, separate from-subdomains. Postmark and Loops support stream separation natively. The reason: a single complaint on a marketing send should not poison your password-reset deliverability.
section 03
Idempotency on the client
Every transactional send should include an idempotency key. The server caches the response and returns the same response for any retry with the same key. Without it, a network retry on a flaky connection sends a duplicate password-reset email, which is at minimum confusing and at worst a security issue. If your provider does not support idempotency keys, deduplicate on your side using a key tied to the user action.
section 04
Retry with backoff, never in a tight loop
When a send fails, retry with exponential backoff (2s, 4s, 8s, 16s, 32s, then give up). Tight retry loops are how you accidentally DDoS your provider and trigger rate-limit blocks that cascade into other sends. Most provider SDKs handle this for you; if you wrote your own client, double-check.
section 05
Handle bounces and complaints automatically
Hard bounces should auto-suppress on the provider side; verify this is on. Complaints should auto-suppress and surface in your dashboard. If you use AWS SES, you have to wire SNS topics yourself; do not skip this step. Aim for a complaint rate under 0.1% per send; above 0.3% triggers reputation problems with mailbox providers.
section 06
Log message IDs structurally
Every send should log the providers message ID alongside your internal user and event IDs. This makes "did the password reset email actually go out for user X?" a one-query answer. Without it, support requests turn into fishing expeditions through provider dashboards.
section 07
Test in staging, not production
Use Mailpit, Mailtrap, or Ethereal in staging. Never point staging at the production sender; you will eventually email a real customer with test data. Most providers offer a sandbox or test mode; turn it on. Better, run a separate provider account for non-production traffic so reputation is isolated.
section 08
Render and review the actual HTML
Email clients render HTML inconsistently. Outlook, Gmail, and Apple Mail behave differently from one another, and from each other on dark mode. Use a render-testing tool (Litmus, Email on Acid, or Mailtraps preview). Ship with both HTML and plain-text bodies; most providers do this automatically if you set both.
section 09
Plan your migration before you need it
Provider lock-in for transactional email is real but smaller than people fear. Before committing, verify: can you export your suppression list? Can you export your event history? Are SDKs available for your stack? If a provider answers no to those, ask why before signing.