Email Webhooks: Track Delivery and Handle Events Reliably
Build a reliable email webhook receiver with signature verification, durable storage, duplicate handling and useful delivery-state tracking.
TL;DR
- A successful send request means the provider accepted work; delivery events report what happened afterward.
- Verify webhook authenticity against the original request bytes before trusting or processing an event.
- Store events durably, handle duplicate deliveries and avoid assuming they arrive in order.
- Use bounce, complaint and unsubscribe signals to update sending decisions, not just a dashboard counter.
Why a send response is only the beginning
Your application asks an email service to send a receipt and receives an email identifier. That identifier is useful for tracking, but it does not prove the customer has received or read the message. Processing, recipient-server acceptance, rejection and later engagement happen after the initial API request.
An email webhook carries those later events to an endpoint your application controls. It lets a product connect a delivery outcome to the original account action. For example, a support screen can distinguish a receipt that is still processing from one that bounced because the recipient address was invalid.
Start with a small event model. Store the provider's email identifier next to your own business event identifier. Keep a receipt or password-reset attempt separate from the user's account: one account can have many messages and outcomes over time.
Build a durable receiver
The receiver has two jobs: establish that a request is authentic and record enough information to process it reliably. Business actions can happen afterward in a worker.
Read the raw request body once. Validate the signature using the provider's documented algorithm, headers and active signing secret. Parsing and re-serializing JSON can change bytes, so it is not a safe substitute for the original signed body. Follow the current MailBlastr documentation for its supported webhook verification contract; header names and signature formats are provider-specific.
After verification, persist the event and enqueue its processing in one reliable operation. Return success after the event is durably recorded. Returning success before storage creates a loss window: the provider may consider the event delivered even if your application crashes immediately afterward.
Keep the public handler fast. Fetching a customer profile, rendering an email and calling several unrelated services before acknowledging the webhook makes a temporary dependency failure look like an endpoint failure.
Expect retries and duplicates
A provider can retry because its request timed out even when your database committed successfully. This creates two deliveries of one event without any malicious behavior.
Prefer a documented stable event identifier for deduplication. If a provider's contract does not offer one, design a documented key from stable fields that actually distinguish events. The email identifier alone is insufficient: one email can have a delivery event, a bounce and several engagement events.
Use a uniqueness constraint for the event key and make downstream actions idempotent too. Deduplicating the inbox table does not prevent a worker from applying the same event twice if it crashes after updating a contact but before marking its job complete.
Store the provider event time and your receipt time separately. The difference can reveal a delayed retry, and it prevents a late-arriving event from appearing to have occurred just now.
Model outcomes without losing history
| Signal | Useful application response |
|---|---|
| Accepted or queued | Show that sending work exists and retain its identifier |
| Delivered | Record recipient-server acceptance, without claiming inbox placement |
| Bounced | Save the diagnostic and apply the appropriate suppression or retry policy |
| Complained | Stop the affected sending according to your suppression policy |
| Unsubscribed | Update the specific subscription and enforce it before future sends |
| Opened or clicked | Treat as an engagement signal with measurement limitations |
Do not overwrite every event into one status field and discard the timeline. A chronological log is valuable when an event arrives late or two signals need different responses. Keep a derived status for convenient display, but retain the evidence used to calculate it.
An open event is not proof that a person read the message. Privacy features and automated fetching can affect tracking. A delivery event also does not tell you whether the message landed in the main inbox, another tab or spam.
Test failure paths deliberately
Use a non-production receiver or controlled test recipients. Verify a normal event, the same event delivered twice, a malformed signature, a valid event with an unsupported type, and two events arriving in reverse order.
Then interrupt processing after the event is stored and confirm the worker resumes without repeating the business effect. Temporarily make storage unavailable and check that the endpoint does not return a misleading success. Finally, test a subscription change against a message that was already queued.
Log identifiers and processing outcomes rather than full message bodies or signing secrets. Support generally needs a timestamp, event type, message reference and diagnostic code. Restrict access to any stored payload that includes recipient details.
Common questions
Should a webhook send another email immediately?
Usually it should record an event and let a separate worker make that decision. Otherwise a delivery event can trigger a send that creates another event and accidentally form a loop.
Can I retry every bounced message?
No. Inspect the failure classification and recipient history first. The bounce-rate guide explains how to separate invalid recipients from temporary delivery problems.
Does switching from SMTP to an API remove the need for webhooks?
No. Submission and later outcomes remain different stages. Choose the interface that fits your application, then connect its event reporting; see email API versus SMTP.