Skip to content
machinemail

Inboxes

Inboxes are email addresses your agents own. Learn about named, generated, ephemeral and catch-all inboxes, the shared machinemail.dev domain, custom domains, pausing and deletion.

An inbox is one email address. It receives mail, sends mail, and holds the threads, messages and drafts for that address. As in AgentMail, the inbox ID is the address itself: inbox_id and email are the same string.

Inbox
{
  "inbox_id": "support-agent@machinemail.dev",
  "organization_id": "org_01m4fy8h140000efvagszh6rpx",
  "pod_id": "pod_01m4fy8h150000w4xty1ehfnqa",
  "email": "support-agent@machinemail.dev",
  "display_name": "Support agent",
  "client_id": "support-agent-v1",
  "status": "active",
  "ephemeral": false,
  "created_by": "api",
  "created_at": "2026-10-09T09:00:15.835Z",
  "updated_at": "2026-10-09T09:00:15.835Z"
}
FieldNotes
inbox_id, emailThe address, lowercase. Use it in paths (URL-encode the @ if your client requires it).
display_nameShown as the sender name on outbound mail: Support agent <support-agent@machinemail.dev>.
statusactive or paused.
metadataYour own string-to-string map.
ephemeral, ttl_seconds, expires_atSet on inboxes that delete themselves. See below.
created_byapi, dashboard, catch_all or agent.
client_idYour idempotency key, if you set one.

Creating an inbox

Terminal
curl https://api.machinemail.dev/v0/inboxes \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"username": "support-agent", "display_name": "Support agent", "client_id": "support-agent-v1"}'
create-inbox.ts
const res = await fetch('https://api.machinemail.dev/v0/inboxes', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.MACHINEMAIL_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ username: 'support-agent', display_name: 'Support agent', client_id: 'support-agent-v1' }),
});
const inbox = await res.json();
create_inbox.py
import os, requests

inbox = requests.post(
    "https://api.machinemail.dev/v0/inboxes",
    headers={"Authorization": f"Bearer {os.environ['MACHINEMAIL_API_KEY']}"},
    json={"username": "support-agent", "display_name": "Support agent", "client_id": "support-agent-v1"},
).json()

All body fields are optional:

FieldNotes
usernameThe local part. a-z, 0-9 and . _ + -, 1 to 64 characters, starting and ending with a letter or digit. Uppercase is lowercased. Omit it for a random 12-character name such as a2rz80xf1b49.
domainmachinemail.dev (the default) or a domain your organization has added.
display_nameSender name.
ttl_secondsMakes the inbox ephemeral.
ephemeraltrue without ttl_seconds means a one-hour TTL.
metadataString-to-string map.
pod_idCreate in a specific pod (organization keys only).
statusactive (default) or paused.
client_idIdempotency key. The same client_id returns the existing inbox with 200 instead of 201.

A taken address returns 409 conflict. Inbox-scoped keys cannot create inboxes.

Checking availability

The dashboard’s create dialog checks names as you type. You can call the same endpoint:

Terminal
curl "https://api.machinemail.dev/v0/inboxes/availability?username=support-agent&domain=machinemail.dev" \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY"
Response
{ "available": false, "email": "support-agent@machinemail.dev", "reason": "taken", "message": "support-agent@machinemail.dev is taken" }

It never fails for a bad name. reason is invalid_username, domain_not_found or taken. An available name returns { "available": true, "email": "..." }.

The shared domain and custom domains

Inboxes on machinemail.dev are free and need no setup. The shared domain is one namespace for everyone, first come first served: if another organization holds support@machinemail.dev, you get the same 409 as if you held it yourself. Each organization can hold up to 1,000 addresses on the shared domain. Mail sent to an address on the shared domain that nobody has created is not delivered; catch-all never applies there.

Some names are reserved on the shared domain and answer the same 409:

  • role and mail-system addresses (abuse, postmaster, hostmaster, admin, mailer-daemon, dmarc, …), including every mailbox a certificate authority accepts for domain validation;
  • addresses that look like MachineMail itself (support, sign-in, noreply, security, billing, api, mcp, anything containing machinemail, …);
  • well-known brands (paypal-support, apple-help, openai-billing, …), which on a shared domain could only be phishing.

Matching ignores case, a +tag, ./_/- and a trailing number, so no.reply, post_master+x and support-24 are reserved too. Ordinary agent names (agent-7, research-bot, invoice-parser) are not. Your own custom domains have no reserved names.

To use your own addresses, add a custom domain. You can create inboxes on it as soon as it is added, with no cap on how many. Mail only arrives once its MX record is in place.

Ephemeral inboxes

An ephemeral inbox has a time to live. MachineMail deletes it once expires_at passes, along with its messages, threads, drafts, raw MIME and attachments, and emits an inbox.expired event. The cleanup job runs every minute.

Terminal
curl https://api.machinemail.dev/v0/inboxes \
  -H "Authorization: Bearer $MACHINEMAIL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"ttl_seconds": 3600}'
Response
{
  "inbox_id": "a2rz80xf1b49@machinemail.dev",
  "email": "a2rz80xf1b49@machinemail.dev",
  "status": "active",
  "ephemeral": true,
  "ttl_seconds": 3600,
  "expires_at": "2026-10-09T10:00:15.841Z",
  "created_by": "api",
  "created_at": "2026-10-09T09:00:15.841Z",
  "updated_at": "2026-10-09T09:00:15.841Z"
}

Each new inbound message pushes expires_at forward to the time of arrival plus ttl_seconds, so an inbox in active use stays alive. Ephemeral inboxes suit sign-up flows and one-off verification codes; see OTP and sign-ups.

Change the TTL later with PATCH:

  • { "ttl_seconds": 86400 } makes the inbox ephemeral (or changes its TTL) and resets expires_at to now plus the new TTL.
  • { "ttl_seconds": null } or { "ephemeral": false } makes it permanent.

Catch-all inboxes

A verified custom domain with catch_all: true accepts mail to any address. The first message to a new address creates the inbox on the spot, with created_by: "catch_all". If the domain sets catch_all_ttl_seconds, those inboxes are ephemeral with that TTL. The local part must still be a valid username. See Domains.

Catch-all is how you give every agent run, test or customer a fresh address without an API call per address.

Updating, pausing and deleting

PATCH /v0/inboxes/{inbox_id} accepts display_name, status, metadata and ttl_seconds.

Pausing ("status": "paused") stops the inbox in both directions:

  • Sending from it returns 403 forbidden.
  • Inbound mail is refused and a message.rejected event is emitted with reason inbox_paused.

DELETE /v0/inboxes/{inbox_id} deletes the inbox and purges everything in it: messages, threads, drafts, raw MIME and attachments. This cannot be undone. Export first if you need a copy; see Search and export. Inbox-scoped keys cannot delete their own inbox.

Listing and searching

MethodPathNotes
GET/v0/inboxesPaginated with limit (default 50, max 500) and page_token.
GET/v0/inboxes/search?q=Case-insensitive match on the address or display name. Scans the newest 500 inboxes in scope and sets truncated: true if there were more.
GET/v0/inboxes/{inbox_id}One inbox.

An inbox.created event fires for every new inbox, including catch-all ones.

Next