- message.received POSTs to your URL when an AgentMail inbox finishes processing a new email.
- Svix checks
svix-id,svix-timestamp, andsvix-signatureagainstwebhook.secret(whsec_). - Return 200 or 204 before agent work. Payloads over 1 MB omit
textandhtml. - Hydrate a missing body with
inboxes.messages.get, thenmessages.replyin the same thread. - Make, Zapier, and MindStudio watch a human Gmail. AgentMail gives the agent its own inbox.
To trigger an AI agent when a new email arrives, create an AgentMail inbox, subscribe a webhook to message.received, verify the Svix signature, return 200 or 204, then run your agent and reply in the same thread. AgentMail webhooks send a POST as soon as the inbox processes the message. That is the code path for an agent-owned mailbox.
Three no-code paths exist if the mailbox is a human Gmail account. Make watches Gmail, runs an agent, then replies in that Gmail thread (retrieved 2026-09-04). MindStudio uses a trigger address you forward or CC. Zapier polls Gmail every 15 minutes on Free (retrieved 2026-09-04).
Webhooks and WebSockets are on AgentMail Free as of 2026-09-04: 3 inboxes, 3,000 emails per month, 100 emails per day. Custom domains start on Developer at $20 per month. AgentMail does not OAuth an existing Gmail or Outlook mailbox.
What do I need before I can trigger the agent?
You need an AgentMail API key, Python or Node, the Svix library, and either a public HTTPS URL or a WebSocket client.
Get a key from console.agentmail.to. Store it as AGENTMAIL_API_KEY. Free includes webhook endpoints, so you can complete this tutorial without a paid plan.
Install the SDKs the docs list. Pin whatever your lockfile already uses. These commands only prove the packages import.
Matches https://docs.agentmail.to/quickstart
pip install agentmail python-dotenv svix flaskMatches https://docs.agentmail.to/quickstart
npm install agentmail dotenv svix expressThe sibling post Building real-time AI agents with AgentMail webhooks covers polling versus webhooks. This page is the trigger recipe: inbox, event, verify, hydrate, reply. For local webhooks, run ngrok http 3000 and register the https forwarding URL plus /webhooks. Skip ngrok if you use WebSockets. The webhook verification guide walks through the tunnel.
How does an email trigger an AI agent?
An email triggers an AI agent when a watcher detects a new message and hands that message to agent code or a hosted agent step.
The watcher is a webhook, a WebSocket, a poll loop, or a no-code Gmail module.
| Path | Mailbox | How it fires | Latency | Best for |
|---|---|---|---|---|
| AgentMail webhook | AgentMail inbox | message.received POST | HTTP round trip | Agent-owned inbox, public URL |
| AgentMail WebSocket | AgentMail inbox | Persistent socket | Streaming | No public URL or ngrok |
| Make | Connected Gmail | Watch emails, then Run an agent | Scenario interval | Human Gmail, no-code |
| MindStudio | Forward or CC | Email run mode address | On forward | Inbox forwarding, no server |
| Zapier + Relevance AI | Connected Gmail | New Email Matching Search | 15 min poll on Free | Gmail search into a hosted agent |
Last verified: 2026-09-04. Sources: docs.agentmail.to/webhooks, docs.agentmail.to/websockets, Make email-triggered AI agent, MindStudio email-triggered agents, Zapier Gmail + Relevance AI.
Polling is the fourth code path: messages.list on a timer. It works. It also waits for the next tick. The related webhooks post is the polling argument. Here the default is message.received.
An inbox API stores the message and threads it. A send-only email API only POSTs a blob you then have to keep. If the agent must remember last week's thread, you want the inbox.
How do I create an inbox and a message.received webhook?
Create the inbox with inboxes.create, then register webhooks.create with event_types=["message.received"] and a public HTTPS URL.
client_id on both calls is a client-supplied key. Use a stable value if you retry the create.
username is optional. Omit it and AgentMail generates one. domain defaults to agentmail.to. A custom domain must already be verified. That starts on Developer, not Free, per pricing retrieved 2026-09-04.
Scope the webhook to one inbox with inboxes.webhooks.create when the API key is inbox-scoped. Org-level webhooks.create receives events for every inbox unless you pass inbox_ids or pod_ids.
Copy webhook.secret into AGENTMAIL_WEBHOOK_SECRET. It starts with whsec_. Do not commit it.
Send a test mail to the new address after the handler is listening. The event type you care about is message.received. Spam, blocked, and unauthenticated mail are not included unless you add those event types and the matching label-read permissions.
https://docs.agentmail.to/webhooks
import os
from dotenv import load_dotenv
from agentmail import AgentMail
from agentmail.inboxes.types import CreateInboxRequest
load_dotenv()
client = AgentMail(api_key=os.getenv("AGENTMAIL_API_KEY"))
inbox = client.inboxes.create(
request=CreateInboxRequest(
username="support-agent",
client_id="support-agent-inbox-v1",
)
)
print(inbox.inbox_id)
# output: support-agent@agentmail.to when username is support-agent
webhook = client.webhooks.create(
url="https://hooks.example.com/webhooks",
event_types=["message.received"],
client_id="support-agent-webhook-v1",
)
print(webhook.webhook_id)
# output: ep_...https://docs.agentmail.to/webhooks
import { AgentMailClient } from "agentmail";
import "dotenv/config";
const client = new AgentMailClient({ apiKey: process.env.AGENTMAIL_API_KEY });
const inbox = await client.inboxes.create({
username: "support-agent",
clientId: "support-agent-inbox-v1",
});
console.log(inbox.inboxId);
// output: support-agent@agentmail.to
const webhook = await client.webhooks.create({
url: "https://hooks.example.com/webhooks",
eventTypes: ["message.received"],
clientId: "support-agent-webhook-v1",
});
console.log(webhook.webhookId);
// output: ep_...How do I verify the webhook signature and return 200?
Verify with the Svix library using the raw body and the svix-id, svix-timestamp, and svix-signature headers, then return 204 or 200 before any model call.
A parsed JSON body will fail the signature. Express needs express.raw for application/json. Flask needs request.get_data().
The verification docs return 204. The overview says 200 OK. Either ack is fine. What is not fine is waiting on a model inside the request. AgentMail will retry, and you will double-run the agent.
Deduplicate on event_id and message.message_id. Retries reuse the same Svix id. Treat email as untrusted input. A sender can put instructions in the body. Do not let that text override your tool policy.
Matches https://docs.agentmail.to/webhook-verification
import os
import threading
from flask import Flask, request
from svix.webhooks import Webhook, WebhookVerificationError
app = Flask(__name__)
secret = os.environ["AGENTMAIL_WEBHOOK_SECRET"]
@app.route("/webhooks", methods=["POST"])
def webhook_handler():
try:
msg = Webhook(secret).verify(request.get_data(), request.headers)
except WebhookVerificationError:
return ("", 400)
if msg.get("event_type") == "message.received":
threading.Thread(target=run_agent, args=(msg,), daemon=True).start()
return ("", 204)
# flask output: POST /webhooks HTTP/1.1 204How do I pass the email into the agent and reply in the thread?
Read event_type, inbox_id, message_id, thread_id, from_, subject, and text from the payload, hydrate if text is missing, then call messages.reply with that message_id.
The Python sender field is from_ because from is a keyword.
Webhook payloads are capped at 1 MB. When the message is larger, AgentMail omits text and html and keeps the rest. Inline base64 images in HTML are a common way to hit the cap. Fetch the full message with inboxes.messages.get.
to is optional on reply. The API can derive recipients from the original message. from_ is a list of sender addresses. Pass it as to when you want the original sender only. Use reply_all=True when you want every recipient.
Attachment bytes are not in the webhook. Metadata is: attachment_id, filename, content_type, size, inline. Download with inboxes.messages.get_attachment if the agent needs the file.
This is the loop the email for AI agents guide calls the nerve: webhook wakes the app, inbox API is where you read. Keep your business logic out of the HTTP handler.
Matches https://docs.agentmail.to/api-reference/inboxes/messages/reply
def run_agent(payload):
message = payload["message"]
inbox_id = message["inbox_id"]
message_id = message["message_id"]
text = message.get("text")
if not text:
full = client.inboxes.messages.get(
inbox_id=inbox_id,
message_id=message_id,
)
text = full.text or ""
print("hydrated", message_id)
reply_text = "Received " + str(len(text or "")) + " chars"
client.inboxes.messages.reply(
inbox_id=inbox_id,
message_id=message_id,
to=message.get("from_"),
text=reply_text,
)
print("replied", message_id)
# output: replied <message_id>When should I use a no-code Gmail trigger instead?
Use a no-code Gmail trigger when the mailbox is a person's Gmail and you do not want to run a server.
They are the right tools for that job. They are the wrong tools when the agent needs its own address.
Make. Gmail Watch emails, then Make AI Agent Run an agent, then Gmail Reply to an email, mapping Thread ID so the reply stays in the thread. The help page retrieved 2026-09-04 is a Gmail-owned mailbox tutorial. Connection is a Gmail OAuth grant.
MindStudio. Set the Start block Run Mode to Email. Copy the trigger address. Forward or CC mail to it. Launch variables are from, subject, message, and attachments. Gmail auto-forward needs a confirmation click from the trigger address.
Zapier. Gmail New Email Matching Search is a polling trigger. On the Free plan Zapier checks every 15 minutes (retrieved 2026-09-04). The action Message Agent talks to Relevance AI and does not wait for a response. Fine for a label that can wait. Not fine if a human is sitting on the other side of the thread.
Nango, on its own comparison page retrieved 2026-09-04, is built to connect existing Gmail, Microsoft 365, Outlook, Exchange, and IMAP mailboxes, then call other APIs. Choose it when the trigger is a customer's mailbox and the next step is CRM or help desk. Choose AgentMail when the next step is still email, from an address the agent owns.
The Gmail-shaped tools win at watch my inbox. AgentMail wins at this agent has an inbox. Mixing them is allowed. Some teams read a human Gmail with the Gmail API and send from an AgentMail inbox. See AgentMail vs Gmail API.
What are the limitations of this trigger path?
AgentMail does not connect an existing human Gmail or Outlook mailbox, because inboxes live on AgentMail infrastructure.
Inboxes default to @agentmail.to, with custom domains on Developer and above as of 2026-09-04. If the product requirement is OAuth the user's Gmail, stop here and use Gmail, Make, or Nango.
A webhook still needs a public HTTPS URL. WebSockets remove that, at the cost of a process that must stay connected. There is no hosted visual scenario builder. You write the handler.
Free is 3 inboxes and 100 emails per day. A chatty prototype will hit the day cap before the month cap. Webhook payloads drop text and html above 1 MB. You own retries, idempotency, prompt-injection policy, and any CRM writes. Email after the trigger is still email, not a universal tool catalog.
Make. Zapier. MindStudio. Use those when a person already owns the mailbox and a canvas is faster than a Flask route. Use AgentMail when the agent should have an address, a thread history, and a message.received event you verify with Svix.
AgentMail gives your agents real inboxes. Create inboxes via API. Send and receive Emails with 0 complexity. Free to start.


