Quickstart
Sign up with a magic link, get an API key, create an inbox, send an email and receive replies by polling, webhook and Server-Sent Events.
This walks through the whole loop with curl, TypeScript (fetch) and Python (requests). It takes about five minutes.
1. Sign up and get an API key
Go to machinemail.dev/signup, enter your email address and, optionally, an organization name. MachineMail emails you a sign-in link. There are no passwords.
Open the link within 15 minutes. It works once. On your first sign-in MachineMail creates your organization, its default pod and a first API key.
The dashboard shows the first key once, right after you land. Copy it. You can create more keys at any time under API keys.
Keep the key in an environment variable:
Warning
An API key is shown only once and only its hash is stored. A key created at the top level can do everything in your organization. For agents, create narrower keys scoped to a pod or an inbox, with a permission list. See API keys.
Check that the key works:
2. Create an inbox
An inbox is an email address. Pick a username, or leave it out and MachineMail generates one. Without a domain, the inbox is created on the shared machinemail.dev domain.
The inbox_id is the address. Use it in every inbox path. Addresses on the shared domain are first come, first served: if support-agent is taken you get 409 conflict, so choose something more specific. GET /v0/inboxes/availability?username=support-agent tells you before you try.
Note
Want an address that cleans up after itself? Add "ttl_seconds": 3600 and the inbox, with its messages, is deleted an hour after it was created or last received mail, whichever is later. See
Inboxes.
3. Send an email
Send to an address you can read, so you can reply in the next step.
The email goes out from Support agent <support-agent@machinemail.dev>. You can also send html, cc, bcc, reply_to, attachments and custom headers. See Sending.
4. Receive the reply
Reply to the email from your own mail client. MachineMail threads the reply onto the message you sent, computes a trust report and emits a message.received event. Pick one of three ways to find out about it.
Option A: poll
The simplest option. List messages newer than a timestamp, then fetch the full message, which includes the body and the trust report.
The labels=INBOX filter skips your own sent messages, which carry SENT instead. after compares against the message timestamp, which for inbound mail comes from the sender’s Date header, so leave a minute of margin for clock skew. Lists are newest first and leave out spam and quarantined mail unless you ask for it with include_spam or include_quarantine.
The full message looks like this (trimmed):
Give your model trust.content.text, not text or html. It is the new part of the message with quoted history, signatures, hidden text and zero-width characters removed. The trust report explains every field.
When you have handled a message, mark it read by removing the UNREAD label:
Option B: webhook
Register an HTTPS endpoint and MachineMail POSTs every matching event to it, signed, with retries.
Each delivery is a JSON POST:
The message and thread objects appear twice: at the top level, as AgentMail sends them, and inside data. Verify X-MachineMail-Signature before you trust the body and return a 2xx quickly. Verify webhook signatures has the code in TypeScript, Python and Go.
To try it without waiting for real mail, send a test event: POST /v0/webhooks/{webhook_id}/test.
Option C: Server-Sent Events
Open a long-lived stream and read events as they happen. Good for scripts and workers that cannot receive inbound HTTP.
The stream needs a key with the metrics_read permission (unrestricted keys have it). It sends a : ping comment every 15 seconds when idle. Reconnect with the Last-Event-ID header to catch up on what you missed. Realtime events covers reconnection and the WebSocket alternative.
5. Reply in the thread
Reply to the inbound message. MachineMail addresses it to the sender (or their Reply-To), sets Re: on the subject and adds In-Reply-To and References, so it lands in the same thread on both sides.
Testing without a mail client
POST /inbound/raw delivers a raw RFC 5322 message straight into one of your inboxes, which is handy in tests and CI. It needs a key with the inbound_raw permission. Full-access keys do not include it: tick inbound_raw when you create the key in the dashboard (or create it with "permissions": {"*": true, "inbound_raw": true} from a key that already has it). Keep it to test and CI keys.
Injected messages get received_via: "raw". Any Authentication-Results headers in them are ignored: authentication is always none, with the reason Injected via API (/inbound/raw); authentication results not verified, so injected mail is never verified or known.