React to Email Events in Real Time

MultiMail emits structured webhook payloads for every stage of the email lifecycle — delivery, bounce, inbound reply, open, complaint — so your agent can act the moment something changes.


Why this matters

AI agents that send email are flying blind without a reliable event stream. A message can bounce silently, a reply can sit unread for hours, or a complaint can go undetected until deliverability craters. Polling the inbox is slow, noisy, and misses transient events like delivery confirmations and bounce codes entirely. Without a real-time signal, agents cannot trigger retry logic, route replies to the right workflow, or escalate failures before they compound.


How MultiMail solves this

MultiMail emits a webhook for every material email event: delivery confirmed, soft bounce, hard bounce, inbound message received, spam complaint, and message opened. Each payload is signed with an HMAC-SHA256 signature so your endpoint can verify authenticity before processing. Events carry enough context — thread ID, mailbox, event type, timestamp, and SMTP diagnostic codes on bounces — that your agent can make a branching decision without a follow-up API call. You subscribe once per mailbox or domain, and MultiMail fans out events to your endpoint in order.

1

Register a webhook endpoint

POST to /v1/webhooks with your endpoint URL and the event types you care about (inbound, delivery, bounce, complaint, open). MultiMail returns a webhook ID and a signing secret you store securely.

2

Receive structured event payloads

When an event fires, MultiMail POSTs a JSON payload to your endpoint. Every payload includes event_type, mailbox, message_id, thread_id, timestamp, and event-specific fields (e.g., bounce_code and diagnostic on bounce events, from and subject on inbound events).

3

Verify the signature

Each request carries an X-MultiMail-Signature header — an HMAC-SHA256 of the raw request body using your signing secret. Reject any request where the signature does not match before executing downstream logic.

4

Route to the correct agent workflow

Branch on event_type. An inbound event triggers check_inbox or read_email to fetch the full message. A hard bounce triggers your suppression and retry logic. A complaint triggers immediate opt-out handling.

5

Update agent state

After processing, your agent calls tag_email or updates its own state store to mark the thread as handled. This prevents duplicate processing if a retry delivers the same event.


Implementation

Register a webhook subscription
python
import httpx

client = httpx.Client(
    base_url="https://api.multimail.dev",
    headers={"Authorization": "Bearer $MULTIMAIL_API_KEY"}
)

response = client.post("/v1/webhooks", json={
    "endpoint_url": "https://agents.acme.com/hooks/multimail",
    "mailbox": "[email protected]",
    "events": ["inbound", "delivery", "bounce", "complaint"],
    "description": "Support agent event stream"
})

webhook = response.json()
print(webhook["webhook_id"])   "cm"># wh_9f3a2c
print(webhook["signing_secret"])  "cm"># store this securely

Subscribe to inbound and bounce events for a specific mailbox using the REST API.

Verify signature and route events
python
import hashlib
import hmac
import os
from fastapi import FastAPI, Request, HTTPException
from multimail_sdk import MultimailClient

app = FastAPI()
client = MultimailClient(api_key=os.environ["MM_API_KEY"])
WEBHOOK_SECRET = os.environ["MM_WEBHOOK_SECRET"].encode()

@app.post("/hooks/multimail")
async def handle_event(request: Request):
    body = await request.body()
    sig = request.headers.get("X-MultiMail-Signature", "")
    expected = hmac.new(WEBHOOK_SECRET, body, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(sig, expected):
        raise HTTPException(status_code=401, detail="Invalid signature")

    event = await request.json()
    match event["event_type"]:
        case "inbound":
            await handle_inbound(event)
        case "bounce":
            await handle_bounce(event)
        case "complaint":
            await handle_complaint(event)
    return {"ok": True}

async def handle_inbound(event):
    "cm"># Fetch full message and dispatch to agent
    message = client.read_email(message_id=event["message_id"])
    await dispatch_to_agent(event["thread_id"], message)

async def handle_bounce(event):
    code = event.get("bounce_code", "")
    if code.startswith("5"):  "cm"># hard bounce
        client.tag_email(message_id=event["message_id"], tags=["hard-bounce"])
        await suppress_address(event["recipient"])

A FastAPI endpoint that verifies the HMAC signature and dispatches to the appropriate agent handler.

Process inbound replies with check_inbox
python
from multimail_sdk import MultimailClient

client = MultimailClient(api_key="$MULTIMAIL_API_KEY")

def on_inbound_event(event: dict):
    thread_id = event["thread_id"]
    mailbox = event["mailbox"]

    "cm"># Pull pending messages in this thread
    inbox = client.check_inbox(
        mailbox=mailbox,
        thread_id=thread_id,
        unread_only=True
    )

    for message in inbox["messages"]:
        content = client.read_email(message_id=message["message_id"])
        route_to_workflow(
            thread_id=thread_id,
            subject=content["subject"],
            body=content["body_text"],
            sender=content["from"]
        )

After an inbound webhook fires, use check_inbox to pull the full thread context before routing.

Bounce handling with retry and suppression
python
from multimail_sdk import MultimailClient
import time

client = MultimailClient(api_key="$MULTIMAIL_API_KEY")

SOFT_BOUNCE_RETRY_DELAY = 3600  "cm"># 1 hour

def on_bounce_event(event: dict):
    code = event.get("bounce_code", "")
    recipient = event["recipient"]
    message_id = event["message_id"]

    if code.startswith("4"):  "cm"># soft bounce — transient
        client.tag_email(message_id=message_id, tags=["soft-bounce", "pending-retry"])
        schedule_retry(recipient=recipient, delay=SOFT_BOUNCE_RETRY_DELAY)
    elif code.startswith("5"):  "cm"># hard bounce — permanent
        client.tag_email(message_id=message_id, tags=["hard-bounce", "suppressed"])
        add_to_suppression_list(recipient)
        notify_ops(f"Hard bounce {code} for {recipient}: {event.get(&"cm">#039;diagnostic')}")

Distinguish soft from hard bounces and implement the correct recovery path for each.


What you get

Zero polling overhead

Webhooks push events to your endpoint the moment they occur. Your agent does not burn API quota or introduce latency by polling check_inbox on a timer.

Structured payloads with full context

Every event includes thread_id, message_id, mailbox, and event-specific diagnostics. A bounce event carries the SMTP code and diagnostic string. An inbound event carries from, subject, and snippet. No follow-up lookup required to decide what to do.

HMAC signature verification

Each webhook delivery is signed with HMAC-SHA256. Your endpoint can cryptographically verify that the payload originated from MultiMail before executing any downstream logic.

Covers the full email lifecycle

Delivery confirmation, soft bounce, hard bounce, inbound message, spam complaint, and open events are all emitted. Agents can build complete state machines over email threads without gaps.

Automatic retry with exponential backoff

If your endpoint returns a non-2xx response, MultiMail retries with exponential backoff for up to 24 hours. You do not lose events due to transient endpoint downtime.


Recommended oversight mode

Recommended
monitored
Webhook-driven workflows process high volumes of events automatically — bounces, inbound replies, complaints — and require immediate action to be useful. Gating every event on human approval defeats the purpose of real-time triggers. Monitored mode lets the agent act autonomously on each event while giving the operator visibility into what fired and what the agent did. Reserve gated_all for the initial rollout period while you verify the routing logic is correct.

Common questions

What events does MultiMail emit webhooks for?
MultiMail emits webhooks for: inbound (a new message arrived), delivery (message accepted by recipient MTA), bounce (with bounce_code and diagnostic), complaint (spam report from recipient's mail provider), and open (recipient opened the message). You subscribe per event type when registering a webhook endpoint.
How do I verify that a webhook came from MultiMail?
Every webhook request includes an X-MultiMail-Signature header containing an HMAC-SHA256 hex digest of the raw request body, keyed with your webhook signing secret. Compute the expected digest on your side and use a constant-time comparison (hmac.compare_digest in Python, crypto.timingSafeEqual in Node.js) to verify. Reject any request where the signatures do not match.
What happens if my endpoint is down when an event fires?
MultiMail retries failed deliveries with exponential backoff for up to 24 hours. If your endpoint recovers within that window, it will receive all missed events. After 24 hours, undelivered events are dropped. You can inspect delivery attempts and status in the MultiMail dashboard.
Can I subscribe to events for an entire domain rather than a single mailbox?
Yes. When registering a webhook, omit the mailbox field and specify a domain instead. MultiMail will route events from all mailboxes on that domain to the endpoint. This is useful when you are dynamically provisioning mailboxes and do not want to register a new webhook for each one.
How should I prevent duplicate event processing?
Each event payload includes a unique event_id. Store processed event IDs in a fast key-value store (Redis, Cloudflare KV, etc.) and check before processing. MultiMail guarantees at-least-once delivery, so your handler must be idempotent. After processing, call tag_email with a handled tag so the thread state reflects completion.
What is the payload structure for an inbound event?
An inbound event payload includes: event_type ("inbound"), event_id, mailbox, message_id, thread_id, from, to, subject, snippet (first 200 characters of the body), and timestamp. To read the full body, call read_email with the message_id from the event payload.

Explore more use cases

The only agent email with a verifiable sender

Email infrastructure built for AI agents. Verifiable identity, graduated oversight, and a 50-tool MCP server. Formally verified in Lean 4.