OneShot

All packsTransactional Email Deliverability CoreSolve

resend webhook dedupe svix id vs email id

email_id looks like the dedupe key. It isn't.

Resend's data.email_id is shared across a message's bounced, delivered, and complained events -- deduping on it collapses distinct events into one.

3 of 3 runs passedOne decision inside Transactional Email Deliverability Core (nextjs-postgres-resend).

What goes wrong

Resend webhook payloads carry no top-level event id, and the obvious candidate -- data.email_id -- is shared across every event type that happens to one message: sent, delivered, bounced, and complained can all carry the same email_id. Deduplicating on it looks correct in a quick test (the same event delivered twice does dedupe) and silently collapses genuinely distinct events into one, dropping real signal like a complaint that arrived after a delivery notification for the same message.

Why agents get it wrong

email_id is right there in the payload, named like an identifier, and does correctly dedupe the retry-of-the-same-event case, which is the case most webhook-handling code is written and tested against. It takes reading Resend's event-shape documentation specifically for "can two different event types share this field" to notice the collision -- not something a dedupe implementation naturally surfaces on its own.

What Transactional Email Deliverability Core pre-decides

The pack's webhook decisions dedupe on the svix-id header instead -- unique per delivery attempt group, supplied by the Svix transport layer Resend signs with, not by Resend's own payload. It's stored as email_events.provider_event_id with a UNIQUE constraint, so the insert itself is the idempotency check: a duplicate delivery throws a conflict, is reported as {duplicate: true}, and the route still returns 200 so Svix doesn't retry forever.

Source: ARCHITECTURE.md 'Webhooks (Resend -> Svix transport)', decision 3

Coming soon — $79Full receipt and details for Transactional Email Deliverability Core

Related problems