Email MCP Server: Give Any MCP Agent an Inbox
How to build an email MCP server: the tool surface for sending, replying, threading and receiving mail, with working TypeScript and API examples.
John Joubert
Founder, Robotomail

Table of contents
An email MCP server exposes mailbox operations as tools that compatible agents can discover and call. Robotomail now provides a hosted MCP server at https://robotomail.com/mcp, so you can connect an existing agent to read, send, and reply to email without building your own adapter.
This guide starts with Robotomail's hosted connection, then explains how an email MCP server works and when a custom adapter is useful. It also covers inbound email arriving while the agent is not running.
Connect to Robotomail's hosted MCP server
Create a Robotomail account and verify your email, then open Connections and choose your agent. Configure Streamable HTTP at https://robotomail.com/mcp, sign in, and approve the requested permissions through OAuth. You do not need to paste a Robotomail API key into the MCP client. Follow the MCP setup guide for the instructions for your client.
The hosted server provides these tools:
| Tool | What it does |
|---|---|
list_mailboxes |
Lists your mailbox IDs, addresses, and sender display names |
search_messages |
Finds messages by text, direction, or date and returns summaries |
read_message |
Reads a message's plain-text body |
send_email |
Sends a plain-text email from a selected mailbox |
reply_to_email |
Replies to the sender while preserving the conversation thread |
set_mailbox_display_name |
Sets the friendly From name for future sends and replies |
Your existing mailbox and plan limits apply. The hosted tools currently handle plain-text email without attachments or reply-all. Use the REST API or official SDKs for mailbox creation, HTML email, attachments, webhooks, and domain management. A connection does not schedule inbox checks or automatically wake your agent.
The ChatGPT directory listing is still coming soon. Custom MCP setup depends on the features and permissions available in your account or workspace; see the current connection instructions.
What an email MCP server actually is
MCP is a transport and discovery protocol. It lets a host application (Claude Desktop, an IDE agent, your own orchestrator) list tools from a server, call them with JSON arguments, and get structured results back. It does not know anything about email.
So an email MCP server is a thin adapter with three jobs:
- Declare tools with schemas the model can reason about.
- Translate tool calls into HTTP calls against a real mail backend.
- Return results that are small enough to not blow up the context window.
The mail backend is where the actual difficulty lives: MX records, DKIM signing, bounce handling, threading headers, spam filtering, attachment storage. An MCP server that speaks SMTP directly can send, badly, and cannot receive at all. You want a mailbox API behind it.
Designing a custom MCP tool surface
If you need a custom adapter, keep its tool surface narrow. Every extra tool consumes context and adds another choice for the model. The table below is an example design for a server you build yourself; these are not the tool names or capabilities of Robotomail's hosted server listed above.
| Tool | Arguments | Returns |
|---|---|---|
create_mailbox |
address |
mailbox id, full address |
send_email |
mailbox_id, to[], subject, body_text |
message id, thread id |
reply_to_email |
mailbox_id, in_reply_to, body_text |
message id, thread id |
list_new_messages |
mailbox_id, limit |
message ids, from, subject, snippet |
get_message |
message_id |
full body, headers, attachment list |
get_thread |
thread_id |
ordered message summaries |
download_attachment |
attachment_id |
file reference or base64 |
Two design notes that matter more than they look.
Separate reply_to_email from send_email. Models are bad at remembering to pass an in_reply_to value to a generic send tool. If replying is its own tool with a required inbound message id, the correct threading behavior becomes the default rather than something the model has to remember. The mail API sets In-Reply-To and References from that id, which is what keeps the conversation in one thread in Gmail and Outlook. More on why that matters in email threading.
Never return full message bodies from a list tool. Return ids and a 200 character snippet. Let the model call get_message on the one it cares about. A list tool that dumps ten HTML emails into context will eat your whole budget and produce worse answers.
Tool descriptions are prompt engineering
The description string is the only instruction the model gets. Write it like an operator, not a doc generator:
send_email: Send a new email from the agent's own mailbox. Use only to
start a NEW conversation. To answer an email the agent received, use
reply_to_email instead so the message stays in the same thread.
Do not use for bulk or unsolicited messages.
That last line does real work. Constraints in tool descriptions reduce the number of times an agent decides to email fifty people.
Optional: building a custom server on Robotomail
The hosted connection above requires no server code. If you need your own tool policies or additional API operations, you can build a custom adapter using the REST API or an official Robotomail SDK. The example below uses direct HTTP calls. Provision a mailbox first, then wire the tools.
1. Create the mailbox. From the CLI:
npx @robotomail/cli mailbox create shopping-agent
Or over HTTP:
curl -X POST https://api.robotomail.com/v1/mailboxes \
-H "Authorization: Bearer rm_your_api_key" \
-H "Content-Type: application/json" \
-d '{"address": "shopping-agent"}'
You get a real, deliverable address on our platform domain. Point the mailbox at your own domain later if you want the agent to look like part of your product.
2. Define the tools. Using the MCP TypeScript SDK to implement the custom server:
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
const server = new McpServer({ name: "email", version: "1.0.0" });
const API_KEY = process.env.ROBOTOMAIL_API_KEY!;
server.tool(
"send_email",
"Send a new email from the agent's mailbox. Use only to start a new " +
"conversation; use reply_to_email to answer an existing one.",
{
mailbox_id: z.string(),
to: z.array(z.string().email()).max(5),
subject: z.string(),
body_text: z.string(),
},
async ({ mailbox_id, to, subject, body_text }) => {
const res = await fetch(
`https://api.robotomail.com/v1/mailboxes/${mailbox_id}/messages`,
{
method: "POST",
headers: {
Authorization: `Bearer ${API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ to, subject, bodyText: body_text }),
}
);
if (!res.ok) {
return {
isError: true,
content: [{ type: "text", text: `Send failed: ${res.status} ${await res.text()}` }],
};
}
const msg = await res.json();
return { content: [{ type: "text", text: JSON.stringify(msg) }] };
}
);
3. Add the reply tool. Same call, plus the inbound message id:
server.tool(
"reply_to_email",
"Reply to an email the agent received. Keeps the message in the same thread.",
{
mailbox_id: z.string(),
in_reply_to: z.string().describe("message_id from the received email"),
body_text: z.string(),
},
async ({ mailbox_id, in_reply_to, body_text }) => {
const res = await fetch(
`https://api.robotomail.com/v1/mailboxes/${mailbox_id}/messages`,
{
method: "POST",
headers: {
Authorization: `Bearer ${API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ inReplyTo: in_reply_to, bodyText: body_text }),
}
);
return { content: [{ type: "text", text: await res.text() }] };
}
);
await server.connect(new StdioServerTransport());
4. Scope the credentials. One API key per agent, not one shared key across your whole fleet. When an agent misbehaves you revoke its key and nothing else breaks. Key handling is covered in authentication.
Receiving email: MCP pulls, mail pushes
This is the part most email MCP servers get wrong. MCP is request/response. The host calls a tool, your server answers. There is no reliable way for an MCP server to wake up an idle agent because a vendor replied twenty minutes later.
Email is push. Inbound mail arrives at a webhook whenever it arrives:
{
"event": "message.received",
"data": {
"message_id": "...", "mailbox_id": "...", "mailbox_address": "shopping-agent@robotomail.co",
"from": "vendor@example.com", "subject": "Re: Order #A-4521",
"body_text": "...", "thread_id": "...", "received_at": "2026-04-17T10:00:00.000Z"
}
}
Two architectures work, and they are not exclusive:
Store and expose. Your webhook handler writes inbound messages to a table with a handled flag. The list_new_messages tool reads unhandled rows for that mailbox, and the agent marks them handled after replying. This keeps everything inside MCP and works with any host, including ones you do not control. The cost is that mail is only processed when someone runs the agent.
Wake the agent. Your webhook handler starts an agent run and injects the inbound message as the initial input. The agent can use the hosted MCP tools to read plain-text mail and reply. A custom adapter or the REST API/SDKs can add thread retrieval and attachment processing. This pattern suits time-sensitive work such as support intake or order updates.
We usually do both. The webhook triggers a run, and the pull tool exists as a recovery path for messages that arrived while the worker was down.
A note on idempotency: webhook deliveries can repeat. Deduplicate on message_id before you start an agent run, or you will send the same vendor two nearly identical replies.
MCP server vs direct API vs a skill
| Approach | Best when |
|---|---|
| MCP server | You want one email integration shared across multiple hosts and agent frameworks, including third-party clients |
| Direct HTTP calls | You control the agent loop and want tight control over prompting, retries, and message trimming |
| Packaged skill | You are on Claude Code or a similar host and want mail working in minutes |
Choose the hosted MCP connection when you want an existing agent client to use Robotomail through discovered tools and OAuth approval. For an agent loop you control, the REST API or official SDKs give you direct access with an API key; Python has synchronous and asynchronous clients. The agent skill is another option for giving a compatible agent instructions for the API and CLI.
Operational details that bite
- Rate limits. Give the MCP server its own limiter. A model in a retry loop will happily call
send_emaila dozen times in five seconds. Return a clear error and let the model back off rather than passing every attempt through. See limits. - Loop protection. If your agent replies to an autoresponder that replies to your agent, you have built a mail loop. Drop messages with
Auto-Submittedheaders, and cap replies per thread per hour. - Attachments. Do not return base64 blobs through MCP. Return metadata and a reference, download server side, and only surface extracted text to the model.
- Error shape. Return
isError: truewith the real status code and body. Models recover well from "422: recipient address is invalid" and badly from "something went wrong". - Recipient caps. Cap
to[]at a small number in the schema. Schema-level limits are cheaper than trusting the description.
FAQ
Does an email MCP server let agents receive email?
Not by itself. MCP tools are pull based, so the agent only sees mail when it calls a tool. Combine the MCP server with an inbound webhook so mail is captured the moment it arrives, then either wake an agent run or let a list_new_messages tool serve it up on the next turn.
Can I use SMTP behind the MCP server instead of an API?
You can send that way, though you will handle DKIM, bounces, and reputation yourself, and you still cannot receive. Receiving over SMTP means running an MX endpoint and parsing MIME. An email API with inbound support removes both problems.
How many tools should an email MCP server expose?
Five to seven. Send, reply, list, read a message, get a thread, plus optional create-mailbox and download-attachment tools. Every additional tool consumes context in every request and increases the chance the model picks the wrong one. Robotomail's hosted server exposes six: list mailboxes, search messages, read a message, send email, reply, and set the sender display name. A custom server can add further REST API operations, but those are separate from the hosted tool set.
Do replies stay in the same conversation thread?
Yes, if your reply tool passes the inbound message_id. We set the In-Reply-To and References headers from it so Gmail, Outlook, and Apple Mail group the messages correctly. Omit it and every reply looks like a brand new email.
Ready to connect an agent? Start with the hosted MCP setup guide. If you are building an application, choose an official SDK or follow the REST quickstart. For a full walkthrough, see how to give your AI agent an email address.
Give your AI agent a real email address
Create a mailbox, connect your agent and test a conversation. Send, receive and retrieve the thread through one API.
Related posts

Email API Pricing for AI Agents: Send + Receive Compared
How email API pricing works for AI agents at 1, 10 and 50 mailboxes: who bills inbound, who caps mailboxes, and where the real cost hides.
Read post
Email API Without OAuth: The API-Key Alternative
Why OAuth breaks AI agent email: consent screens, refresh token expiry, app verification. How an API-key email API gets an agent sending in minutes.
Read post
Build an n8n Email Agent With a Real Inbox
Give your n8n AI agent its own email address: trigger workflows from inbound mail via webhook, send threaded replies, and run per-workflow mailboxes.
Read post