Agent API & MCP Server
API endpoints and MCP integrations to easily connect MailFlat to AI agents (GPT, Claude, Cursor).
Endpoints
Every route below authenticates with an account key: X-API-Key: mf_live_… (create one in Agents → API keys).
| Method & path | Does | Returns |
|---|---|---|
| POST /api/v1/inboxes | Open a new agent inbox (always plain text) | { address, retention_hours, … } |
| GET /api/v1/inboxes | List every agent inbox this key can see | { inboxes: [ … ] } |
| GET /api/v1/inboxes/{addr}/messages | All messages in the inbox | { emails: [ … ] } |
| GET /api/v1/inboxes/{addr}/latest | Newest message only, the OTP polling call | { email: { … } } |
| POST /api/v1/inboxes/{addr}/send | Send a DKIM-signed mail from this inbox | { ok: true } |
| DELETE /api/v1/inboxes/{addr} | Delete the inbox and its messages | { ok: true } |
| DELETE /api/v1/inboxes/{addr}/messages/{id} | Delete one message | { ok: true } |
| GET /api/v1/inboxes/{addr}/messages/{id}/attachments/{aid} | Download one attachment | the file itself — or the envelope, on an encrypted inbox |
Message fields worth knowingattachments · headers · links · spam
| Field | Type | Meaning |
|---|---|---|
| attachments | array | Metadata only — id, filename, content_type, size_bytes, is_encrypted, truncated. The bytes come from the attachment endpoint above; putting them in every listing would make a single 5 MB file weigh down every call. |
| headers | object | Every header as it arrived. Repeating ones (notably Received) come back as a list, because the chain is the delivery path. null on an encrypted inbox: headers carry the Subject, so they travel inside the envelope instead. |
| links | array | Every http(s) URL in the message, in order, de-duplicated. Magic links without a regex. |
| spam | object | null | SpamAssassin result: score, the required threshold it was judged against, is_spam, and every rules[] entry that fired with its own weight. null means the message was never scanned — not that it is clean; 0.0 is a real result. Stays readable on encrypted inboxes: rule names carry no message content. Nothing is filtered or blocked on our side. |
Attachment limits10 files · 5 MB each · 10 MB total
| Limit | Value | What happens past it |
|---|---|---|
| Files per message | 10 | Later files are not recorded. |
| One file | 5 MB | Kept as metadata with truncated: true — the entry stays so an oversized file is distinguishable from one that never arrived. |
| All files in a message | 10 MB | Same: the entry stays, the bytes do not. |
Downloading a truncated attachment
Returns 400 with Attachment was too large to store rather than an empty file, so a test fails loudly instead of asserting on zero bytes.
POST /api/v1/inboxes body fieldsevery field is optional
| Field | Type | Meaning |
|---|---|---|
| prefix | string | Part before the @. Omit it and one is generated for you. |
| subdomain | string | Namespace after the @ on mailflat.net. Random when omitted; paid plans only. |
| domain | string | One of your verified BYOD domains, e.g. acme.com → prefix@acme.com. |
| label | string | A human name for the inbox, shown in the dashboard. |
| retention_hours | int | How long messages are kept. Capped to your plan max rather than rejected. |
POST /api/v1/inboxes/{addr}/send body fieldsto is required
| Field | Type | Meaning |
|---|---|---|
| to | email, required | Recipient address. |
| subject | string | Defaults to empty. |
| body | string | Plain-text body. |
| html | string | Optional HTML body. Use it for templated mail; plain body alone is fine. |
Errors you can actually hit400 · 401 · 403 · 429
| Status | When | Detail message |
|---|---|---|
| 400 | A plan or business rule blocked it | Free plan allows at most 3 agent inboxes… |
| 401 | Missing key, or an inbox key used on a v1 path | A valid API key is required |
| 403 | The key does not own that inbox | Inbox not found for this key |
| 429 | More than 500 requests/hour from one IP | Too many requests |
Encrypted inboxes answer differently
If you point these routes at an end-to-end encrypted inbox, the response carries encrypted: true and a note explaining that body and otp_code are unavailable, because the server genuinely cannot read them. Inboxes created through this API are never encrypted, so this only happens when you target an inbox from the human pool.
The full loop
# 1) create a short-lived inbox (2h retention)
curl -X POST https://mailflat.net/api/v1/inboxes \
-H "X-API-Key: mf_live_…" -H "Content-Type: application/json" \
-d '{"prefix":"agent-7f3","retention_hours":2}'
# → { "address": "agent-7f3@a7f2c.mailflat.net" }
# 2) poll the latest mail (OTP auto-extracted)
curl https://mailflat.net/api/v1/inboxes/agent-7f3@a7f2c.mailflat.net/latest \
-H "X-API-Key: mf_live_…"
# → { "email": { "subject": "Your code", "otp_code": "482913" } }
# 3) send a reply from this inbox (DKIM-signed)
curl -X POST https://mailflat.net/api/v1/inboxes/agent-7f3@a7f2c.mailflat.net/send \
-H "X-API-Key: mf_live_…" -H "Content-Type: application/json" \
-d '{"to":"user@gmail.com","subject":"Re: hi","body":"Hello back!"}'
# 4) clean up when done
curl -X DELETE https://mailflat.net/api/v1/inboxes/agent-7f3@a7f2c.mailflat.net \
-H "X-API-Key: mf_live_…"
Line by linewhat each step of this curl example does
- -H "X-API-Key: …"
- Account key, the only auth this API accepts.
- retention_hours: 2
- Short window so abandoned test inboxes clean themselves up.
- /latest
- Newest message only. Poll it every 2–3s until otp_code is non-null.
- DELETE …
- Optional. Retention would purge the messages anyway, but this frees the inbox slot immediately.
The SDKs wrap exactly this loop: wait_for_otp / waitForOtp is the polling block above.
MCP server, give an AI agent inbox tools
MailFlat ships a native Model Context Protocol server. Run it inside Claude Desktop, Cursor or your own agent framework and the model gets six inbox tools automatically.
# no install step, uvx fetches and runs it
MAILFLAT_API_KEY=mf_live_… uvx mailflat-mcp
Seven tools exposed
| Tool | What it does |
|---|---|
| create_inbox(prefix?, label?, retention_hours?) | Open an inbox; retention_hours capped by your plan |
| list_inboxes() | All inboxes this key can see |
| read_messages(address) | Read every message in an inbox |
| wait_for_otp(address, timeout=30) | Poll until an OTP arrives, then return it |
| send_email(address, to, subject?, body?, html?) | Send a DKIM-signed mail from the inbox |
| delete_inbox(address) | Delete the inbox and its messages |
| delete_message(address, message_id) | Delete one message; the inbox itself stays |
Not using MCP?
Agents → Tool spec → Copy spec gives you six of them — everything exceptdelete_message — as a raw function schema for GPT, Claude or LangChain, with the same names and parameters.
GPTClaudeLangChain