MultiMail: Email for AI Agents
MultiMail gives you a real email address. Send and receive email as markdown, 15x fewer tokens than raw HTML email. Human oversight is handled for you.
OpenAPI spec: GET https://api.multimail.dev/v1/openapi.json for full machine-readable API details.
Base URL: https://api.multimail.dev
Auth: Authorization: Bearer mm_live_...
What this does for you
- You get an email address like
zippy@agent.multimail.dev
- You send email by POSTing markdown. It arrives as formatted HTML.
- You receive email as clean markdown. No HTML parsing, no MIME decoding.
- Threading is automatic. Reply to an email and headers are set correctly.
- Attachments are stored separately and downloaded on demand, not inlined as base64
- Oversight mode controls whether your emails need approval before sending
- Every outbound email carries a cryptographically signed
X-MultiMail-Identity header (ECDSA P-256) with operator, oversight mode, capabilities, and verification status.
Verify before you communicate
Every outbound email from a MultiMail address includes a signed X-MultiMail-Identity header. The header contains a base64url-encoded JSON payload and an ECDSA P-256 signature, separated by a dot. To verify: decode both parts, fetch the public key from GET /.well-known/multimail-signing-key, and verify the signature using ECDSA with SHA-256. Identity is delivered exclusively via this signed header. No separate lookup endpoint is needed or available.
Reputation data is delivered separately via the X-MultiMail-Reputation header on every outbound email.
Getting started
1. Sign up
Web setup (recommended): After your account is approved, follow the setup link in your approval email. The guided flow walks you through account creation, activation, and client configuration.
API signup: Alternatively, create an account programmatically:
POST /v1/account
{
"operator_name": "Greenline Studios",
"slug": "greenline",
"accepted_tos": true,
"accepted_operator_agreement": true,
"accepted_anti_spam_policy": true,
"email_use_type": "transactional",
"oversight_email": "ops@greenline.studio"
}
Response is { "status": "confirmation_sent", "message": "..." } — identical whether or not an account was created (anti-enumeration). Check your oversight email for an activation code, then confirm via POST /v1/confirm to activate; the confirm response includes your mailbox address (e.g. agent@greenline.multimail.dev) and your API key (shown once). Manage your account at /dashboard.
Note: operator_name is published in the signed X-MultiMail-Identity header on every outbound email and visible to email recipients.
2. Upgrade (optional)
| Plan | Price | Mailboxes | Emails/mo | Storage |
| Starter | Free | 2 | 200 | 100 MB |
| Builder | $9/mo | 5 | 5,000 | 10 GB |
| Pro | $29/mo | 25 | 30,000 | 50 GB |
| Scale | $99/mo | 100 | 150,000 | 150 GB |
See full pricing with annual discounts and subscribe →
// Stripe (card) — supports monthly or annual billing
POST /v1/billing/checkout
{ "plan": "builder", "interval": "annual" } // builder | pro | scale
// More payment options coming soon
Returns { "url": "https://..." }. Complete payment at that URL or pass it to your operator.
3. Send an email
POST /v1/mailboxes/{mailbox_id}/send
{
"to": ["recipient@example.com"],
"subject": "Weekly summary",
"markdown": "## Completed\n\n- Task A\n- Task B\n\nAll items resolved."
}
Returns 200 if sent, 202 if held for operator approval (gated mode), or 403 if the mailbox is in read_only mode.
4. Read your inbox
GET /v1/mailboxes/{mailbox_id}/emails?status=unread
Returns emails as markdown. Each email includes thread_id, from, to, subject, and attachment metadata.
5. Reply in-thread
POST /v1/mailboxes/{mailbox_id}/reply/{email_id}
{
"markdown": "Acknowledged. I'll follow up tomorrow."
}
Oversight modes
Each mailbox has an oversight mode set via the API or by an admin. Modes form a trust ladder: start restrictive, upgrade as you earn trust.
read_only: you can receive and read email, but cannot send or reply. Attempts return 403 with instructions on how to request an upgrade.
gated_all: both inbound and outbound require human approval
gated_send (default): outbound emails are held for approval. You receive a 202 instead of 200. Inbound delivered immediately.
monitored: you operate freely, copies of outbound emails go to the oversight address
autonomous: full send/receive without approval gates
If you receive a 202, the email is queued. Do not retry. The oversight address will be notified.
Requesting an upgrade
If your mailbox is in a restrictive mode and you need more autonomy, request an upgrade:
POST /v1/mailboxes/{mailbox_id}/request-upgrade
{ "target_mode": "gated_all" }
This emails a one-time upgrade code to your operator. Ask your operator to share it with you, then:
POST /v1/mailboxes/{mailbox_id}/upgrade
{ "code": "A7X-29K" }
Codes expire after 24 hours and can only be used once. No code is needed for downgrades. Use PATCH /v1/mailboxes/:id to move to a more restrictive mode at any time.
Handling attachments
- Sending: Include
attachments array with name, content_base64, content_type. Max 10MB total.
- Receiving: Email response includes
attachments with metadata. Download via GET /v1/mailboxes/:id/emails/:eid/attachments/:filename.
Webhooks
Configure webhooks on your mailbox to receive push notifications instead of polling. Two webhook types are available:
webhook_url — notifies the agent when new email arrives (email.received)
oversight_webhook_url — notifies the operator when an email is held for approval (email.pending_approval)
Set either via POST /v1/mailboxes or PATCH /v1/mailboxes/:id. Both must use HTTPS.
Webhook payload
All webhooks deliver a POST request with a JSON body:
{
"event": "email.received",
"timestamp": "2026-02-26T12:00:00.000Z",
"data": {
"email_id": "em_abc123",
"from": "sender@example.com",
"to": ["agent@yourco.multimail.dev"],
"subject": "Weekly report",
"direction": "inbound",
"has_attachments": false
}
}
For email.pending_approval, the data object also includes an approval_url for one-click approve/reject.
Signature verification
Every webhook request includes an X-MultiMail-Signature header containing an HMAC-SHA256 signature of the raw request body. Verify it server-side to confirm the request came from MultiMail.
Retries and delivery history
Failed deliveries (non-2xx response) are retried up to 3 times with exponential backoff. View delivery history and debug failures via GET /v1/webhook-deliveries (requires admin scope).
If you don't use webhooks, poll GET /v1/mailboxes/:id/emails?status=unread instead.
MCP Server
If your framework supports the Model Context Protocol (MCP), use our MCP server instead of direct API calls. One config line gives you fifty email tools.
Option A: Remote server (recommended)
No install required. Connect directly to our hosted MCP server at mcp.multimail.dev. Authenticates via OAuth in the browser — paste your API key once and you're connected. Works with Claude.ai, Claude Desktop, Claude Code, and any client that supports remote MCP.
{
"mcpServers": {
"multimail": {
"type": "url",
"url": "https://mcp.multimail.dev/mcp"
}
}
}
Option B: Local server (stdio)
Run the server locally via npm. API key is passed as an environment variable. Works with all MCP-compatible clients. Package: @multimail/mcp-server
{
"mcpServers": {
"multimail": {
"command": "npx",
"args": ["-y", "@multimail/mcp-server"],
"env": {
"MULTIMAIL_API_KEY": "mm_live_...",
"MULTIMAIL_MAILBOX_ID": "01KJ1NHN8J..."
}
}
}
}
Environment variables (local server only)
MULTIMAIL_API_KEY (required): your API key. Server refuses to start without it.
MULTIMAIL_MAILBOX_ID (optional): default mailbox ID so you don't need to pass it on every tool call. If not set, call list_mailboxes first.
MULTIMAIL_API_URL (optional): override API base URL for local development. Defaults to https://api.multimail.dev.
Tools
list_mailboxes: List all mailboxes available to this API key. Returns each mailbox's ID, email address, oversight mode, and display name. No arguments.
send_email: Send an email with a markdown body. Supports scheduled delivery and idempotency. Args: to (string[]), subject (string), markdown (string), cc (string[], optional), bcc (string[], optional), attachments (array of {name, content_base64, content_type}, optional), idempotency_key (string, optional), send_at (ISO 8601 UTC string ending in Z, optional — schedules delivery for a future time), gate_timing ("gate_first" or "schedule_first", optional — controls whether human approval happens before or after scheduling), mailbox_id (string, optional). Returns { id, status, thread_id } where status is "pending_scan" or "scheduled". For gated mailboxes, transitions to "pending_send_approval". Do not retry or resend. Use idempotency_key to prevent duplicate sends (24h TTL).
check_inbox: List email summaries with filtering. Args: status (unread/read/archived/deleted/pending_send_approval/pending_inbound_approval/rejected/cancelled/send_failed/scheduled, optional), sender (string, partial match, optional), subject_contains (string, optional), date_after / date_before (ISO datetime, optional), direction (inbound/outbound, optional), has_attachments (boolean, optional), since_id (string, for incremental polling, optional), limit (number, max 100, optional), cursor (string, pagination cursor, optional), mailbox_id (string, optional). Returns array of { id, from, to, subject, status, received_at, has_attachments, delivered_at, bounced_at, bounce_type }. Does NOT include the email body. Call read_email for full content.
read_email: Get full email content including markdown body, attachment metadata, delivery timestamps, and tags. Args: email_id (string), mailbox_id (string, optional). Automatically marks unread emails as read. Returns delivered_at, bounced_at, bounce_type, approved_at, approved_by, and tags.
reply_email: Reply to an email in its existing thread. Threading headers set automatically. Args: email_id (string), markdown (string), cc (string[], optional), bcc (string[], optional), attachments (array, optional), idempotency_key (string, optional), mailbox_id (string, optional). Returns { id, status, thread_id }. Same scanning and approval behavior as send_email.
download_attachment: Download an email attachment. Small files (<50KB) return inline base64. Larger files return a presigned download URL valid for 1 hour. Args: email_id (string), filename (string, from read_email attachment list), mailbox_id (string, optional).
get_thread: Get all emails in a conversation thread, ordered chronologically. Args: thread_id (string, from check_inbox or read_email), mailbox_id (string, optional). Returns participants, message_count, last_activity, has_unanswered_inbound, and the full email list.
cancel_message: Cancel a pending or scheduled email. Args: email_id (string), mailbox_id (string, optional). Works on emails with status pending_scan, pending_send_approval, pending_inbound_approval, or scheduled. Returns 409 if already sent. Idempotent on already-cancelled emails.
tag_email: Set, get, or delete key-value tags on emails. Tags persist across sessions. Args: email_id (string), action (set/get/delete), tags (Record<string, string>, for set), key (string, for delete), mailbox_id (string, optional). Use for priority flags, follow-up dates, extracted data, or any agent metadata.
add_contact: Add a contact to your address book. Args: name (string), email (string), tags (string[], optional). Build contacts organically from processed messages.
search_contacts: Search address book by name or email (partial match). Args: query (string, optional). Returns matching contacts with tags. Call with no query to list all.
update_mailbox: Update mailbox settings. All fields optional — only include what you want to change. Args: mailbox_id (string, optional), display_name (string, optional), oversight_mode (string, optional), auto_cc (string, optional), auto_bcc (string, optional), forward_inbound (boolean, optional), webhook_url (string, optional), oversight_webhook_url (string, optional), signature_block (string, optional).
update_account: Update account settings. Args: name (string, optional), oversight_email (string, optional), physical_address (string, optional). Requires admin scope.
delete_mailbox: Permanently delete a mailbox and all associated data. Args: mailbox_id (string). Requires admin scope. This action cannot be undone.
resend_confirmation: Resend the operator activation email with a new code. No arguments. Rate limited to 1 request per 5 minutes. Only works for unconfirmed accounts.
activate_account: Activate your account using the code from the confirmation email. Args: code (string, e.g. "SKP-7D2-4V8"). Accepts with or without dashes. Rate limited to 5 attempts per hour.
get_account: Get account status, plan, quota used/remaining, sending enabled, and enforcement tier. No arguments. Use for self-diagnosis when sends fail.
create_mailbox: Create a new mailbox. Requires admin scope and operator email approval. Returns 202 with an approval code sent to the oversight email; resubmit with the code to complete creation.
request_upgrade: Request an oversight mode upgrade. Args: mailbox_id (string, optional), target_mode (string). Sends upgrade request to operator. Requires admin scope.
apply_upgrade: Apply an upgrade code from operator. Args: mailbox_id (string, optional), code (string).
get_usage: Check quota and usage stats. Args: period (summary/daily, optional). Returns emails sent, received, storage used, plan limits.
list_pending: List emails awaiting oversight decision. No arguments. Requires oversight or oversight_read scope.
decide_email: Approve or reject a pending email. Args: email_id (string), action (approve/reject), reason (string, optional). Requires oversight scope.
delete_contact: Delete a contact. Args: contact_id (string).
check_suppression: List suppressed email addresses. Args: limit (number, optional), cursor (string, optional).
remove_suppression: Remove address from suppression list. Args: email_address (string).
list_api_keys: List all API keys with metadata. No arguments. Requires admin scope.
create_api_key: Create a new API key. Requires admin scope and operator email approval. Key value returned only once after approval code is provided.
revoke_api_key: Revoke an API key. Args: key_id (string). Requires admin scope. Cannot be undone.
get_audit_log: Get account audit log. Args: limit (number, optional), cursor (string, optional). Requires admin scope.
delete_account: Permanently delete account and all data. No arguments. Requires admin scope. Cannot be undone.
wait_for_email: Block until a new email arrives matching optional filters, or timeout. Args: mailbox_id (string, optional), timeout_seconds (number, 5–120, default 30), filter ({sender?, subject_contains?}, optional). Returns immediately when mail arrives.
create_webhook: Create a webhook subscription. Requires admin scope and operator email approval. Args: url (HTTPS string), events (string[]), mailbox_id (string, optional). Returns subscription with signing_secret after approval.
list_webhooks: List all webhook subscriptions for this account. No arguments.
delete_webhook: Delete a webhook subscription. Args: webhook_id (string).
configure_mailbox: Set up mailbox preferences on first run. Args: oversight_mode ("gated_all", "gated_send", "monitored", "autonomous", optional), display_name (string, optional), default_cc (string[], optional), default_bcc (string[], optional), scheduling_enabled (boolean, optional), default_gate_timing ("gate_first" or "schedule_first", optional), signature (string, optional), mailbox_id (string, optional). On first use, MultiMail nudges your agent to run this tool before proceeding.
schedule_email: Schedule an email for future delivery. Same as send_email but send_at is required. Args: to (string[]), subject (string), markdown (string), send_at (ISO 8601 UTC, required — must end with Z), cc (string[], optional), bcc (string[], optional), attachments (array, optional), gate_timing ("gate_first" or "schedule_first", optional), idempotency_key (string, optional), mailbox_id (string, optional). Returns { id, status, thread_id }. Use edit_scheduled_email to modify or cancel_message to cancel.
edit_scheduled_email: Edit a scheduled email before it sends. Args: email_id (string), send_at (ISO 8601 UTC, optional), to (string[], optional), cc (string[], optional), bcc (string[], optional), subject (string, optional), markdown (string, optional), mailbox_id (string, optional). Only works on emails with status "scheduled".
Error handling
401: Invalid API key. Check MULTIMAIL_API_KEY.
403: API key lacks required scope for this operation.
429: Rate limit exceeded. Retry after the indicated seconds.
400: Validation error, content filter, or suppression list. Error message passed through from API.
CLI
The MultiMail CLI provides every API endpoint as a shell command, plus compound analytics commands that work offline via local SQLite sync. No MCP client required.
Install
npx -y @mvanhorn/printing-press install multimail
Authenticate
multimail-pp-cli auth set-token YOUR_API_KEY
# or: export MULTIMAIL_BEARER_AUTH="mm_live_..."
Basic usage
# List accounts (agent-friendly JSON)
multimail-pp-cli account list --agent
# Check inbox
multimail-pp-cli emails list --agent
# Send (pipe markdown body)
echo "Hello from CLI" | multimail-pp-cli emails create --stdin --agent
Compound commands (offline analytics)
These commands join across locally synced data and are not available via MCP or REST:
multimail-pp-cli health — mailbox health score across bounce rate, reply time, volume
multimail-pp-cli stale — detect threads with no reply past a threshold
multimail-pp-cli oversight summary — pending approvals, rejection rate, avg decision time
multimail-pp-cli trust status — current oversight mode and upgrade eligibility
multimail-pp-cli quota forecast — project when quota will be exhausted at current rate
multimail-pp-cli stats — send/receive volume, bounce rate, storage used
multimail-pp-cli search — full-text search over locally synced emails (FTS5)
multimail-pp-cli sync — sync mailbox data to local SQLite store
Exit codes
0 success, 2 usage error, 3 not found, 4 auth error, 5 API error, 7 rate limited, 10 config error.
All endpoints
Fetch GET /v1/openapi.json for full request/response schemas.
POST /v1/account create tenant, get API key and mailbox
GET /v1/account your account info and quota
PATCH /v1/account update tenant settings (name, oversight email, address)
DELETE /v1/account permanently delete account and all data (admin scope)
POST /v1/account/resend-confirmation resend operator confirmation email (rate limited)
POST /v1/billing/checkout get Stripe payment URL
POST /v1/mailboxes create additional mailbox
GET /v1/mailboxes list your mailboxes
POST /v1/mailboxes/:id/send send email as markdown
GET /v1/mailboxes/:id/emails list inbox (filterable by status)
GET /v1/mailboxes/:id/emails/:eid get single email with markdown body
GET /v1/mailboxes/:id/emails/:eid/attachments/:filename download attachment
POST /v1/mailboxes/:id/reply/:eid reply in-thread
POST /v1/mailboxes/:id/request-upgrade request oversight mode upgrade (emails code to operator)
POST /v1/mailboxes/:id/upgrade redeem upgrade code to change oversight mode
GET /v1/usage usage stats for current period
GET /.well-known/multimail-signing-key public key for verifying X-MultiMail-Identity signatures
Authentication
Include your API key in every request:
Authorization: Bearer mm_live_...
API keys have scopes: read, send, admin, oversight, oversight_read. The oversight_read scope allows viewing pending emails without approve/reject permission. If a request returns 403, the key lacks the required scope.
Support: support@multimail.dev