# Resend Webhooks — Email Delivery Events for AI Agents > Resend webhooks fire on delivery, open, click, bounce, complaint. Route to your agent for follow-ups, deliverability monitoring, cleanup. ## Install Save the content below to `.claude/skills/` or append to your `CLAUDE.md`: ## Quick Use 1. Add endpoint at resend.com/webhooks, select `email.*` events, copy signing secret 2. Build /resend/webhook handler that verifies svix-signature 3. 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 ```python 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_SECRET` env var ### Event payload structure ```json { "type": "email.opened", "created_at": "2026-05-11T14:23:00Z", "data": { "email_id": "8d22a0b1-...-...-...", "to": ["user@example.com"], "from": "TokRepo ", "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](https://github.com/resend). Webhook docs at [resend.com/docs/dashboard/webhooks/introduction](https://resend.com/docs). > > [resend/resend-node](https://github.com/resend/resend-node) — official SDK --- ## 快速使用 1. 在 resend.com/webhooks 加 endpoint,选 `email.*` 事件,复制签名 secret 2. 写 /resend/webhook 处理器验 svix-signature 3. 按 event.type 路由到 suppress / followup / 告警 --- ## 简介 Resend 对每个邮件生命周期事件触发 webhook —— delivered(已投递)、opened(已打开)、clicked(已点击)、bounced(退信)、complained(投诉)、delivery_delayed(投递延迟)。接到 AI agent 就能在邮件打开时触发自动跟进、硬退信时抑制收件人、投诉率飙升时告警。适合冷启动外联 agent、需要退信清理的事务流水线、按互动分支的营销自动化。任何 HTTPS endpoint,Resend ≥ 2024 都可用。装机时间 15 分钟。 --- ### FastAPI webhook endpoint ```python 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, "签名错") event = json.loads(payload) handler = EVENT_HANDLERS.get(event["type"]) if not handler: return {"status": "ignored"} # 交给 Claude 决定下一步 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['type']}\n{event['data']}"}], ) return {"status": "handled"} ``` ### 配置 webhook Resend 仪表盘 → Webhooks → Add endpoint: - URL:`https://your-app.com/resend/webhook` - 事件:选 `email.*`(delivered / opened / clicked / bounced / complained / delivery_delayed) - 保存并复制**签名 secret** → 放进 `RESEND_WEBHOOK_SECRET` 环境变量 ### 事件 payload 结构 ```json { "type": "email.opened", "created_at": "2026-05-11T14:23:00Z", "data": { "email_id": "8d22a0b1-...-...-...", "to": ["user@example.com"], "from": "TokRepo ", "subject": "Welcome to TokRepo", "tags": [{"name": "campaign", "value": "welcome-2026-05"}] } } ``` ### 生产清洁 - **硬退信** → 立即把收件人标记 suppressed。再发会伤域信誉。 - **投诉** → suppress 并降低该 campaign 发送量。 - **投递延迟** → 通常临时。仅在 >24 小时延迟或重复收件人时告警。 - **幂等** → 存 `event['data']['email_id'] + event['type']` 去重。 --- ### FAQ **Q: 打开追踪 —— 准吗?** A: Opens 在追踪 pixel 加载时触发。屏蔽图片的客户端(Apple Mail Privacy Protection、很多企业客户端)让 open 数虚高或为零。把 open 当方向信号,不是精确指标。Click 可靠很多。 **Q: 为啥用 webhook 不轮询 API?** A: Webhook 实时;轮询烧速率限制加延迟。Resend Emails API 适合按需查状态(`emails.retrieve`),但 webhook 是事件驱动 agent 流程的对的 primitive。 **Q: 本地怎么测 webhook?** A: 用 `ngrok http 8000` 打通隧道;把 ngrok URL 粘进 Resend webhook 配置。Resend 仪表盘有 'Send test event' 按钮,不发真邮件就能重放 payload。 --- ## 来源与感谢 > Built by [Resend](https://github.com/resend). Webhook docs at [resend.com/docs/dashboard/webhooks/introduction](https://resend.com/docs). > > [resend/resend-node](https://github.com/resend/resend-node) — official SDK --- Source: https://tokrepo.com/en/workflows/resend-webhooks-email-delivery-events-for-ai-agents Author: Resend