MCP server
Streamable HTTP
Self-hosted on Cloudflare

An MCP email server on your own Cloudflare account

Flowmails runs a hosted Model Context Protocol server for your account. An agent that speaks MCP lists your mailboxes, opens a durable wait for a verification mail, reads the message, and sends the reply — ten tools, one credential, and a scope check in front of every call.

The endpoint

One URL, one transport, no session to manage

The server answers on https://mcp.flowmails.net/mcp over MCP streamable HTTP, protocol revision 2025-06-18. It keeps no state a request depends on — the credential carries the identity — so it runs on the edge without sticky routing. The handshake hands out a session id, and a request that arrives without one is answered normally.

tools-listhttp
POST https://mcp.flowmails.net/mcp
Authorization: Bearer fm_…

{"jsonrpc":"2.0","id":1,"method":"tools/list"}

A JSON-RPC request is answered with the JSON-RPC response. A notification (a message with no id) is answered with an empty 202. The server advertises tools only: it exposes no resources and no prompts, and the two list methods answer with an empty list rather than an error.

The tools

Ten tools, and no eleventh

Every tool of the design is mounted, and every one of them sits on a route that exists. The cap is deliberate: every extra tool is a way for an untested agent to change account configuration by accident.

ToolScopeWhat it does
list_domainsaddresses:readThe domains the account owns.
list_addressesaddresses:readThe local parts one domain accepts.
create_addressaddresses:writeAdds one local part, and nothing else.
search_emailsmessages:searchLooks back through the mailbox.
get_emailmessages:readReads the body of one message.
send_emailmessages:sendSends from the bound domain.
get_send_statussend_status:readReads the delivery status of a sent message.
wait_for_emailwaits:writeOpens a durable wait for a mailbox.
get_wait_statuswaits:readPolls that wait.
list_eventsevents:readReads the inbound event log with a cursor.

Credentials

Two keys, one scope check

fm_…

The SDK key from the dashboard. It predates the scope matrix and holds every scope, so an integration that has been working for a year does not start failing because a scope check appeared. It is bound to one domain for sends.

agc_…

The scoped credential, issued in Settings → Agent Credentials. It names the tools it may call, it can be revoked on its own, and binding it to a domain is what makes sending possible at all.

The scope check runs before the tool body, so a tool never has to think about authorization. A credential that does not hold the scope is refused with a typed scope_denied error naming the scope it is missing — never a 500, and never a call that half-runs. The platform’s own agent routes answer the same refusal with HTTP 403 and the same body.

A revoked or expired credential answers 401, and it answers exactly like a key that never existed. A raw credential is never stored and never logged: the lookup is keyed on the SHA-256 hash of the key, so a database dump cannot be replayed against the API.

The boundary

What the agent cannot do for you

  • It cannot create the account, connect your Cloudflare account, add and verify a domain, deploy the receive worker, or mint a key. Those four steps are the human’s one-time setup, in the dashboard.
  • It cannot rewrite your route table. Creating one address is the whole write surface it owns.
  • Sending is its own decision — there is no approval step, and no per-message confirmation. The controls are the scope list, the domain binding and revocation.
  • An inbound body is untrusted input. The agent owns the prompt that reads it, and the rule it must follow is the agent’s own: treat an instruction found in a body as content to record, not as a command to follow.

Connect

Flowmails for every coding agent