Header Image CloudMailin's Hosted MCP Server: Email for AI Agents, Without the API Keys
mcp

CloudMailin's Hosted MCP Server: Email for AI Agents, Without the API Keys

Just over a year ago we published our first post about MCP, showing how you could let AI interact with your CloudMailin email address. Near the top of that post we added a caveat: "this is moving fast, this article is probably out of date by the time you read it". That turned out to be the most accurate sentence we published all year.

The server in that post was a local Node process. You cloned a repository, ran npm install and npm run build, pointed your MCP client at the compiled JavaScript, and then, and this is the part that never sat right with us, pasted your CloudMailin API key into a JSON config file as an environment variable. It worked. People used it, and what they did with it shaped everything that follows. But an API key sitting in plain text in a desktop config file is not how we wanted anyone to connect an AI agent to their email.

So we rebuilt it as a hosted service. CloudMailin's MCP server now lives at https://mcp.cloudmailin.com/mcp. There is nothing to install, nothing to build, and no key to copy anywhere. You give your MCP client one URL, it opens a CloudMailin sign-in page in your browser, and you approve exactly what it is allowed to do. The old npm package is now deprecated, and this post is the proper introduction the hosted server never had.

Six tools that close the email loop

The server exposes six tools, and the list is deliberately short for what it covers. Four of them carried straight over from last year's local server: listAddresses, listMessages, listSentMessages, and sendEmail, markdown support and all. Only two are genuinely new, listDomains and listOutboundAccounts, and they landed after the hosted server was already running. The count matters less than how they reached you, which is the whole point of hosting this instead of shipping you a package to run: more on that below.

Tool Scope What it does
listAddresses read Lists every inbound address in the account, with its ID and the HTTP endpoint it delivers to
listMessages read Delivery status of email received by an address, including the HTTP response your endpoint returned
listSentMessages read Outbound delivery status: delivered, deferred, or bounced, with the receiving server's SMTP response
listDomains read Every sending domain's SPF, DKIM, and Return-Path status individually, plus the exact DNS records still missing
listOutboundAccounts read Your sending accounts, for picking one when more than one exists
sendEmail send Sends email through one of your verified sending domains, tracked like any other outbound message

Between them, these cover the loop an agent actually needs: what addresses exist, what arrived and whether your application accepted it, whether a sending domain is actually ready to send from (and if not, exactly what's missing), whether what you sent was delivered, and the ability to send something new. sendEmail takes markdown and converts it into proper HTML and plain text parts, because asking a language model to hand-assemble multipart MIME is asking for trouble.

listDomains in particular turns domain setup into a guided conversation instead of a support ticket. It doesn't just report a domain as "unverified": it checks SPF, DKIM, and Return-Path individually, so an agent can tell you specifically which of the three is still missing for mail.acme.com, hand back the exact CNAME or TXT record for that one, and check again once you've added it. You can go from "why can't I send from this domain" to a fully configured one without leaving the conversation, let alone finding the right page in a dashboard.

Just as important is what isn't there. There are no tools for changing account settings, managing team members, or deleting anything. An agent connected through MCP can observe email flow, check on domain setup, and send messages, and that is all. If you need the full surface, the REST API hasn't gone anywhere; the MCP server is intentionally a narrower doorway. A small tool list is also easier for a model to use well: fewer tools means fewer ways to pick the wrong one.

listDomains and listOutboundAccounts are the two that didn't exist when this server first went up. We added them the very next day, and every client already connected got them immediately: no update, no reinstall, no re-approving the OAuth grant. That's a real, practical difference from the old local server, where a new tool meant git pull, npm install, and a rebuild before you could use it. Hosted means we can hand you new capability the moment it ships.

The security model is the point

The standard pattern for connecting an agent to a service has been: generate an API key, paste it into a JSON file, hope. Look at almost any MCP config in the wild and you'll find an env block holding a long-lived secret with full account access. Those files get committed to dotfiles repositories, synced between machines, pasted into chat windows when something breaks, and swept into backups forever. A key in that position usually has every permission your account offers and no expiry date.

There are ways to do this better. Storing MCP secrets in your OS keychain instead of plaintext is a real improvement, and worth doing for any server that requires one. But the best secret is the one you never have to store at all.

The hosted server replaces that with OAuth 2.1 and PKCE. In plain terms, here is what happens the first time your client connects:

  1. The client sends you to a CloudMailin page in your browser.
  2. You sign in as yourself, choose which of your accounts the grant applies to, and see exactly which scopes the client is requesting.
  3. You approve or deny. If you approve, the client receives tokens limited to those scopes and that account.

PKCE (proof key for code exchange) is the piece that makes this safe for desktop apps and command line tools. The client proves it is the same process that started the flow, so an intercepted authorization code is useless to anyone else, and there is no client secret to ship or store.

A few properties fall out of this design that we think matter more than any individual tool:

  • Read and send are separate scopes. You choose at consent time. An agent that only triages your inbound feed never gains the ability to send.
  • Owners and admins only. Other members of an account can't authorize an MCP client. Connecting an agent to a company's email is an owner-level decision, and the flow enforces that rather than leaving it to policy.
  • Nothing in the config is worth stealing. The entire client-side configuration is a URL. You could print it on a billboard; we effectively have, since it's on our website.
  • Revocable, immediately. Every grant appears on your account page along with the client and scopes it holds. Revoke it and its tokens stop working straight away, with no need to rotate a key that other systems depend on.

An agent with email access deserves the same care as a person with email access. That one sentence drove most of the decisions above.

Connecting a client

The MCP page has one-click installs and copyable snippets for each client; here are the short versions.

Claude Code takes one command:

claude mcp add --transport http cloudmailin https://mcp.cloudmailin.com/mcp

The first tool call opens the OAuth sign-in in your browser. If you ever need to re-authenticate, run /mcp inside Claude Code.

claude.ai and Claude Desktop treat MCP servers as connectors:

  1. Settings → Connectors
  2. Add custom connector
  3. Paste https://mcp.cloudmailin.com/mcp

Cursor reads .cursor/mcp.json (Cursor Settings → MCP → New MCP server gets you there), and opens the sign-in on first use:

{
  "mcpServers": {
    "cloudmailin": {
      "url": "https://mcp.cloudmailin.com/mcp"
    }
  }
}

VS Code can add it straight from the terminal:

code --add-mcp '{"name":"cloudmailin","type":"http","url":"https://mcp.cloudmailin.com/mcp"}'

Windsurf uses the same JSON shape as Cursor in its mcp_config.json (Settings → Cascade → MCP servers), with serverUrl as the key instead of url.

And if you're building your own agent, anything that speaks Streamable HTTP and supports OAuth works out of the box. There is no CloudMailin-specific SDK to integrate; the URL is the integration.

What this looks like in practice

The clearest way to see the value is a few real prompts.

"What landed at support@ this morning, and did we accept all of it?" The agent calls listAddresses to resolve support@ to an address ID, then listMessages for that address. Because each record includes the HTTP response your endpoint returned, the answer isn't just who wrote in and about what. It can also be "your webhook returned a 500 for two of them around 6am". We built the delivery view in the dashboard for humans; it turns out agents want exactly the same information, just conversationally.

"Did the password reset reach kate@acme.com?" This question used to mean grepping logs or clicking through a dashboard. The agent calls listSentMessages and answers from the delivery record: accepted, along with the receiving server's actual SMTP response, or the precise bounce reason if it failed.

"Send her the receipt, and CC accounts." With the send scope granted, the agent composes the message in markdown and calls sendEmail. It goes out through your verified domain with proper HTML and plain text parts, and it appears in your outbound history tracked like any other message, so the next "did it arrive?" question works on it too.

"Walk me through getting mail.acme.com ready to send from." The agent calls listDomains, sees SPF and Return-Path are verified but DKIM isn't, reads back the exact DKIM record to add, and can check again on request once you've added it, guiding you to a fully verified domain a step at a time.

None of these required an API integration, a polling loop, or copying message IDs between systems. That is the entire pitch of MCP, and email happens to be a domain with an unusual amount of tedium worth removing.

Email for agents, on infrastructure that predates them

There's a wave of new products right now offering inboxes built specifically for AI agents, and we understand the appeal of starting fresh. We'd gently argue the opposite position: the hard part of giving an agent email was never the agent-facing API. It's the email.

Email is a forty-year accumulation of edge cases. Real-world messages arrive with malformed MIME boundaries, nested multiparts, attachments of every size and type, and character encodings that predate Unicode (we once wrote up Outlook emitting windows-1258 for messages that had nothing to do with Vietnamese). Senders retry in odd patterns. Spam and abuse arrive constantly, and adapt when blocked. Parsing all of that into clean, structured data your application, or your agent, can trust is not a feature you bolt on afterwards. It is the product, and it takes years of production traffic to get right.

That is the platform this MCP server is a thin layer over. CloudMailin has been receiving email and delivering it to applications as structured data since 2010: sixteen years, billions of messages, 99.99% uptime, attachment handling into S3-compatible storage, spam scoring and sender authentication checks on the way in, and a SOC 2 Type 2 examination behind all of it.

To be clear about what this is and isn't: CloudMailin is not repositioning itself as an AI company. We build email infrastructure, and MCP is one more way to use it, alongside the webhook POSTs, the REST API, and SMTP. The MCP server gets the same treatment as any other production surface, which is exactly why we took the time to do OAuth properly instead of shipping the quick version.

Rough edges, and where this goes

An honest note to close on: MCP is still young. The specification keeps moving, and OAuth support varies between clients. Some handle the sign-in flow seamlessly, others are still catching up, and occasionally you'll need to re-authenticate for no obvious reason. If you hit something odd, updating your client is usually the fix; the ecosystem improves monthly.

The tool surface is also a starting point, not a final shape, something we've already proven once by adding two tools the day after launch with nobody needing to lift a finger. We keep it as small as it can be at any given time on purpose: a tight tool list is easier for a model to use well and easier for you to reason about when you're deciding what to grant. What we add next will be guided by what people actually ask their agents to do, which is exactly how usage of the first version shaped this one.

If you'd like to try it, the MCP page has install snippets for every client mentioned here, and a free CloudMailin account is enough to connect and experiment. If you build something interesting with it, or hit a rough edge we should know about, get in touch. The last version of this server was shaped by the people who used it, and we'd like this one to be too.