Blog/Guide

Webhooks For Transactional Email: Delivered, Bounced, Complained

Sending is only half of a transactional pipe. The other half is knowing what happened. Here is how to consume email event webhooks so you can trust them.

The Events That Actually Matter

  • accepted — the receiving MTA has taken responsibility. Not the same as landing in the inbox, but the handoff succeeded.
  • delivered — the receiver confirmed final delivery. This is the strongest positive signal you get.
  • bounced — permanent failure. Includes the SMTP reply so you can distinguish invalid mailbox from reputation block.
  • deferred — temporary failure, will retry. Interesting only in aggregate; a spike often means an ISP is throttling you.
  • complained — the recipient hit “report spam.” Always suppress. Investigate the pattern.
  • opened / clicked — useful signals, but unreliable individually because of image proxies (Apple Mail Privacy, corporate scanners). Trust them in aggregate, never per user.

Verify The Signature. Every Time.

Every mature provider signs its webhook payloads. The provider sends the raw body plus a signature header; you recompute an HMAC over the body with a shared secret and compare in constant time. If the signatures do not match, reject with 401. An unauthenticated webhook endpoint is a public endpoint any attacker can post to.

Verify against the raw request body before any JSON parsing. Frameworks that re-serialize the body will silently break signature checks.

Idempotent Handlers

Providers retry webhooks. That means the same event will hit your endpoint more than once, sometimes days apart if you were down during the first attempt. Every payload includes a unique event id; store it and use it as a deduplication key.

  1. Insert the event id into an events table (with a unique constraint).
  2. If the insert conflicts, return 200 without reprocessing.
  3. If it succeeds, run the handler and commit.

Return 200 Fast, Do The Work Async

The webhook HTTP handler should acknowledge receipt in milliseconds and enqueue the actual work. Anything you do inline — database writes, downstream API calls, sending your own follow-up mail — is a chance to time out and trigger a provider retry, which turns one event into many.

Replay Protection

Signature verification proves the payload came from the provider. It does not prove it came recently. Include the timestamp header most providers ship, and reject events older than a few minutes unless you have an explicit reason to accept them. This protects against an attacker replaying a captured signed payload later.

What To Do With Each Event

  • bounced (hard): suppress the recipient, record the SMTP reply on the user, flag their profile.
  • complained: suppress, and page whoever owns deliverability if you cross a rolling threshold.
  • delivered: update the outbound message row so your support console can answer “did they get it?”
  • deferred spike: alert. It usually maps to a reputation issue at a specific receiver.

Send transactional email that lands.

MailRoundup is a transactional-only pipe for receipts, confirmations, password resets, and alerts. One endpoint, honest logs, no marketing traffic in the way.