Switching from AgentMail
MachineMail serves AgentMail's /v0 API. Change the base URL and the API key, keep your code and the AgentMail SDKs, and pick up trust reports, catch-all and ephemeral inboxes.
MachineMail implements AgentMail’s /v0 API. Wherever AgentMail defines a path, a field name or an enum value, MachineMail uses the same one. Switching is a base URL and an API key.
The change
Get a MachineMail key
Sign up at machinemail.dev/signup and copy the API key the dashboard shows after your first sign-in. Keys start with
mm_live_. See the Quickstart.Point your client at MachineMail
Update webhook verification
MachineMail signs webhooks with its own
X-MachineMail-Signatureheader. If you verify AgentMail webhooks with Svix, replace that check. See Verify webhook signatures.Update your MCP config
If your agent uses MCP, point it at
https://mcp.machinemail.dev/mcpwith the MachineMail key as a Bearer token. Tool names differ from AgentMail’s MCP server. See MCP server and MCP tools.
Note
MachineMail’s test suite runs the official agentmail npm package against a real MachineMail server, covering inboxes, messages (send, reply, reply-all, forward, labels, delete), threads, drafts,
webhooks and domains. The Python SDK calls the same paths, but it is not part of that suite.
What carries over
| AgentMail feature | On MachineMail |
|---|---|
| Inboxes: create, list, get, update, delete, search | Same paths. inbox_id is the email address, as on AgentMail. |
Messages: list, get, send, reply, reply-all, forward, update labels, delete, raw, attachments, batch-get, batch-update, search | Same paths and fields. |
Threads, at /v0/threads and /v0/inboxes/{inbox_id}/threads | Same. |
Drafts, including send_at scheduling | Same. |
| Webhooks: create, list, get, update, delete | Same, with a different signature header. |
Domains: create, list, get, update, delete, verify, zone-file | Same, plus MachineMail fields. |
Lists at /v0/lists/{direction}/{type}, also under pods and inboxes | Same. |
Pods, and pod-nested routes under /v0/pods/{pod_id}/... | Same. |
| API keys with AgentMail permission names, at org, pod and inbox level | Same. |
GET /v0/organizations, GET /v0/auth/me | Same, plus usage counters. |
Metrics: /v0/metrics/events, /usage, /rates | Same paths, with MachineMail event types. |
WebSocket at /v0/ws with subscribe messages | Same protocol. |
client_id idempotency on creates | Same. |
What MachineMail adds
These are extra fields and endpoints. AgentMail clients ignore what they do not know, so they do not break anything.
truston every inbound message, plustrust_levelon list items andGET /v0/inboxes/{inbox_id}/messages/{message_id}/trust. See Trust.- A
QUARANTINElabel. Mail that tries to instruct your agent, or looks like phishing, getsQUARANTINEinstead ofINBOXand amessage.received.quarantinedevent. Lists hide it unless you passinclude_quarantine=true. - Ephemeral inboxes with
ttl_seconds,ephemeralandexpires_at, and aninbox.expiredevent when they are deleted. - Catch-all domains with
catch_all,catch_all_ttl_secondsandinbound_enabledon domains, plus DNS host detection and Domain Connect. See Domains. - Webhook delivery log:
GET /v0/webhooks/{webhook_id}/deliveries, replay and test. - An event log and SSE stream:
GET /v0/eventsandGET /v0/events/stream. - Suppressions at
/v0/suppressions, filled automatically from bounces and complaints. - Export at
GET /v0/inboxes/{inbox_id}/export?format=mbox|jsonl. - Agent self-registration through auth.md.
In the TypeScript SDK, extension fields come through untouched and in snake_case, so read them with a cast:
What is different
| Area | Difference |
|---|---|
| Error bodies | {"error": {"type", "message", "param"}} with the matching HTTP status. type is one of not_found, validation, unauthorized, forbidden, rate_limited, conflict, internal, not_implemented. See Errors. |
| Webhook signatures | X-MachineMail-Signature: t=<unix>,v1=<hex>, an HMAC-SHA256 of <t>.<body> with the webhook secret. |
| Webhook and stream bodies | AgentMail’s shape (type: "event", event_type, event_id, and the message, thread or send objects at the top level) plus a data object holding the same payload and a timestamp. |
| Event types | Adds message.received.quarantined, inbox.created, inbox.expired, draft.sent and webhook.test. There is no message.opened and there are no calendar events. |
| Hidden labels | List calls hide SPAM, BLOCKED, TRASH, UNAUTHENTICATED and QUARANTINE unless you pass the matching include_* flag. |
| Permissions | AgentMail’s permission names, plus inbound_raw for POST /inbound/raw. |
| Agent sign-up | /v0/agent/sign-up, /v0/agent/human and /v0/agent/verify are not available. Agents register through /auth.md instead. |
| Inbox event log | GET /v0/inboxes/{inbox_id}/events returns MachineMail’s event log for that inbox, not AgentMail’s label-event log. |
Not available yet
These AgentMail endpoints return 404 on MachineMail:
- Apps and accounts (
/v0/apps,/v0/accountsand the inbox and pod variants) - Calendar (
/v0/inboxes/{inbox_id}/calendar/...) - Custom webhook headers (
.../webhooks/{webhook_id}/headers) - Hosted domain setup links (
/v0/domains/{domain_id}/setup-link) POST /v0/inboxes/{inbox_id}/authorize- Attachment downloads through threads and drafts (
/threads/{thread_id}/attachments/{attachment_id},/drafts/{draft_id}/attachments/{attachment_id}). Fetch attachments through the message instead.
The AgentMail compatibility reference lists every operation.
Moving existing mail
There is no importer. Your AgentMail data, and addresses on AgentMail’s own domain, stay with AgentMail. A practical migration:
- Create the new inboxes on MachineMail, on
machinemail.devor on your own domain. - If you use a custom domain, add it to MachineMail and switch its MX, SPF and DKIM records to the ones MachineMail gives you. See Set up a custom domain.
- Recreate your webhooks. Each gets a new
secret. - Keep the old AgentMail inboxes readable until in-flight threads are finished.