Inboxes · MachineMail Docs

Creating an inbox

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"
}
Field Notes
inbox_id, email The address, lowercase. Use it in paths (URL-encode the @ if your client requires it).
display_name Shown as the sender name on outbound mail: Support agent <support-agent@machinemail.dev>.
status active or paused.
metadata Your own string-to-string map.
ephemeral, ttl_seconds, expires_at Set on inboxes that delete themselves. See below.
created_by api, dashboard, catch_all or agent.
client_id Your 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"}'
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();
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:

Field Notes
username The local part. a-z, 0-9 and . _ + -, 1 to 64 characters, starting and ending with a letter or digit. Uppercase is lowercased.
domain machinemail.dev (the default) or a domain your organization has added.
display_name Sender name.
ttl_seconds Makes the inbox ephemeral.
ephemeral true without ttl_seconds means a one-hour TTL.
metadata String-to-string map.
pod_id Create in a specific pod (organization keys only).
status active (default) or paused.
client_id Idempotency 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

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:

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.

Every organization’s slug is also reserved, for that organization alone: only the organization with the slug acme-agents can create acme-agents@machinemail.dev. Everyone else gets the same 409, and the availability check answers taken. Only that exact name is reserved. MachineMail creates it with the organization as its default inbox. Names of the form agent- followed by 16 or more hex digits are kept for the organizations of agents that sign up with an identity assertion, so other organizations cannot create them on the shared domain.

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.

Change the TTL later with PATCH:

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. 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:

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.

Listing and searching

Method Path Notes
GET /v0/inboxes Paginated 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.
GET /v0/inboxes/{inbox_id} One inbox.

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