Theme

Resend API notes

What the Resend adapter does under the hood — the endpoints, the limits, the quirks worth knowing, and the capabilities it declares.

Capabilities

CapabilityValueConsequence
one_off_sendtrueCan be the one-off sender.
broadcast_sendfalseNo bulk API, so broadcasts fan out through the paced queue — with a full report.
custom_headerstrueOur headers are passed through.
injects_unsubscribe_headersfalseNothing is auto-injected, so the RFC 8058 headers are entirely ours.
send_time_suppressiontrueSilent, region-wide drops for bounces and complaints.
suppression_read:noneNo API at all. The nightly suppression sync doesn't run.
suppression_webhookstrueThe only route for Resend-side suppression.
engagement_webhooksfalsePer-domain tracking stays off — tracking is ours.
push_unsubscribefalseNo write API. The standing guard covers it.
remote_deletefalseGDPR erase can't remove the address Resend-side.

Sending

CaseEndpoint
One messagePOST /emails
More than onePOST /emails/batch, chunked at 100

Rate limit: 10 requests/second, team-wide. The adapter declares emails_per_minute: 600, and the sender's pacer counts every send against that budget — one message per request through the queue, so 600 a minute is the honest ceiling.

Headers

"headers": {
  "List-Unsubscribe": "<https://your-instance.com/u/SIGNED_TOKEN>",
  "List-Unsubscribe-Post": "List-Unsubscribe=One-Click"
}

Resend injects nothing, so these are ours. The token is the same signed token the subscription page takes, which is what attributes a header unsubscribe to the exact email it came from. Both halves matter: without List-Unsubscribe-Post, a mail client shows a link rather than its own unsubscribe button.

Transactional mail carries no unsubscribe header. You can't opt out of your own password reset.

Webhooks

Resend eventNormalizes to
email.delivereddelivered
email.bouncedbounced
email.complainedcomplained
email.suppressedsuppressed
anything elseacknowledged and ignored

A bounce's type maps to our bounce kind: Permanent → hard (suppresses the person), Transient → soft (doesn't — a full mailbox is a try-again, not a dead address).

Signed with Svix: HMAC-SHA256 over svix-id.svix-timestamp.body, base64, compared in constant time, with a five-minute freshness window so a captured request can't be replayed tomorrow. Multiple v1,… signatures are accepted so a secret can be rotated without dropping anything mid-flight. See Webhooks for the shared contract.

Quirks

Credentials

SettingWhat
provider.resend.api_keyThe API key.
provider.resend.webhook_secretThe Svix signing secret, whsec_….

Both are entered on the Settings page and encrypted in the instance database, and neither ever appears in a definitions repo.

verify_config calls GET /domains and fails when no domain is verified, naming the ones still pending — reporting a good connection that would refuse every send isn't a verification.

See also: Resend setup · Webhooks · Write your own adapter