# Email Webhooks: Tracking Delivery, Opens, Clicks, and Bounces in Your App

> Email webhooks push per-message events — delivered, opened, clicked, bounced, complained, unsubscribed — to your HTTPS endpoint as they happen. Handle them idempotently (events can arrive twice), verify signatures (anyone can POST JSON), respond 200 fast and process async. The two events your product should always act on: bounced (mark the address invalid) and complained (stop all non-critical mail immediately).

Source: https://www.mailblastr.com/blog/email-webhooks-guide
Published: 2026-08-02 · Last reviewed: 2026-08-02 · Tags: webhooks, email-api, events, developers

---

Sending an email is a request; knowing what happened to it is a webhook. Every serious email platform can POST per-message events to your backend as they occur — and the difference between teams that trust their email system and teams that don't usually comes down to whether those events are wired into the product. Here's the full pattern.

## The event vocabulary

The lifecycle every provider reports, with what each event should trigger in your app:

- **`sent` / `delivered`** — accepted by the receiving server. Use for receipt confirmation and latency monitoring. Delivered ≠ inboxed (spam-folder placement is invisible to senders).
- **`bounced`** — the receiver rejected it. On a *hard* bounce, [mark the address invalid immediately](https://www.mailblastr.com/blog/reduce-email-bounce-rate): flag the user record, prompt for a new address on next login, stop non-critical sends. Your provider should suppress it globally at the same time.
- **`complained`** — the recipient clicked "mark as spam". Treat as a super-unsubscribe: stop everything except strictly transactional mail. Complaint rates above 0.1% [damage your whole domain](https://www.mailblastr.com/blog/why-emails-go-to-spam).
- **`opened`** — tracking pixel loaded. Fine for send-over-send trends; unreliable per-recipient because Apple Mail Privacy Protection prefetches pixels (see [open-rate accuracy](https://www.mailblastr.com/docs/kb/open-rates-accuracy)).
- **`clicked`** — a tracked link was followed. The strongest engagement signal you get; feed it to engagement scoring and automation triggers.
- **`unsubscribed`** — sync to your preference center the moment it fires, not on a nightly job. Sending to someone who unsubscribed an hour ago is how complaints happen.

MailBlastr signs and delivers [all of these](https://www.mailblastr.com/docs/webhooks/events), plus contact and domain lifecycle events.

## Building a handler that survives production

The reference shape, in an Express-style handler:

```js
app.post('/webhooks/email', async (req, res) => {
  // 1. Verify the signature BEFORE parsing anything else.
  if (!verifySignature(req.headers['mb-signature'], req.rawBody, SECRET)) {
    return res.status(401).end();
  }

  // 2. Acknowledge fast; do real work async.
  const event = JSON.parse(req.rawBody);
  await queue.push(event);
  res.status(200).end();
});

async function processEvent(event) {
  // 3. Idempotency: at-least-once delivery means duplicates WILL arrive.
  const fresh = await db.insertEventOnce(event.id); // ON CONFLICT DO NOTHING
  if (!fresh) return;

  switch (event.type) {
    case 'email.bounced':    return markEmailInvalid(event.data.to);
    case 'email.complained': return muteAllMarketing(event.data.to);
    case 'email.clicked':    return recordEngagement(event.data.to, event.data.link);
    // ...
  }
}
```

The four rules embedded there:

**Verify signatures.** The endpoint is public; the signature (HMAC over the raw body) is the only proof the event came from your provider. Verify against the *raw* bytes — parsing and re-serializing JSON breaks the MAC. [Verification docs](https://www.mailblastr.com/docs/webhooks/verify) include per-language examples.

**Respond 200 immediately.** Providers time out slow endpoints and count them as failures. Queue first, process after.

**Process idempotently.** Retries mean duplicates. A unique-key insert on the event ID makes them free.

**Handle out-of-order arrival.** `clicked` can beat `delivered` to your endpoint. Store events as facts, derive state from the full set rather than assuming sequence.

## Testing without sending real mail

Wire a request bin (or `curl` your own endpoint) with sample payloads first, then use [test events](https://www.mailblastr.com/docs/api/webhooks-test) to fire signed samples at your handler before going live. Rotate secrets periodically — [rotation](https://www.mailblastr.com/docs/api/webhooks-rotate) without downtime means accepting two secrets during the overlap window.

## Reconciliation: the part everyone skips

Webhooks are push; sometimes push fails silently (your endpoint was down past the retry window, a deploy dropped the queue). Schedule a daily reconcile against the provider's [logs/events API](https://www.mailblastr.com/docs/api/emails-list) for the previous day: fetch terminal states, diff against your records, replay the gaps. Thirty days of searchable history is your safety margin — it turns "we lost a day of bounce events" from an incident into a cron job.

Wired this way, email stops being a black box: bounces update your user records within seconds, complaints stop future annoyance automatically, and engagement feeds your product analytics. The whole loop — signed webhooks, test fixtures, searchable logs — is included on [every MailBlastr plan](https://www.mailblastr.com/pricing), free tier included.


## Frequently asked questions

**What email events should my application handle?**

Minimum: bounced (mark the address bad, stop sending) and complained (the user marked spam — cease non-critical mail). Valuable next: delivered (confirm receipts went out), clicked (engagement scoring), unsubscribed (sync preferences). Opens are useful for trends but unreliable per-recipient because of Apple Mail Privacy Protection prefetching.

**Why do I need to verify webhook signatures?**

Your webhook endpoint is a public URL that accepts POSTs. Without verification, anyone who discovers it can forge events — fake bounces to suppress your users, fake unsubscribes, junk analytics. Providers sign each request (HMAC over the payload); verify before trusting a byte of it.

**Why must webhook handlers be idempotent?**

Delivery is at-least-once: if your endpoint times out or returns 5xx, the provider retries, so the same event can arrive twice. Process on a unique event ID with an idempotency check (INSERT ... ON CONFLICT DO NOTHING pattern) so duplicates are no-ops.

**Are email open rates still accurate?**

Directionally yes, absolutely no. Apple Mail Privacy Protection preloads tracking pixels for a large share of consumer inboxes, inflating opens regardless of reader behavior. Use opens for trend comparison between sends; use clicks and replies for per-recipient truth.

**What happens if my webhook endpoint goes down?**

Providers retry with backoff for a window (hours to days, provider-dependent), then drop or disable. Design for it: respond 200 immediately and queue the work, monitor failure rates, and reconcile gaps against the provider's logs API — MailBlastr keeps 30 days of searchable events for exactly this.
