Email for agents
Give your agent a real inbox.
AI Mail MCP is an email server you talk to through MCP. Your agent can add a domain, get the exact DNS records, create mailboxes, read one inbox across all of them, wait for a sign-up code, and send or reply in-thread. Every write has a dry run, every call is audited, and every token is scoped.
MCP endpoint https://aimailmcp.com/mcp
1. Bring a domain
add_domain returns the MX, SPF, DKIM and DMARC records. On domains we manage, apply_dns writes them for you; anywhere else, publish them and call verify_domain.
2. Make mailboxes
create_mailbox for agent@yourdomain.com, plus aliases, a catch-all and forwarding. Each mailbox also gets a route address any mail host can forward to.
3. Read and send
list_messages is one inbox across every mailbox the token can see. send_message, reply and forward go out through Cloudflare Email Sending with Resend as failover.
Connect
Sign in with an emailed code through OAuth (Claude and most MCP clients do this for you), or get a token without a browser.
Claude Code
claude mcp add --transport http aimail https://aimailmcp.com/mcpClaude Code opens the sign-in page on first use. To use a token instead, add --header "Authorization: Bearer $AIMAIL_TOKEN".
Claude.ai and Claude Desktop
Settings, Connectors, Add custom connector. Paste:
https://aimailmcp.com/mcpYou will be asked for your email and a 6-digit code.
Any MCP client (JSON)
{
"mcpServers": {
"aimail": {
"type": "http",
"url": "https://aimailmcp.com/mcp",
"headers": { "Authorization": "Bearer amk_..." }
}
}
}Agents without a browser
curl -X POST https://aimailmcp.com/api/v1/signup \
-H 'content-type: application/json' \
-d '{"email":"you@example.com"}'
# then, with the code from the email:
curl -X POST https://aimailmcp.com/api/v1/signup/confirm \
-H 'content-type: application/json' \
-d '{"email":"you@example.com","code":"123456"}'Returns an admin token once. Use create_token to hand narrower tokens to other agents.
Tool reference
Generated from the live registry at /tools.json. Every tool that writes accepts dry_run.
Domains 6
Bring a domain, get the exact DNS records, verify it.
add_domainAdd a domain
Add a domain (or subdomain) to this account so it can hold mailboxes. Returns the DNS plan. A domain whose zone is on this service's Cloudflare account is "managed" (apply_dns can write its records, internal accounts only); any other domain is "external" and proves ownership with a TXT record, then verify_domain.
| Parameter | Type | Notes |
|---|---|---|
name required | string | Domain name, for example example.com or mail.example.com. |
dry_run | boolean | Validate and report what would happen without changing anything. |
get_dns_planGet the DNS plan
The exact DNS records (MX, SPF, DKIM, DMARC, ownership TXT) a domain needs for receiving and sending, and whether each is in place.
| Parameter | Type | Notes |
|---|---|---|
domain required | string | Domain name, for example example.com or mail.example.com. |
apply_dnsApply DNS records (managed domains)
Write the DNS plan to a managed domain on this service's Cloudflare account: enables Email Routing (MX + SPF), routes mail to the receiver, registers the domain for sending and adds DKIM and DMARC. Never deletes records. Refuses when the name already has MX records pointing elsewhere unless replace_existing_mx is true. Use dry_run first.
| Parameter | Type | Notes |
|---|---|---|
domain required | string | Domain name, for example example.com or mail.example.com. |
replace_existing_mx | boolean | Allow enabling routing on a name whose MX currently points at another provider. |
dry_run | boolean | Validate and report what would happen without changing anything. |
verify_domainVerify a domain
Check the domain's live DNS: ownership TXT (external domains), MX, SPF, DMARC and the sending provider's DKIM status. Marks the domain verified when ownership is proven, and registers it for sending once it is.
| Parameter | Type | Notes |
|---|---|---|
domain required | string | Domain name, for example example.com or mail.example.com. |
dry_run | boolean | Validate and report what would happen without changing anything. |
list_domainsList domains
Every domain on this account with its status, mode, sending readiness and mailbox count.
No parameters.
remove_domainRemove a domain
Remove a domain from this account. Refuses while it still has mailboxes unless delete_mailboxes is true (which deletes them and their mail). DNS records are left as they are; the response lists the ones you may want to remove.
| Parameter | Type | Notes |
|---|---|---|
domain required | string | Domain name, for example example.com or mail.example.com. |
confirm required | string | Repeat the domain name to confirm. |
delete_mailboxes | boolean | |
dry_run | boolean | Validate and report what would happen without changing anything. |
Mailboxes 6
Addresses that receive and send, plus aliases, catch-alls and forwarding.
create_mailboxCreate a mailbox
Create a mailbox (an address that receives and sends) on one of this account's domains. Also returns its route address, which any mail host can forward to.
| Parameter | Type | Notes |
|---|---|---|
address required | string | The new address, for example agent@example.com. |
display_name | string | |
dry_run | boolean | Validate and report what would happen without changing anything. |
list_mailboxesList mailboxes
Every mailbox this token can see, with unread counts, last received time, aliases, forwarding and route address. Also describes the calling token (scope, can_send, daily cap).
No parameters.
delete_mailboxDelete a mailbox
Delete a mailbox. Mail to it is rejected from then on; its stored messages stay readable by owner tokens for audit until the domain is removed.
| Parameter | Type | Notes |
|---|---|---|
address required | string | Mailbox address (or mailbox id). |
confirm required | string | Repeat the address to confirm. |
dry_run | boolean | Validate and report what would happen without changing anything. |
add_aliasAdd an alias
Deliver mail sent to another address on one of this account's domains into an existing mailbox.
| Parameter | Type | Notes |
|---|---|---|
mailbox required | string | Mailbox address (or mailbox id). |
alias required | string | The extra address, for example support@example.com. |
dry_run | boolean | Validate and report what would happen without changing anything. |
set_catch_allSet the catch-all
Deliver mail for any unknown address on a domain into one mailbox, or pass mailbox: null to turn the catch-all off.
| Parameter | Type | Notes |
|---|---|---|
domain required | string | |
mailbox required | string | null | Mailbox address (or mailbox id). |
dry_run | boolean | Validate and report what would happen without changing anything. |
set_forwardingSet forwarding
Also forward every message a mailbox receives to up to 5 external addresses (the original is attached as message/rfc822). Pass an empty list to stop. Forwarded copies never re-forward.
| Parameter | Type | Notes |
|---|---|---|
mailbox required | string | Mailbox address (or mailbox id). |
forward_to required | string[] | |
dry_run | boolean | Validate and report what would happen without changing anything. |
Read 9
One unified inbox across every mailbox the token can see.
list_messagesList messages (unified inbox)
One inbox across every mailbox this token can see, newest first, each item tagged with its mailbox. Filters: mailboxes, folder (inbox, archive, sent, drafts, trash, all), unread_only, label, from, subject_contains, since (default 30 days). Paginate with next_cursor. Sender-controlled text arrives inside an untrusted_external_content envelope.
| Parameter | Type | Notes |
|---|---|---|
mailboxes | string[] | Limit to these mailboxes (addresses or ids). Default: every mailbox this token can see. |
folder | "inbox" | "archive" | "sent" | "drafts" | "trash" | "all" | |
unread_only | boolean | |
direction | "inbound" | "outbound" | |
label | string | |
from | string | |
subject_contains | string | |
since | string | ISO date; default 30 days ago. |
limit | integer | |
cursor | string |
search_messagesSearch messages
Full-text search over subject, sender, recipients and body across every mailbox this token can see (all folders by default).
| Parameter | Type | Notes |
|---|---|---|
query required | string | |
mailboxes | string[] | Limit to these mailboxes (addresses or ids). Default: every mailbox this token can see. |
folder | "inbox" | "archive" | "sent" | "drafts" | "trash" | "all" | |
since | string | |
limit | integer | |
cursor | string |
get_messageGet a message
Headers, text body (or text derived from HTML), extracted links, attachment list and threading ids for one message. Body truncated at max_chars (default 20000, max 200000) with a truncated flag. HTML only with include_html.
| Parameter | Type | Notes |
|---|---|---|
id required | string | |
max_chars | integer | |
include_html | boolean |
get_threadGet a thread
The whole conversation a message belongs to, inbound and outbound, oldest first, as previews.
| Parameter | Type | Notes |
|---|---|---|
id required | string | Any message id in the thread. |
wait_for_messageWait for a message
Block until a matching message arrives (or timeout_s, max 60, default 25) and return it in full. Built for sign-up codes and magic links: create a mailbox, use it in a form, wait here. since defaults to 60 seconds ago.
| Parameter | Type | Notes |
|---|---|---|
mailbox | string | |
from | string | |
subject_contains | string | |
since | string | |
timeout_s | integer | |
max_chars | integer |
get_attachmentGet an attachment
One attachment as base64 (up to 5 MB). attachment_id is the index listed by get_message.
| Parameter | Type | Notes |
|---|---|---|
message_id required | string | |
attachment_id required | integer |
mark_readMark read or unread
Mark up to 100 messages read (or unread with read: false). Ids outside this token's scope come back in not_found_in_scope.
| Parameter | Type | Notes |
|---|---|---|
ids required | string[] | Message ids (1 to 100). |
read | boolean | |
dry_run | boolean | Validate and report what would happen without changing anything. |
labelAdd or remove labels
Add and/or remove labels on up to 100 messages. Labels are free-form short strings; filter on them with list_messages label.
| Parameter | Type | Notes |
|---|---|---|
ids required | string[] | Message ids (1 to 100). |
add | string[] | |
remove | string[] | |
dry_run | boolean | Validate and report what would happen without changing anything. |
archiveArchive
Move up to 100 messages out of the inbox into archive (or back with unarchive: true).
| Parameter | Type | Notes |
|---|---|---|
ids required | string[] | Message ids (1 to 100). |
unarchive | boolean | |
dry_run | boolean | Validate and report what would happen without changing anything. |
Send 4
Send, reply in-thread, forward and draft, behind per-token gates.
send_messageSend a message
Send a new email from a mailbox in scope (or send a saved draft with draft_id). Requires a token with can_send; counts against its daily cap; at most 20 recipients. Use dry_run to check the gates without sending.
| Parameter | Type | Notes |
|---|---|---|
from | string | Mailbox address to send from. |
to | string | string[] | |
cc | string | string[] | |
bcc | string | string[] | |
subject | string | |
text | string | |
html | string | |
reply_to | string | |
attachments | object[] | Up to 10 attachments, 5 MB each, base64. |
draft_id | string | |
dry_run | boolean | Validate and report what would happen without changing anything. |
replyReply
Reply in-thread from the mailbox that received the message (In-Reply-To and References set, "Re:" added once, original quoted). reply_all adds the other recipients as cc.
| Parameter | Type | Notes |
|---|---|---|
message_id required | string | |
text | string | |
html | string | |
reply_all | boolean | |
cc | string | string[] | |
attachments | object[] | Up to 10 attachments, 5 MB each, base64. |
dry_run | boolean | Validate and report what would happen without changing anything. |
forwardForward
Forward a message (with its attachments) to new recipients, with an optional note on top. Sends from the receiving mailbox unless from names another mailbox in scope.
| Parameter | Type | Notes |
|---|---|---|
message_id required | string | |
to required | string | string[] | |
cc | string | string[] | |
text | string | |
from | string | |
dry_run | boolean | Validate and report what would happen without changing anything. |
create_draftCreate a draft
Save a draft in a mailbox (folder drafts) for a human or another agent to review. Send it later with send_message draft_id. Does not need can_send. Pass reply_to_message_id to thread it as a reply.
| Parameter | Type | Notes |
|---|---|---|
from required | string | |
to | string | string[] | |
cc | string | string[] | |
bcc | string | string[] | |
subject | string | |
text | string | |
html | string | |
reply_to_message_id | string | |
dry_run | boolean | Validate and report what would happen without changing anything. |
Automation 3
Signed webhooks when mail arrives or a send finishes.
create_webhookCreate a webhook
POST a signed JSON event to your https URL when mail arrives or a send finishes. Events: message.received, message.sent, message.failed. Each delivery carries X-AIMail-Timestamp and X-AIMail-Signature (v1=hex HMAC-SHA256 of "timestamp.body" with the secret returned once here). Private and loopback targets are refused.
| Parameter | Type | Notes |
|---|---|---|
url required | string | |
events | "message.received" | "message.sent" | "message.failed"[] | |
mailboxes | string[] | |
dry_run | boolean | Validate and report what would happen without changing anything. |
list_webhooksList webhooks
Webhooks on this account with their events, scope and last delivery status. Secrets are never shown again.
No parameters.
delete_webhookDelete a webhook
Stop and remove a webhook.
| Parameter | Type | Notes |
|---|---|---|
webhook_id required | string | |
dry_run | boolean | Validate and report what would happen without changing anything. |
Access 4
Scoped tokens for other agents, and the audit log.
create_tokenCreate a token
Mint a token for another agent, scoped to some mailboxes (default: all), with or without send permission and with a daily send cap. A token can never exceed the one that creates it. The token value is returned once and never stored.
| Parameter | Type | Notes |
|---|---|---|
label required | string | |
mailboxes | string[] | |
can_send | boolean | |
daily_send_cap | integer | |
is_admin | boolean | |
expires_in_days | integer | |
dry_run | boolean | Validate and report what would happen without changing anything. |
list_tokensList tokens
Tokens on this account: label, prefix, scope, send permission, cap, last use. Never the values.
| Parameter | Type | Notes |
|---|---|---|
include_revoked | boolean |
revoke_tokenRevoke a token
Revoke a token by id or prefix. It stops working immediately.
| Parameter | Type | Notes |
|---|---|---|
token required | string | Token id or its prefix (amk_...). |
dry_run | boolean | Validate and report what would happen without changing anything. |
audit_logRead the audit log
Recent tool calls on this account (every attempt, including refusals and dry runs): tool, outcome, token, time.
| Parameter | Type | Notes |
|---|---|---|
limit | integer | |
tool | string |
How mail reaches you
Managed domains
When a domain's DNS lives on our Cloudflare account, its MX records point at Cloudflare Email Routing (route1/2/3.mx.cloudflare.net). Routing hands each message to our aimail-receiver worker, which signs the raw message and posts it to this server. We store the original RFC 822 source and parse it into the inbox.
Your own DNS
Keep your MX where it is. Prove ownership with one TXT record (_aimail.yourdomain), add the sending records from get_dns_plan, and forward the addresses you want to each mailbox's route address. Sending works from your domain with your DKIM.
| Record | Name | Value | Why |
|---|---|---|---|
| MX | yourdomain | route1/2/3.mx.cloudflare.net | Inbound, managed domains only |
| TXT | _aimail.yourdomain | aimail-verify=... | Ownership, external domains |
| TXT / MX | send.yourdomain | from get_dns_plan | SPF and bounces for sending |
| TXT | resend._domainkey.yourdomain | from get_dns_plan | DKIM signature key |
| TXT | _dmarc.yourdomain | v=DMARC1; p=none | Policy; tighten once mail flows |
Security model
Scoped tokens
A token sees only its mailboxes. Sending needs can_send and stops at a rolling 24 hour cap. A token can never mint one with more power than itself. Only hashes are stored.
Signed inbound
The receiver worker signs every message with HMAC-SHA256 over a timestamp and the exact body. Unsigned, stale or altered posts are rejected before parsing. Outbound webhooks are signed the same way.
Untrusted content
Everything a sender controls comes back inside an untrusted_external_content envelope with a note that it is data, not instructions. Links are extracted; HTML is returned only on request.
Audit and dry runs
Every tool call writes an audit row, including refusals. Every write accepts dry_run. Destructive tools ask you to repeat the name you are removing.
DNS writes are fenced
apply_dns only works on zones in our own Cloudflare account, for internal accounts, never deletes records, and refuses to take over a domain whose mail goes elsewhere.
Your raw mail
The original message source is kept in private object storage, so nothing is lost to parsing. Bodies are searchable; attachments are served only to tokens that can see the mailbox.