MultiMail's inbound webhooks and gated_send oversight let AI agents draft contextual replies that a human reviews before anything reaches the customer.
Generic auto-responders erode trust. A customer emailing about a billing dispute doesn't want 'Thanks for contacting us — we'll respond within 2 business days.' They want an answer. But fully autonomous AI replies carry real risk: hallucinated policy details, wrong account information, or a tone-deaf response to an already-frustrated customer. Most teams are stuck between 'useless template' and 'fully autonomous AI' with no intermediate option that's actually safe to deploy.
MultiMail's inbound webhook delivers the full email to your agent the moment it arrives. The agent calls get_thread to load the complete conversation history, drafts a reply using your LLM of choice, then queues it via reply_email with gated_send mode. A human reviewer sees the draft alongside the original thread, approves or edits it, and MultiMail delivers. The agent handles reading and drafting; the human handles last-mile judgment. As you build confidence in specific email categories, you can shift them to monitored or autonomous mode without changing your agent's code.
Configure your MultiMail mailbox to POST to your webhook endpoint on every inbound message. The payload includes message_id, thread_id, sender, subject, and body text. Your agent receives this event and begins processing immediately — no polling required.
The agent calls get_thread with the thread_id from the webhook payload. This returns the complete conversation history — all prior messages, timestamps, and directions — so the draft accounts for everything that's already been said and avoids repeating questions already answered.
The agent passes the thread history and incoming message to your LLM with a system prompt that includes your product knowledge and any verified account data you've fetched from your own database. The model generates a reply scoped to what it actually knows.
The agent calls reply_email with the drafted response and oversight_mode set to gated_send. The message is held in the pending queue rather than delivered. Reviewers access the queue via list_pending and see the original email, full thread, and proposed reply side by side.
The reviewer calls decide_email with decision: approve, approve with a revised_body, or reject with a rejection_note. Approved messages deliver immediately. Rejected drafts can trigger a revision loop in your agent.
MultiMail fires a delivery event when the message sends. Your agent logs the outcome, calls tag_email to mark the thread resolved, and optionally updates your CRM or support system with the resolution details.
import hmac
import hashlib
from flask import Flask, request, jsonify
from multimail import MultimailClient
app = Flask(__name__)
client = MultimailClient(api_key="$MULTIMAIL_API_KEY")
WEBHOOK_SECRET = b"your_webhook_secret"
@app.route("/webhooks/inbound", methods=["POST"])
def handle_inbound():
sig = request.headers.get("X-MultiMail-Signature", "")
expected = hmac.new(WEBHOOK_SECRET, request.data, hashlib.sha256).hexdigest()
if not hmac.compare_digest(sig, expected):
return jsonify({"error": "invalid signature"}), 401
payload = request.json
message_id = payload["message_id"]
thread_id = payload["thread_id"]
thread = client.get_thread(thread_id=thread_id)
draft = draft_reply(thread=thread, incoming=payload)
client.reply_email(
message_id=message_id,
body=draft,
oversight_mode="gated_send"
)
return jsonify({"status": "queued"}), 200Receive an inbound email event, verify the signature, fetch the thread, and queue a draft for review.
import anthropic
client_ai = anthropic.Anthropic()
def draft_reply(thread: dict, incoming: dict) -> str:
history = []
for msg in thread["messages"]:
role = "assistant" if msg["direction"] == "outbound" else "user"
history.append({"role": role, "content": msg["body_text"]})
system_prompt = (
"You are a support agent for Acme SaaS. "
"Draft a helpful, accurate reply to the customer&"cm">#039;s email. "
"Be concise. Do not make promises you cannot verify. "
"If you are unsure about account-specific details, write "
"[REVIEWER: please fill in X] so the human reviewer knows where to complete the draft."
)
response = client_ai.messages.create(
model="claude-sonnet-4-6",
max_tokens=512,
system=system_prompt,
messages=history + [
{"role": "user", "content": incoming["body_text"]}
]
)
return response.content[0].textBuild a prompt from the full thread and use an LLM to generate a reply grounded in the actual conversation.
from multimail import MultimailClient
client = MultimailClient(api_key="$MULTIMAIL_API_KEY")
def process_review_queue(mailbox_id: str):
pending = client.list_pending(mailbox_id=mailbox_id)
for item in pending["messages"]:
print(f"From: {item[&"cm">#039;from']}")
print(f"Subject: {item[&"cm">#039;subject']}")
print(f"Draft reply:\n{item[&"cm">#039;draft_body']}\n")
action = input("[a]pprove / [e]dit / [r]eject: ").strip().lower()
if action == "a":
client.decide_email(
message_id=item["message_id"],
decision="approve"
)
elif action == "e":
revised = input("Enter revised reply: ")
client.decide_email(
message_id=item["message_id"],
decision="approve",
revised_body=revised
)
elif action == "r":
note = input("Rejection note for agent: ")
client.decide_email(
message_id=item["message_id"],
decision="reject",
rejection_note=note
)
process_review_queue(mailbox_id="[email protected]")Build a simple approval loop using list_pending and decide_email. Runs in a dashboard backend or a CLI tool for your support team.
from flask import Flask, request, jsonify
from multimail import MultimailClient
app = Flask(__name__)
client = MultimailClient(api_key="$MULTIMAIL_API_KEY")
@app.route("/webhooks/delivery", methods=["POST"])
def handle_delivery():
payload = request.json
if payload.get("event") == "message.delivered":
client.tag_email(
message_id=payload["original_message_id"],
tags=["auto-replied", "resolved"]
)
return jsonify({"ok": True}), 200Use the delivery webhook to mark the original message resolved via tag_email, keeping your inbox organized without manual work.
# Check for new inbound emails
check_inbox(mailbox_id="[email protected]", filter="unread")
# Load full thread context
get_thread(thread_id="thread_01abc123")
# Read the specific inbound message
read_email(message_id="msg_01xyz789")
# Queue a draft reply for human review
reply_email(
message_id="msg_01xyz789",
body="Hi Alex, you can cancel your subscription from Settings > Billing > Cancel Plan. The cancellation takes effect at the end of your current billing period. Let me know if you need help finding it.",
oversight_mode="gated_send"
)
# Check what's pending approval
list_pending(mailbox_id="[email protected]")
# Approve a draft
decide_email(message_id="msg_01xyz789", decision="approve")
# Tag original thread resolved
tag_email(message_id="msg_01xyz789", tags=["resolved"])If your agent runs inside an MCP-compatible client, use these tool calls directly — no webhook handler required.
The agent reads the full conversation thread via get_thread before drafting. Replies reference what the customer actually said, not a generic acknowledgment that ignores the question entirely.
gated_send mode holds every draft in the pending queue. Your team reviews the agent's work and approves, edits, or rejects before anything reaches the customer. You get drafting speed without losing control over what goes out.
Start with gated_send across all email categories. As you build confidence in specific types — shipping status, password reset instructions, plan upgrade confirmations — switch those categories to monitored or autonomous. The oversight_mode is set per-send, so you can mix modes without infrastructure changes.
Every draft, every approval decision, and every delivery event is logged. You can query the full history of any thread, see exactly what the agent drafted, and review what a human changed before approving. This matters for support quality audits.
MultiMail handles inbound routing, thread stitching, and delivery. Your agent can use any model for the drafting step — Claude, GPT-4o, Gemini, or a fine-tuned model trained on your historical support data. The API does not care which LLM you use.
Email infrastructure built for AI agents. Verifiable identity, graduated oversight, and a 50-tool MCP server. Formally verified in Lean 4.