What an MCP email server is, and how to connect one

Model Context Protocol gives agents a standard way to pick up tools. An MCP email server is that standard applied to a mailbox: a fixed set of verbs, a scoped key, and no bespoke integration per client. The value is not novelty. It is that a mailbox stops being an integration project and becomes a URL plus a key.

6 min read

An agent client listing the email tools exposed by an MCP server
One endpoint, one key, and the client discovers the tool surface on its own.

The problem it replaces

Before MCP, every agent framework had its own idea of what a tool was. Adding email meant writing an adapter per framework and rewriting it whenever either side changed. Worse, each adapter had to decide the same hard questions on its own: how much of a thread to load, how to strip quoted history, what to do when a send is refused.

MCP replaced the plumbing with one contract. A server advertises tools with typed arguments, a client discovers them at connect time, and the model calls them. What it does not do is answer the hard questions, which is why the design of the tool surface matters more than the protocol.

Verbs at the level the agent thinks in

A good email server exposes operations at the level of the job, not the level of the protocol. read_message and get_thread rather than a mailbox sync. wait_for_reply rather than a polling loop the model has to invent. MCPmailer groups the surface into four families.

FamilyToolsWhat it is for
Emailsend_email, reply_all, forward_email, list_messages, read_message, get_thread, search_inbox, wait_for_reply, archive_messageEverything a conversation needs, at the thread level
Identityget_identity, update_identity, list_identities, set_mail_ruleWho the agent is, and which senders can reach it
Contactslookup_contact, search_contacts, create_contact, remember_about_contact, merge_contactsA shared address book with memory, so context survives across runs
Notes and vaultlist_notes, read_note, create_note, get_secret, get_totp_codeWorkspace knowledge, and credentials the agent may use but never holds in plaintext

Every tool with its arguments and return shape is in the tools reference, which is also served as markdown at /docs.md for agents that would rather not parse a page. Why a product should publish documents that way at all is in making your product readable by agents.

The MCPmailer tool list as shown by an MCP client
What the client sees after connecting. No per-framework adapter involved.

Connecting

The endpoint speaks Streamable HTTP. Authentication is either a bearer key scoped to one agent identity, or OAuth 2.1 for clients that cannot hold a static key.

JSON
{
  "mcpServers": {
    "mcpmailer": {
      "type": "http",
      "url": "https://connect.mcpmailer.com/mcp",
      "headers": { "Authorization": "Bearer mmk_live_..." }
    }
  }
}

That block covers Claude and most desktop clients; there is a step by step version in how to let Claude send and receive email. Agents running as code use the equivalent for their framework.

TypeScript
await this.addMcpServer('mcpmailer', 'https://connect.mcpmailer.com/mcp', {
  transport: {
    type: 'streamable-http',
    headers: { Authorization: 'Bearer mmk_live_...' }
  }
});

That one is the Cloudflare Agents SDK. The Vercel AI SDK, LangChain, the OpenAI Agents SDK, n8n, OpenClaw, and Hermes each have their own config shape and the same endpoint.

If part of your stack does not speak MCP at all, the same keys work against the REST API with the same scoping and the same rules.

Shell
curl https://mcpmailer.com/v1/messages \
  -H "Authorization: Bearer mmk_live_..." \
  -d '{"to": ["anna@customer.com"], "subject": "Hello", "body": "Written by an agent."}'

The full REST surface is documented in the REST chapter and described formally in openapi.json.

Scope is the whole security model

A key belongs to one agent identity. Every tool call resolves against that identity, so an agent cannot list another agent's messages, read notes it was not granted, or send as an address it does not own. Revoking a key removes exactly one agent's access and nothing else, which is the property that makes running five agents no more frightening than running one.

OAuth tokens identify a person rather than an agent. If that person owns several agents, the call has to say which one it is acting as with an X-MCPmailer-Agent header, otherwise it is refused as ambiguous. Guessing would be the wrong default when the answer decides who a message appears to come from. The flow, including dynamic client registration and the required resource audience, is in OAuth.

Diagram showing an API key scoped to a single agent identity within a workspace
One key, one identity. The blast radius of a leaked key is one agent.

Rate limits, refusals, and why they are readable

Limits exist so one enthusiastic loop cannot damage a domain's reputation.

  • 300 requests per minute per key, across REST and MCP.
  • 30 per minute per IP without a key, and 5 per minute per IP for OAuth client registration.
  • Five recipients per message, always.
  • One daily and one monthly send allowance, drawn down by replies and cold sends alike.

Refusals are written to be acted on rather than logged. A rejected cold send returns a reason such as daily_send_quota_exhausted with a reset time, which an agent can reason about: wait, ask a human, or do something else. A 429 carries retry-after in seconds, and the correct response is to wait it out rather than retry harder.

Reacting to inbound without polling

A tool surface is only half of an email integration. The other half is knowing when something arrived. wait_for_reply covers the case where the agent is already in a conversation. For everything else, register a webhook and get a signed POST for message.received, message.bounced, message.complained, and message.filtered, with HMAC-SHA256 signatures and five retries.

That combination is what lets an agent answer in seconds rather than on an interval, which is most of the perceived quality in use cases like support and inbound sales.

When to reach for MCP and when not to

Use MCP when a model is deciding what to do: reading a thread, writing an answer, choosing whether to escalate. Use REST when your own code already knows what to do, such as a webhook handler that files an incoming invoice or a nightly job that archives resolved threads. There are worked examples both ways in build an email agent on the Cloudflare Agents SDK and sending and receiving email from a Python agent. They share keys, rules, and the audit trail, so mixing them costs nothing and most real deployments do.

What the server deliberately does not do is bulk sending. Five recipients per message is a hard cap, and campaigns are a different product with different infrastructure. If you are considering outbound at volume, read keeping an autonomous agent from becoming a spam problem first.

Questions

What is an MCP email server?
A server that exposes email operations as Model Context Protocol tools, so any MCP-capable agent client can send, read, and thread mail without a custom integration.
Which clients can connect?
Anything that speaks MCP over Streamable HTTP, including Claude, ChatGPT, the Vercel AI SDK, LangChain, the OpenAI Agents SDK, n8n, OpenClaw, Hermes, and the Cloudflare Agents SDK. Non-MCP stacks use the REST API with the same key.
Do I need OAuth?
Only if your client cannot hold a static key. A scoped bearer key is simpler and binds the connection to a single agent identity. The flow, the scopes, and the agent header are in connecting a hosted client that cannot hold a key. OAuth tokens identify a person and need an X-MCPmailer-Agent header when that person owns several agents.
What happens if the agent hits a rate limit?
It receives a 429 with retry-after in seconds. Wait for that window rather than retrying immediately; retrying harder is how a transient limit becomes a reputation problem.
Can an agent send bulk email through it?
No. Five recipients per message is a hard cap, every send draws down a metered allowance, and cold sends trip duplicate-content and velocity checks. This is infrastructure for conversations, not campaigns.
How does the agent know mail arrived?
Either wait_for_reply inside a conversation, or a message.received webhook for anything else. Polling list_messages works but costs you latency and requests.

Give your agent an address it can answer from.

Create an inbox