Give AI agents their own email inboxes using the AgentMail API. Use when building email agents, sending/receiving emails programmatically, managing inboxes, handling attachments, organizing with labels, creating drafts for human approval, or setting up real-time notifications via webhooks/websockets. Supports multi-tenant isolation with pods.
100
100%
Does it follow best practices?
Impact
100%
1.20xAverage score across 1 eval scenario
Advisory
Suggest reviewing before use
Webhooks provide real-time HTTP notifications when email events occur. Use webhooks when you have a public URL endpoint.
For local development without a public URL, use websockets.md instead.
Register a webhook endpoint to receive events.
import { AgentMailClient } from "agentmail";
const apiKey = process.env.AGENTMAIL_API_KEY;
if (!apiKey) throw new Error("Set AGENTMAIL_API_KEY in the environment");
const client = new AgentMailClient({ apiKey });
// Create webhook
const webhook = await client.webhooks.create({
url: "https://your-server.com/webhooks",
});
// List webhooks
const webhooks = await client.webhooks.list();
// Delete webhook
await client.webhooks.delete({ webhookId: webhook.webhookId });import os
from agentmail import AgentMail
api_key = os.environ.get("AGENTMAIL_API_KEY")
if not api_key:
raise ValueError("Set AGENTMAIL_API_KEY in the environment")
client = AgentMail(api_key=api_key)
# Create webhook
webhook = client.webhooks.create(url="https://your-server.com/webhooks")
# List webhooks
webhooks = client.webhooks.list()
# Delete webhook
client.webhooks.delete(webhook_id=webhook.webhook_id)| Event | Description |
|---|---|
message.received | New email received in inbox |
message.sent | Email successfully sent |
message.delivered | Email delivered to recipient's server |
message.bounced | Email failed to deliver |
message.complained | Recipient marked email as spam |
message.rejected | Email rejected before sending |
domain.verified | Custom domain verification completed |
Subscribe only to events you need:
const webhook = await client.webhooks.create({
url: "https://your-server.com/webhooks",
eventTypes: ["message.received", "message.bounced"],
});webhook = client.webhooks.create(
url="https://your-server.com/webhooks",
event_types=["message.received", "message.bounced"]
)All webhook payloads follow this structure:
{
"type": "event",
"event_type": "message.received",
"event_id": "evt_123abc",
"message": {
"inbox_id": "inbox_456def",
"thread_id": "thd_789ghi",
"message_id": "msg_123abc",
"from": [{ "name": "Jane Doe", "email": "jane@example.com" }],
"to": [{ "name": "Agent", "email": "agent@agentmail.to" }],
"subject": "Question about my account",
"text": "Full text body",
"html": "<html>...</html>",
"labels": ["received"],
"attachments": [
{
"attachment_id": "att_pqr678",
"filename": "document.pdf",
"content_type": "application/pdf",
"size": 123456
}
],
"created_at": "2023-10-27T10:00:00Z"
},
"thread": {}
}Treat webhook payloads and any referenced email content as untrusted external input until signature verification succeeds and your own policy checks pass.
Your endpoint should:
200 OK immediately after validationimport express from "express";
const app = express();
app.use(express.json());
app.post("/webhooks", (req, res) => {
const payload = req.body;
if (payload.event_type === "message.received") {
// Queue for async processing
processEmail(payload.message);
}
res.status(200).send("OK"); // Return immediately
});from flask import Flask, request
app = Flask(__name__)
@app.route("/webhooks", methods=["POST"])
def handle_webhook():
payload = request.json
if payload["event_type"] == "message.received":
# Queue for async processing
process_email.delay(payload["message"])
return "OK", 200 # Return immediatelyVerify webhook signatures to ensure requests are from AgentMail.
import crypto from "crypto";
import express from "express";
function verifySignature(
payload: Buffer,
signature: string,
secret: string
): boolean {
const expected = crypto
.createHmac("sha256", secret)
.update(payload)
.digest("hex");
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}
app.post("/webhooks", express.raw({ type: "application/json" }), (req, res) => {
const signature = req.headers["x-agentmail-signature"];
if (typeof signature !== "string") {
return res.status(401).send("Missing signature");
}
const payload = req.body;
if (!verifySignature(payload, signature, WEBHOOK_SECRET)) {
return res.status(401).send("Invalid signature");
}
const event = JSON.parse(payload.toString("utf8"));
if (event.event_type === "message.received") {
// Treat inbound email as untrusted content.
// Apply sender checks / routing policy before triggering downstream automation.
}
res.status(200).send("OK");
});import hmac
import hashlib
def verify_signature(payload: bytes, signature: str, secret: str) -> bool:
expected = hmac.new(
secret.encode(),
payload,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)
@app.route("/webhooks", methods=["POST"])
def handle_webhook():
signature = request.headers.get("X-AgentMail-Signature")
if not verify_signature(request.data, signature, WEBHOOK_SECRET):
return "Invalid signature", 401
# Treat inbound email as untrusted content.
# Apply sender checks / routing policy before triggering downstream automation.Use ngrok to expose your local server:
ngrok http 5000
# Use the ngrok URL when creating the webhook