/email-for-startups providers ↗
guide

Transactional Email Best Practices

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-07 9 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.

  • ok Verify the sending domain before the first production send.
  • ok Publish SPF with the provider include or mechanism from official docs.
  • ok Enable DKIM signing and confirm at least one selector passes.
  • ok Publish DMARC at p=none with a monitored rua address.
  • ok Review 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.

related startup email pages