Email SDK
Reference

Webhooks

Verify Resend and Mailgun webhook requests and normalize Resend, Postmark, and Mailgun delivery events.

Import webhook helpers from @opencoredev/email-sdk/webhooks, not the root SDK. They use Web Crypto and do not create an email client, send email, or emit telemetry.

Verify before processing

The example verifies and normalizes only. In production, atomically persist the event and deduplicate by provider plus deliveryId before acknowledging it. Apply request-size limits, avoid logging payloads or credentials, and process side effects through durable storage. Timestamp checks alone do not stop replay within the acceptance window.

Verification support

ProviderHelperAuthenticated content
ResendverifyResendWebhookSvix ID, timestamp, and original raw UTF-8 body
MailgunverifyMailgunWebhookTimestamp and token only
PostmarkNoneConfigure authentication separately in your application

Both helpers return Promise<boolean> and fail closed (false) on missing secrets, invalid signatures, malformed data, non-object JSON, or timestamps outside the tolerance. They accept secret: string | readonly string[] for key rotation, toleranceSeconds (default 300), and now (epoch milliseconds, default Date.now()). Both stale and future timestamps are checked; tolerance must be finite and nonnegative. Keep clocks synchronized. A larger tolerance may be needed for delayed Mailgun processing, but increases replay exposure.

Resend accepts headers: Headers | Readonly<Record<string, string | undefined>> with case-insensitive svix-id, svix-timestamp, and svix-signature names. Pass the signing secret (whsec_…), not an API key. Multiple space-separated versioned Svix signatures are supported; a valid v1 signature from any configured key succeeds. Never parse and reserialize the body before verifying it.

Mailgun

This helper accepts Mailgun's JSON event webhook envelope with a nested signature object containing timestamp, token, and hexadecimal signature. It does not handle inbound multipart/form-data routes. For subaccount events, explicitly select signatureField: "parent-signature" when using the parent account's signing key.

Mailgun signatures do not bind event-data

Mailgun signs only timestamp concatenated with token, not the event body. Changing event-data does not invalidate that signature. Require HTTPS, protect signing tokens, and persist replay protection for the token as well as delivery ID; consider Mailgun's documented TLS client authentication where available. Do not describe this helper as proof of body integrity.

Normalize delivery events

normalizeWebhookEvent({ provider, body, headers? }) returns Promise<NormalizedWebhookEvent>. It does not authenticate the request. Supported normalization providers are exactly "resend" | "postmark" | "mailgun"; other providers throw, even if the SDK can send through them. Invalid JSON, null, arrays, and primitive JSON also throw.

Result fieldMeaning
providerSupported provider name
deliveryIdStable delivery identity; use with provider for deduplication
providerMessageId?Original provider message identity, when present
type?Lowercased event name, with Resend's email. prefix removed
status?"delivered", "bounced", or "complained" only
payloadOriginal parsed JSON object; may contain personal data

Unknown events retain their type and payload without assigning delivery state. Mailgun failed maps to bounced only with permanent severity. Postmark Bounce with HardBounce, BadEmailAddress, or ManuallyDeactivated maps to bounced; transient and other supplied bounce classes do not. For compatibility, a bounce without a class maps to bounced. Postmark numeric ID values are converted to strings.

Resend prefers a payload ID, then the svix-id header. Other supported providers use their event IDs, including Postmark ID and Mailgun event-data.id. Without an ID, the helper hashes provider plus the raw body using SHA-256. This fallback deduplicates byte-identical retries only: whitespace changes, field reordering, or changed timestamps yield different IDs. It does not deduplicate semantically equivalent events.

Exported types: WebhookHeaders, WebhookProvider, WebhookDeliveryStatus, WebhookVerificationOptions, ResendWebhookVerificationOptions, MailgunWebhookVerificationOptions, NormalizeWebhookOptions, and NormalizedWebhookEvent.

Provider documentation

For managed persistence and delivery state, see Convex webhooks.

On this page