ItsBingMyBusiness

Developer Guides

Testing transactional email without sending any

Email is one of the few things in an application routinely shipped untested, because the obvious way to test it — send one and look — does not fit in a test suite.

Most of what can go wrong is testable without sending anything.

Unit: the content

Render the template with known data and assert on the output. No network.

  • Every variable substituted — no stray placeholder tokens in the result
  • Values that arrive from users are escaped; a name containing a tag does not become markup
  • Links point at the right environment, not at localhost
  • The plain-text alternative exists and contains the same essential information, including the link

The placeholder check catches the most common production embarrassment, and it is one assertion.

Integration: the call

Substitute a fake transport that records instead of sending. Then assert on what the application tried to do.

  • Exactly one message was sent for one request — no duplicates
  • It went to the right recipient
  • The idempotency key is present and stable across retries
  • A failure from the transport is handled rather than swallowed
  • A suppressed recipient results in no send

That last one is worth an explicit test. Suppression logic that is never exercised is suppression logic that will not run.

Contract: the webhook handler

Your handler is an endpoint that mutates state based on external input. Test it like one.

  • A valid signature is accepted
  • An invalid signature is rejected
  • An expired timestamp is rejected
  • The same event ID delivered twice has the effect of one
  • Events arriving out of order do not produce a wrong final state

See verifying webhook signatures for what each of these protects against.

Where a real send is required

Three things, and none of them belong in a unit test:

Rendering in real clients. Outlook, Gmail and Apple Mail disagree about CSS in ways no test catches. This needs rendering previews or real accounts.

Authentication as a receiver computes it. SPF, DKIM and DMARC alignment can only be confirmed by a receiver evaluating a real message. The inbox tester does exactly this.

Deliverability. Not testable in any meaningful sense before you send. It is monitored, not asserted.

Staging

The failure mode is a staging run that mails real customers. Two defences, both:

Route all non-production mail to a catch-all sandbox, so an address that escapes goes nowhere real. And enforce an allowlist in the application for non-production environments, so the code refuses to send to anything outside it.

One of these is a configuration someone can change. Two means a mistake has to happen twice.

Ready to send authenticated mail?

DKIM signing, SPF alignment and DMARC are set up the moment you verify a domain.

Get started