Giving AI agents an email address: design notes
An agent is not a person with a mail client. It cannot glance at an inbox or click around. These are the decisions we made so an agent can use email in as few steps as possible.
By Arin Prajapati, founder, 5 min read
On this page (7)
Email was designed for people with mail clients. An agent has neither eyes nor a client: it has tool calls, a context window and a habit of doing exactly what the last error told it to. These are the decisions we made in Mailbay's API and MCP server with that reader in mind.
Count the steps
The job that comes up most is a sign up: get an address, give it to a website, read the code that arrives, type it in. Every extra tool call is a chance for the model to lose the thread, and every token of raw email it reads is context it cannot use for the actual task. So the goal was a sign up in two calls, each returning only what the next step needs:
create_inbox {"name": "signup-bot"}
→ {"id": "ibx_…", "address": "signup-bot@k7m3qz.mailbay.email", "created_at": "…"}
wait_for_email {"inbox_id": "ibx_…", "after": "<created_at>"}
→ {"message": {…}, "codes": ["482913"], "links": ["https://…/verify?token=…"]}Wait, do not poll
Left alone, an agent checks for new mail in a loop: list, read, sleep, list again. That burns tool calls and tokens, and each loop is another decision it can get wrong. wait_for_email (and GET /v1/inboxes/{id}/wait in the API) holds the request open until a matching message arrives, up to 120 seconds, and is safe to call again on timeout.
Under the hood the wait does not poll either. When our SMTP server commits a message, a Postgres trigger sends a NOTIFY inside the same transaction, so it fires only after the commit. Each API process holds one listening connection and wakes the waiting requests for that inbox, which run one query from their cursor. A slow safety poll covers the rare case of a dropped notification.
Two details matter for correctness. A wait only matches mail received after after, which defaults to the moment the call starts, so it never returns last week's code. And because a fast website can send the email before the agent starts waiting, the tool description tells the model to pass the inbox's created_at as after.
Pull the code out
Reading a verification email is easy for a model and also a waste: hundreds of tokens of HTML to find six digits. Every message Mailbay returns to an agent carries codes and links next to the text.
- Codes are standalone 4 to 8 digit numbers in order of appearance. Digits inside words, dates, times, prices, phone numbers and longer numbers are skipped, and a year like 2026 is dropped when another candidate exists, because footers love copyright lines.
- Links are the https URLs in the text or HTML, with
&decoded and trailing punctuation trimmed. Plain http links are left out.
"Your code is 482913. It expires in 10 minutes." → ["482913"]
"Code: 1234\n\n© 2026 Acme" → ["1234"]
"G-583921 is your code" → ["583921"]
"Order A12345 on 2026-10-13 at 10:00, total $1999, ..." → []The extraction is deliberately simple and predictable. When it finds nothing, the full text is still there for the model to read.
Addresses are free
Creating an inbox is cheap, but an agent running a hundred sign ups should not have to create a hundred inboxes, or delete them afterwards. Every workspace has a private domain such as k7m3qz.mailbay.email, and any address under it that is not a named inbox lands in the Catch-all. The agent invents signup-7f3a@k7m3qz.mailbay.email, uses it, and passes it as to when it waits. The Catch-all does not count against the inbox limit.
The code is random and permanent, so addresses are unguessable and stay yours. Mail for a code that does not exist is refused during the SMTP session.
Errors that say what to do
A model that hits an error will usually try to fix it, if it knows how. So every Mailbay error carries a machine code, a sentence, and a fix:
{
"error": {
"code": "wait_timeout",
"message": "no matching message arrived within 60 seconds",
"fix": "Call wait again (it is safe to repeat), loosen the from or subject filter, or check the address the email was sent to"
}
}Over MCP, errors are tool results with isError: true rather than protocol failures, so the model sees the fix and carries on instead of the client giving up. The same shape comes back from the REST API, the CLI and the SMTP server's rejection text, because all four call the same function in our core library.
Tool descriptions are prompts
An MCP tool description is the only documentation the model reads, at the moment it decides what to do. Ours follow one pattern: what the tool does, what it returns, which errors it can raise, and which tool to call next.
Create a new email inbox that can receive (and send) mail right away.
Returns {id, address, display_name, created_at}.
Errors: inbox_limit_reached (plan limit; delete an inbox or upgrade), inbox_exists (pick another name),
invalid_local_part, domain_not_found (custom domain not added), insufficient_scope (key limited to one inbox).
Next: give `address` to the service you are signing up for, then call wait_for_email with `id`
(pass after = created_at so an email that arrives early is not missed).The “Next” line does more work than it looks. It turns fifteen separate tools into a path, and it is where we put advice that heads off the easy mistakes, like starting to wait after the email has already arrived.
Limits on purpose
- Scoped keys. An inbox key can read, wait and reply in one inbox. Everything else answers
insufficient_scope. Give each agent the smallest key that works. - A sending sandbox. Until a workspace verifies a domain, mail from its
mailbay.emailaddresses only reaches its own members and our test addresses, so a new account cannot be turned into a spam cannon by a confused agent. - Authentication results on every message. SPF, DKIM and DMARC results and a spam flag let you instruct an agent to ignore mail that fails them, which matters when the mail says “click this link”.
- A ceiling on waits. A wait holds a connection for at most 120 seconds; on timeout the error tells the model to call again.
Try the flow yourself with an MCP client or plain HTTP.

Arin Prajapati, founder of MailbayA software engineer in Mumbai who builds Mailbay's mail servers, API and site. More about Arin and why he built Mailbay.
Read next
- An email inbox for your AI agentAn agent that signs up for things needs an address that receives mail, and a way to get the code out of it without polling. Here is the whole flow in three steps.
- Give an MCP agent its own email addressMCP lets an agent use email the way it uses any other tool. Add one server to your client and the agent can create an inbox, send, reply and wait for a code.
- Mailbay vs AgentMailBoth give agents their own inboxes over an API and MCP. AgentMail is further along; Mailbay adds sending for apps, a support inbox and custom domains on the free plan.
Join the beta
Mailbay is in private beta and free while the beta runs: inboxes, sending, your own domains and MCP. We invite people in small batches and read every note on the form.