Email webhooks explained
A webhook is how your provider tells your application that something happened, without your application having to keep asking. When an email is delivered, bounces, or is marked as spam, the provider sends an HTTP request to a URL you control carrying the details of that event. Your code reacts to it there and then.
Here is what a webhook is, the events you can expect, why signed webhooks matter, and how to consume them.
Webhooks vs polling
Without webhooks, the only way to find out what happened to a message is to ask repeatedly: send a request every few seconds asking "has this been delivered yet?" That is polling, and it is wasteful. Most of the requests come back with no news, you hammer the provider's API for nothing, and you still learn about events late, only as often as you poll.
A webhook inverts that. Instead of you asking, the provider tells you. The moment an event happens, it makes a single POST request to your endpoint with the event data. You do no work until there is something to react to, and you hear about it immediately. For anything event-driven, a bounce that should pause a dunning sequence, an inbound reply that should open a support ticket, webhooks are the right tool and polling is the wrong one.
The common email event types
Most email events fall into a handful of types your webhook will receive:
- delivered - the receiving server accepted the message. Confirmation it got there.
- bounced - the message could not be delivered. The event says whether the bounce was hard or soft so you can suppress a dead address or retry a temporary failure. See how to handle email bounces.
- complained - the recipient marked the message as spam. A strong signal to stop sending to that address.
- opened - the recipient opened the message. Useful as an engagement signal, though it is best treated as a soft hint rather than a hard fact, since image blocking and privacy features make it unreliable.
- inbound received - someone sent an email *to* one of your addresses, and the provider parses it and posts it to you as an event. This is how you build reply handling, support inboxes and address-based automation on top of email.
Delivery events and inbound mail arrive through the same mechanism: an HTTP request to your endpoint describing what happened.
Why signed webhooks matter
Your webhook endpoint is a public URL. Anyone who learns it, or simply guesses it, can send it a POST that looks like a real event. If you trust every request that arrives, an attacker can forge a "bounced" event to get a real customer suppressed, or a fake "inbound received" to inject a message into your support queue. An unverified webhook endpoint is an open door.
Signing closes it. The provider computes an HMAC signature over the request body using a secret only the two of you know, and sends that signature in a header. Your endpoint recomputes the signature with the same secret and checks it matches. If it does, the event genuinely came from your provider and was not altered in transit. If it does not, you reject the request. This is the difference between knowing an event is real and merely hoping it is, and it is why you should only consume webhooks that are signed.
How to consume them
Consuming webhooks is deliberately simple. You stand up a small HTTP endpoint and tell your provider its URL. From then on:
- The provider sends a
POSTto your endpoint for each event. - Your endpoint verifies the HMAC signature before trusting anything in the body.
- It reads the event and does its work: update a record, suppress an address, open a ticket.
- It returns a
2xxstatus to tell the provider the event was received.
Two practical rules keep this robust. Return `2xx` quickly. The provider treats your status code as an acknowledgement, so acknowledge first and do heavy work afterwards, ideally on a background queue, rather than making the provider wait. Expect retries. If your endpoint is down or returns an error, a good provider retries the event later with backoff, so a brief outage on your side does not lose events. The flip side is that you should make your handler idempotent, because a retry means you may see the same event more than once and should not act on it twice.
How Mailhuset handles it
Mailhuset delivers every event as a signed webhook. Each request carries an HMAC signature computed with your secret, so your endpoint can verify the event is genuinely from us before acting on it. Delivery events, bounces and complaints for your outgoing mail, and inbound messages sent to your addresses, all arrive the same way: a POST to your endpoint that you verify, handle and acknowledge with a 2xx. Failed deliveries are retried, so a momentary outage on your side does not cost you an event. It is all one product, transactional email, inbound and broadcasts, sending from the EU by default.
See webhooks and inbound for how to set up your endpoint and start receiving events.