MCP, SDK or REST for Agent Email: Which to Use When
Should your agent reach its inbox through MCP, an SDK, the REST API or the CLI? Pick by who makes the call, then see one create and send task done all four ways.
- engineering
- mcp
- sdk
- api
- cli
- ai-agents

TL;DR
An agent can reach the same email inbox through four doors: an MCP server, a Python or Node SDK, a plain HTTP API, or a command-line tool. They do the same work, so the choice comes down to who is making the call and where it runs. By the end you will know which door fits your agent, what credential each one expects, and what the same task looks like through all four.
Once you've decided your agent needs its own inbox, a second question shows up fast. How should the agent talk to it? You can hand an assistant a set of tools and let it decide when to send. You can write the send yourself in your agent's code. You can call the API from a script, or from a shell.
Each choice looks reasonable on day one. The cost shows up later. You need a retry that doesn't double-send, a step the assistant can't reach, or a second service in a language with no library.
Most writing on this compares one vendor against another. Here the vendor stays fixed and the doors get compared instead. First comes the short answer, then the four options side by side, then when each one wins. After that, one task done all four ways, and the things the inbox does not do for you.
Should an agent use MCP or an SDK for email?
Use MCP when a model decides which email action to take, and an SDK when your code decides. MCP, the Model Context Protocol, is an open standard that lets an AI client call outside tools. An assistant like Claude or ChatGPT connects to a server and gets a menu of email actions it can pick from mid conversation.
An SDK is a library you import. Your agent loop calls send or reply at the exact point your logic says so, with retries and error handling you wrote. Nothing is left to the model's judgment about whether to send.
Everything else goes through the HTTP API or the CLI. That covers a Rust service, a cron job, a CI step, or an agent that already drives a terminal. All four reach the same inbox, so an address created through one is visible through the others.
MCP, SDK, REST and CLI side by side
The four surfaces differ in who calls them and how they authenticate. The inbox behind them is the same.
| Surface | Who makes the call | Credential | Reaches pods, domains and webhooks | Best for |
|---|---|---|---|---|
| Hosted MCP server | The model, choosing a tool | OAuth sign-in, or an API key in the x-api-key header | No | Assistants a person talks to, and agents built on an MCP-native framework |
| Python or Node SDK | Your code | API key passed to the client | Yes | Agent loops you wrote, where sends must be exact and retried safely |
| HTTP API | Any program that speaks HTTP | API key as a Bearer token | Yes | Services in languages the SDKs don't cover, and quick checks with curl |
| CLI | A shell, a script, or an agent driving a terminal | AGENTMAIL_API_KEY in the environment, or the OS keychain | Follows the API's resources | Cron jobs, CI steps and coding agents that already run commands |
The fourth column is the one that surprises people. The MCP catalog covers inboxes, threads, messages, drafts, attachments, calendars and allow and block lists. It has no tools for pods, custom domains or webhooks, so setup work for those stays in code.
When the MCP server is the right call
We run a hosted MCP server, and it fits when a person is in the conversation and the model should pick the action. Someone asks Claude to check the support inbox and draft replies to anything urgent. The model reads, decides and acts, and the person sees each step.
Auth follows the client. Claude Desktop, Claude.ai and ChatGPT sign in with OAuth through the AgentMail console, so no key is pasted anywhere. Cursor, Windsurf and other clients without the MCP sign-in flow send an API key in the x-api-key header. Keys in a query string end up in logs and history, so the header is the only safe place for one.
It also fits agents you build on a framework that already speaks MCP. The Claude Agent SDK connects to the hosted server with a few lines of config and no extra package. Tools show up as mcp__agentmail__send_message and so on. For agents like that, use the API key header, because OAuth is meant for end-user clients.
Push is the part that depends on the client. The server publishes a message.received event through MCP Events, which lets ChatGPT start a task within seconds of new mail instead of polling. The event carries metadata only. The assistant then reads the message with get_message.
When an SDK is the right call
The SDK fits when your code owns the decision to send. Suppose your agent books meetings. The model drafts the text, but your code decides that a confirmation goes out exactly once, to exactly one address, after the calendar write succeeds. That logic belongs in code you can test.
Retries are where this pays off. Creates take a client_id, so running the same create twice returns the first inbox instead of making a second. Sends take an idempotency key. A retry with the same key returns the original message and sends nothing new. The same key on a different request returns a 409, so a reused key fails loudly.
The SDK also reaches the setup work MCP can't. That includes pods, which isolate one customer's inboxes and data from another's, custom domains and webhook subscriptions. Both SDKs are open source under the MIT license.
If your agent runs on the Vercel AI SDK or the OpenAI Agents SDK, there's a middle path. The agentmail-toolkit package wraps the API as tools for those frameworks, so the model picks actions while you keep the client in your own process.
When plain REST or the CLI is the right call
REST fits any language the documented SDKs don't cover. Those are Python and Node. A Rust or Ruby service calls the HTTP API directly with a Bearer token, and gets the same client_id and idempotency key behavior.
The CLI fits work that already lives in a shell. It ships as a single native binary with no runtime to install, so it drops into a CI image or a cron container. Commands mirror the API, as in agentmail inboxes messages send. A --schema flag prints a machine-readable description of every command, which lets a coding agent look up flags instead of guessing them.
Neither is a downgrade. They hit the same endpoints the SDKs wrap. The difference is who writes the request and how much of it you type yourself.
The same task four ways: create an inbox and send
Each version below creates an inbox and sends one message. The Python and Node versions are the same SDK surface in two languages.
Through MCP, the task is a prompt. This Python agent on the Claude Agent SDK hands the model the hosted server and lets it do both steps.
Python
# Claude Agent SDK agent using the hosted AgentMail MCP serverimport asyncioimport osfrom claude_agent_sdk import ClaudeAgentOptions, ResultMessage, queryoptions = ClaudeAgentOptions( mcp_servers={ "agentmail": { "type": "http", "url": "https://mcp.agentmail.to/mcp", "headers": {"x-api-key": os.environ["AGENTMAIL_API_KEY"]}, } }, allowed_tools=["mcp__agentmail__create_inbox", "mcp__agentmail__send_message"],)async def main(): prompt = "Create an inbox for yourself, then email ops@yourcompany.com that you are online." async for message in query(prompt=prompt, options=options): if isinstance(message, ResultMessage) and message.subtype == "success": print(message.result)asyncio.run(main())# prints the model's summary of the two tool callsThrough the Python SDK, your code makes both calls. The client_id and the idempotency key make a rerun safe.
Python
# create an inbox and send once (agentmail 2.0.18)import osfrom agentmail import AgentMailfrom agentmail.inboxes import CreateInboxRequestclient = AgentMail(api_key=os.environ["AGENTMAIL_API_KEY"])inbox = client.inboxes.create(request=CreateInboxRequest(client_id="ops-agent-primary"))sent = client.inboxes.messages.send( inbox.inbox_id, to="ops@yourcompany.com", subject="Agent online", text="The ops agent is online.", idempotency_key="ops-agent-online-1",)print(inbox.inbox_id, sent.message_id)# prints the new inbox address and the sent message idThe Node SDK is the same two calls. Fields are camelCase.
TypeScript
// create an inbox and send (agentmail 0.5.46)import { AgentMailClient } from "agentmail";const client = new AgentMailClient({ apiKey: process.env.AGENTMAIL_API_KEY ?? "" });const inbox = await client.inboxes.create({ clientId: "ops-agent-primary" });const sent = await client.inboxes.messages.send(inbox.inboxId, { to: "ops@yourcompany.com", subject: "Agent online", text: "The ops agent is online.",});console.log(inbox.inboxId, sent.messageId);// prints the new inbox address and the sent message idOver HTTP, you write both requests yourself. The key rides as a Bearer token.
Bash
# create an inbox, then send from it (replace <inbox_id> with the id returned)curl -X POST https://api.agentmail.to/inboxes \ -H "Authorization: Bearer $AGENTMAIL_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "client_id": "ops-agent-primary" }'curl -X POST https://api.agentmail.to/inboxes/<inbox_id>/messages/send \ -H "Authorization: Bearer $AGENTMAIL_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "to": "ops@yourcompany.com", "subject": "Agent online", "text": "The ops agent is online." }'# the first call returns the inbox object, the second returns message_id and thread_idFrom a shell, the CLI reads the key from the environment. There is no program to write.
Bash
# agentmail-cli 1.9.0, with AGENTMAIL_API_KEY exportedagentmail inboxes createagentmail inboxes messages send \ --inbox-id <inbox_id> \ --to ops@yourcompany.com \ --subject "Agent online" \ --text "The ops agent is online."# the first command prints the new inbox, including its inbox_idThe MCP version is the shortest, and the only one where you can't say in advance exactly which calls run. That is the trade in one line.
Can one agent use more than one surface?
Yes. One workable split puts setup in code and conversation in tools. A provisioning script uses the SDK to create the pod, the domain and the webhook. The agent then talks to its inbox through MCP or a framework toolkit.
Inbound often goes a third way. A webhook to your server suits agents and backends you run yourself. MCP Events suits an assistant like ChatGPT. Both report the same message.received event, so pick by who should wake up.
Keep one rule across all of them: one key per agent or service, scoped as tightly as the work allows. If a key leaks from a shell history, you want to revoke one script's access, not the whole agent's.
For more depth on each door, read how to set up MCP with an email API, email from the command line for agents, and idempotent inbound email for agents. If you're still choosing a provider rather than a surface, this comparison of email MCP servers covers who ships one. The difference between a send API and an inbox API is in email API vs inbox API.
What AgentMail does not do
Our documented SDKs cover Python and Node, so a service in another language calls the HTTP API directly. The MCP server has no tools for pods, custom domains or webhooks, so an assistant can't do that setup for you. Push to an assistant works only in clients that implement MCP Events; elsewhere the client checks the inbox on a timer. And no surface decides for you when the model should be allowed to send. That policy stays in your code.




