SkillsMay 11, 2026·4 min read

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.

Agent ready

This asset can be read and installed directly by agents

TokRepo exposes a universal CLI command, install contract, metadata JSON, adapter-aware plan, and raw content links so agents can judge fit, risk, and next actions.

Stage only · 17/100Stage only
Agent surface
Any MCP/CLI agent
Kind
Skill
Install
Stage only
Trust
Trust: New
Entrypoint
Asset
Universal CLI install command
npx tokrepo install e60ca183-5be1-4d16-ae96-2b7b3bedba46
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_SECRET env 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.


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

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

{
  "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

🙏

Source & Thanks

Built by Resend. Webhook docs at resend.com/docs/dashboard/webhooks/introduction.

resend/resend-node — official SDK

Discussion

Sign in to join the discussion.
No comments yet. Be the first to share your thoughts.

Related Assets