← Back to Blog

Real-Time Email Webhooks: The Key to Responsive AI Agents

Your AI agent needs to know when an email arrives. The old way is polling: check the inbox every few seconds, see if anything's new, repeat forever. It works, technically. It's also wasteful, slow, and a terrible foundation for responsive AI agents. Webhooks are the better answer — and they're not even close.

Polling vs. Webhooks

With polling, your agent asks the server "any new mail?" on a loop. If you poll every 10 seconds, that's 8,640 API calls per day per inbox — and most of them return nothing. You're burning rate limits, paying for API calls, and your agent still has up to a 10-second delay before it sees a new message.

With webhooks, the server tells your agent the moment something happens. No wasted calls, no delay. An email arrives, and within milliseconds your webhook endpoint receives a POST request with the full message payload. Your agent can start processing immediately.

The numbers make the case clearly:

  • Polling (10s interval): 8,640 API calls/day, up to 10s latency, most calls return empty.
  • Webhooks: 0 wasted calls, sub-second latency, only fires when there's real data.

For a single inbox, polling is annoying but manageable. For 50 inboxes? You're making 432,000 API calls per day to stay current. Webhooks scale to any number of inboxes with zero additional overhead on your side.

Webhook Event Types

Not all email events are created equal. Dead Simple's webhook system fires events for the moments that matter to your agent:

  • message.received — A new email arrived in one of your inboxes. The payload includes the sender, subject, body (plain text and HTML), headers, and any attachment metadata. This is the event most agents care about.
  • message.sent — An outgoing email was successfully accepted for delivery. Use this to confirm sends, update conversation state, or trigger follow-up workflows.
  • message.bounced — An email couldn't be delivered. The payload includes the bounce type (hard or soft), the reason, and the original recipient. Hard bounces mean the address is invalid — your agent should stop sending there. Soft bounces are temporary and may resolve on retry.

Each event is delivered as a JSON payload to the webhook URL you configure per inbox or globally for your account. You can subscribe to all events or just the ones you need.

Securing Webhooks with HMAC Signing

Anyone who knows your webhook URL can send fake payloads to it. Without verification, your agent might process a spoofed "message.received" event and take action on a message that never existed. This is why every Dead Simple webhook request includes an HMAC-SHA256 signature.

Here's how it works: when you register a webhook, you receive a signing secret. Every webhook delivery includes an X-DeadSimple-Signature header containing an HMAC-SHA256 hash of the request body, computed with your secret. Your handler computes the same hash and compares. If they match, the payload is authentic.

import hmac, hashlib

def verify_signature(payload, signature, secret):
    expected = hmac.new(
        secret.encode(), payload, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, signature)

Always use hmac.compare_digest instead of == for comparison. Regular string comparison is vulnerable to timing attacks that can leak your secret one character at a time.

Retry Logic and Reliability

Networks fail. Servers restart. Your endpoint might be down for maintenance. A reliable webhook system needs retry logic built in, and you need to design your handler to work with it.

Dead Simple retries failed webhook deliveries with exponential backoff: 10 seconds, 30 seconds, 1 minute, 5 minutes, 30 minutes, and then hourly for up to 24 hours. A delivery is considered failed if your endpoint returns a non-2xx status code or doesn't respond within 10 seconds.

This means your handler needs to be idempotent — processing the same event twice should produce the same result as processing it once. The simplest approach is to track event IDs:

processed_events = set()  # Use Redis or a database in production

def handle_webhook(event):
    if event["id"] in processed_events:
        return  # Already handled
    processed_events.add(event["id"])
    # Process the event...

Building a Webhook Handler

Here's a complete webhook handler using Python and Flask that verifies signatures, handles idempotency, and processes incoming email events:

import hmac, hashlib, json
from flask import Flask, request, jsonify

app = Flask(__name__)
WEBHOOK_SECRET = "your_signing_secret_here"

@app.route("/webhook", methods=["POST"])
def handle_webhook():
    # 1. Verify the signature
    signature = request.headers.get("X-DeadSimple-Signature")
    if not signature:
        return jsonify({"error": "Missing signature"}), 401

    expected = hmac.new(
        WEBHOOK_SECRET.encode(),
        request.data,
        hashlib.sha256
    ).hexdigest()

    if not hmac.compare_digest(expected, signature):
        return jsonify({"error": "Invalid signature"}), 401

    # 2. Parse the event
    event = request.get_json()

    # 3. Route by event type
    if event["type"] == "message.received":
        msg = event["data"]
        print(f"New email from {msg['from']}: {msg['subject']}")
        # Pass to your AI agent for processing

    elif event["type"] == "message.bounced":
        print(f"Bounce: {event['data']['reason']}")
        # Remove bad address from your contact list

    return jsonify({"status": "ok"}), 200

The key principles: always verify the signature first, always return a 200 quickly (do heavy processing asynchronously), and always handle unknown event types gracefully so your handler doesn't break when we add new events.

How Dead Simple's Webhook System Works

Under the hood, Dead Simple processes incoming email through a pipeline that extracts metadata, parses content, and dispatches webhook events in parallel with message storage. This means your webhook fires at roughly the same time the message appears in the API — there's no queuing delay between "email received" and "webhook delivered."

Configuration is simple. In the dashboard or via the API, set a webhook URL and choose which events to subscribe to:

client.webhooks.create(
    url="https://myapp.com/webhook",
    events=["message.received", "message.bounced"],
    secret="your_signing_secret"
)

You can also configure webhooks per inbox if different inboxes route to different services. Every webhook delivery is logged and visible in the dashboard, including the payload, response code, and any retry attempts — making it easy to debug integration issues without guessing.

The Bottom Line

Responsive AI agents need real-time data. Polling introduces latency, wastes resources, and doesn't scale. Webhooks deliver events the moment they happen, with cryptographic verification and automatic retries built in.

If you're building an AI agent that interacts with the outside world via email, webhooks aren't optional — they're the foundation of a responsive, reliable system.

Ready to build real-time email into your agent? Join the waitlist and start building with Dead Simple.

Build real-time email agents

Webhooks, HMAC signing, and automatic retries — all included on every plan, including free.