Quick Use
- Add endpoint at resend.com/webhooks, select
email.*events, copy signing secret - Build /resend/webhook handler that verifies svix-signature
- Route by event.type to suppress / followup / alert
Intro
Resend fires webhooks for every email lifecycle event — delivered, opened, clicked, bounced, complained, delivery_delayed. Wire them to your AI agent and you can trigger automated follow-ups when an email opens, suppress recipients on hard bounce, or alert when complaint rates spike. Best for: cold outreach agents, transactional pipelines that need bounce hygiene, marketing automations with engagement-based branching. Works with: any HTTPS endpoint, Resend ≥ 2024. Setup time: 15 minutes.
FastAPI webhook endpoint
import os, hmac, hashlib, json
from fastapi import FastAPI, Request, HTTPException
from anthropic import Anthropic
app = FastAPI()
SIGNING_SECRET = os.environ["RESEND_WEBHOOK_SECRET"].encode()
claude = Anthropic()
def verify(payload: bytes, sig_header: str) -> bool:
expected = hmac.new(SIGNING_SECRET, payload, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, sig_header)
EVENT_HANDLERS = {
"email.opened": "schedule_followup",
"email.clicked": "log_engagement",
"email.bounced": "suppress_recipient",
"email.complained":"alert_compliance",
}
@app.post("/resend/webhook")
async def resend_webhook(request: Request):
payload = await request.body()
sig = request.headers.get("svix-signature") or ""
if not verify(payload, sig.split(",")[-1]):
raise HTTPException(401, "bad signature")
event = json.loads(payload)
handler = EVENT_HANDLERS.get(event["type"])
if not handler:
return {"status": "ignored"}
# Hand off to Claude for next-step decision
claude.messages.create(
model="claude-3-5-haiku-20241022",
max_tokens=512,
tools=[{"name": handler, "input_schema": {"type": "object", "properties": {
"to": {"type": "string"}, "subject": {"type": "string"}, "campaign_id": {"type": "string"},
}}}],
messages=[{"role": "user", "content": f"Resend event: {event['type']}\n{event['data']}"}],
)
return {"status": "handled"}Configure the webhook
In Resend dashboard → Webhooks → Add endpoint:
- URL:
https://your-app.com/resend/webhook - Events: select
email.*(delivered, opened, clicked, bounced, complained, delivery_delayed) - Save and copy the signing secret → put in
RESEND_WEBHOOK_SECRETenv var
Event payload structure
{
"type": "email.opened",
"created_at": "2026-05-11T14:23:00Z",
"data": {
"email_id": "8d22a0b1-...-...-...",
"to": ["user@example.com"],
"from": "TokRepo <hi@tokrepo.com>",
"subject": "Welcome to TokRepo",
"tags": [{"name": "campaign", "value": "welcome-2026-05"}]
}
}Production hygiene
- Hard bounces → mark recipient suppressed immediately. Sending again hurts your domain reputation.
- Complaints → suppress AND lower send volume for the source campaign.
- Delivery delays → usually transient. Alert only on >24h delay or repeating recipients.
- Idempotency → store
event['data']['email_id'] + event['type']to dedupe retries.
FAQ
Q: Open tracking — is it accurate? A: Opens fire on tracking-pixel load. Image-blocking clients (Apple Mail Privacy Protection, many corporate clients) inflate or null open counts. Use opens as a directional signal, not a precise metric. Clicks are far more reliable.
Q: Why use webhooks vs polling the API?
A: Webhooks are real-time; polling burns rate limit and adds latency. The Resend Emails API is fine for status-on-demand (emails.retrieve) but webhooks are the right primitive for event-driven agent flows.
Q: How do I test webhooks locally?
A: Use ngrok http 8000 to tunnel; paste the ngrok URL into Resend webhook config. Resend dashboard has a 'Send test event' button to replay payloads without sending real email.
Source & Thanks
Built by Resend. Webhook docs at resend.com/docs/dashboard/webhooks/introduction.
resend/resend-node — official SDK