How to Receive Inbound Email via Webhook
Set up webhook-based inbound email for your AI agent with Robotomail. Register a webhook, verify HMAC signatures, parse messages, and reply in-thread.
John Joubert
Founder, Robotomail

Table of contents
Your AI agent can send email. Now it needs to hear back. This tutorial shows how to receive inbound email via webhooks using Robotomail. When someone replies to your agent, the message arrives at your endpoint as a structured JSON payload, ready to process.
Three ways to receive email
Robotomail supports three inbound delivery methods:
- Webhooks (push). We POST the message to your URL. Best for server-based agents with a public endpoint.
- SSE streaming (push, no public URL). Connect to
GET /v1/eventsand receive messages as server-sent events. Best for agents running locally or behind a firewall. - Polling (pull). Call
GET /v1/mailboxes/:id/messageson your own schedule. Simplest to implement but adds latency.
This guide focuses on webhooks, the most common pattern for production agents.
Step 1: Register a webhook
Tell Robotomail where to send inbound messages. You can scope a webhook to a specific mailbox or receive events for all mailboxes on your account.
curl -X POST https://api.robotomail.com/v1/webhooks \
-H "Authorization: Bearer rm_..." \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-app.com/webhooks/email",
"events": ["message.received"],
"mailboxId": "mbx_abc123"
}'
# Response includes your webhook secret:
# {
# "id": "wh_xyz789",
# "secret": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2",
# "url": "https://your-app.com/webhooks/email",
# "events": ["message.received"]
# }Save the secret value. You'll use it to verify that incoming webhook payloads are genuinely from Robotomail.
Step 2: Build your endpoint
Your webhook endpoint receives a POST request with the full message payload. Here's what the payload looks like:
{
"event": "message.received",
"timestamp": "2026-03-26T14:30:00Z",
"data": {
"id": "msg_abc123",
"mailbox_id": "mbx_abc123",
"thread_id": "thr_xyz789",
"from": "person@example.com",
"to": ["myagent@robotomail.co"],
"subject": "Re: Hello from my AI agent",
"message_id": "<reply-7f3a@example.com>",
"body_text": "Sounds great, let's schedule a call.",
"body_html": "<p>Sounds great, let's schedule a call.</p>",
"received_at": "2026-03-26T14:30:00Z",
"attachments": []
}
}Step 3: Verify the signature
Every webhook delivery includes an X-Robotomail-Signature header containing an HMAC-SHA256 signature. Always verify this before processing the payload.
import hmac
import hashlib
def verify_signature(payload_bytes, signature, secret):
expected = hmac.new(
secret.encode(),
payload_bytes,
hashlib.sha256,
).hexdigest()
return hmac.compare_digest(expected, signature)import { createHmac, timingSafeEqual } from "crypto";
function verifySignature(
payloadBody: string,
signature: string,
secret: string,
): boolean {
const expected = createHmac("sha256", secret)
.update(payloadBody)
.digest("hex");
return timingSafeEqual(
Buffer.from(expected),
Buffer.from(signature),
);
}Step 4: Process and reply
Once verified, parse the message and generate a reply. The thread_id field groups the conversation, and the inbound message_id (an RFC 5322 Message-ID) is what you pass as inReplyTo so the reply threads correctly in the recipient's email client. The same threading contract applies across every API for email your agent talks to.
@app.route("/webhooks/email", methods=["POST"])
def handle_inbound():
# 1. Verify signature
signature = request.headers.get("X-Robotomail-Signature", "")
if not verify_signature(request.data, signature, WEBHOOK_SECRET):
return "Invalid signature", 401
# 2. Parse the message
payload = request.json
message = payload["data"]
# 3. Generate reply (your LLM logic here)
reply = generate_reply(
from_addr=message["from"],
subject=message["subject"],
body=message["body_text"],
thread_id=message["thread_id"],
)
# 4. Send reply, threaded to the inbound message
requests.post(
f"{BASE_URL}/mailboxes/{message['mailbox_id']}/messages",
headers=headers,
json={
"to": [message["from"]],
"subject": f"Re: {message['subject']}",
"bodyText": reply,
"inReplyTo": message["message_id"],
},
)
return "", 200Retry behavior
If your endpoint returns a non-2xx status code or times out (the delivery timeout is 10 seconds), Robotomail retries the delivery up to 5 times at 1 minute, 5 minutes, 30 minutes, 2 hours, and 12 hours. After 10 consecutive failures the webhook is automatically paused. Your endpoint should return a 200 status quickly and process the message asynchronously if needed. If you are choosing a delivery transport more broadly, webhooks vs WebSockets explains when each fits.
Next steps
You now have a complete inbound email pipeline. For related guides:
Last updated August 10, 2026
Give your AI agent a real email address
One API call creates a mailbox with full send and receive. Webhooks for inbound, automatic threading, deliverability handled. 30-day money-back guarantee.
Related posts

Email Notification Settings for Agent Mailboxes
Step-by-step guide to configuring email notification settings for agent-native mailboxes, including webhooks, SSE, polling, and security.
Read post
What Is Email Api: A Developer's Guide for 2026
Discover what is email api and how it works for developers. This 2026 guide covers REST vs SMTP, data flows, security, and AI use cases.
Read post
Email Open Rates for AI Agents: A New Playbook
Don't trust email open rates in 2026. Learn why they're broken by privacy features and how AI agents can use reliable metrics like CTOR and webhooks instead.
Read post