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.
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.
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.
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.
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).
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.
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.
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.
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 securelySubscribe to inbound and bounce events for a specific mailbox using the REST API.
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.
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.
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.
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.
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.
Each webhook delivery is signed with HMAC-SHA256. Your endpoint can cryptographically verify that the payload originated from MultiMail before executing any downstream logic.
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.
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.
Email infrastructure built for AI agents. Verifiable identity, graduated oversight, and a 50-tool MCP server. Formally verified in Lean 4.