api.email — full reference =========================== Disposable inboxes for automated tests and coding agents. Create a namespace, receive mail at any address under it (no per-address registration needed), read it back over REST or MCP. Terse version: https://api.email/llms.txt OpenAPI 3.1 document: https://api.email/openapi.json QUICKSTART ---------- POST https://api.email/v1/signup with an optional {"email": "..."} body (no auth) returns { api_key, namespace, example_address, ... }. Store api_key — it is shown only once. Any address at *@{namespace}.api.email then receives mail immediately, no prior registration of the address required. Connect an MCP agent to the hosted server (no install, no local process): claude mcp add --transport http api-email https://api.email/mcp Then call the signup tool, and pass the returned api_key as an "Authorization: Bearer ae_..." header on the connection to use the other tools. AUTHENTICATION -------------- Every /v1 endpoint except POST /v1/signup requires "Authorization: Bearer ae_..." with a key from POST /v1/signup or POST /v1/keys. Missing or invalid keys get 401 unauthorized. Same rule for MCP tools other than signup: the connection needs the header, or every authenticated tool returns { isError: true, ... "Authentication required" }. The Stripe webhook is authenticated separately, by signature. REST ENDPOINTS (relative to https://api.email) ----------------------------------------------- POST /v1/signup Create a free-plan account: a namespace and an API key. Rate limited to 10/hour per IP. auth: none - email (body, optional): Optional; used for login and billing receipts. curl -s -X POST https://api.email/v1/signup \ -H 'content-type: application/json' -d '{"email":"you@example.com"}' 201 { "account_id": "66b1...", "namespace": "swift-otter-42", "api_key": "ae_...", "plan": "free", "example_address": "x7f2@swift-otter-42.api.email", "docs": "https://api.email/llms.txt", "hint": "Store api_key now — it is shown only once. ..." } GET /v1/messages List received messages, newest first. auth: Authorization: Bearer ae_... - namespace (query, optional): Restrict to one namespace slug. - tag (query, optional): Exact match on the tag (local part before @). - tag_prefix (query, optional): Prefix match on tag; ignored if tag is set. - since (query, optional): Only messages received after this: epoch ms or ISO 8601. - limit (query, optional): 1-100, default 10. - offset (query, optional): Default 0. curl -s "https://api.email/v1/messages?tag=signup&limit=5" \ -H "Authorization: Bearer ae_..." 200 { "messages": [ { "id": "...", "tag": "signup", "subject": "Your code", "extracted": { "otp_codes": ["482913"], "links": [], "verification_links": [] }, ... } ], "count": 1, "limit": 5, "offset": 0 } GET /v1/messages/{id} Get one message by id. auth: Authorization: Bearer ae_... - id (path, required): Message id, from GET /v1/messages. - headers (query, optional): Pass "true" to include the raw email headers. curl -s https://api.email/v1/messages/66b2... -H "Authorization: Bearer ae_..." 200 { "message": { "id": "66b2...", "subject": "Your code", "text": "Your code is 482913", "extracted": { "otp_codes": ["482913"], "links": [], "verification_links": [] }, "attachments": [], ... } } DELETE /v1/messages/{id} Delete a message. auth: Authorization: Bearer ae_... - id (path, required): Message id, from GET /v1/messages. curl -s -X DELETE https://api.email/v1/messages/66b2... -H "Authorization: Bearer ae_..." 200 { "deleted": true } GET /v1/messages/{id}/attachments/{n} Download one attachment's bytes. auth: Authorization: Bearer ae_... - id (path, required): Message id. - n (path, required): Zero-based index into the message's "attachments" array. curl -s https://api.email/v1/messages/66b2.../attachments/0 \ -H "Authorization: Bearer ae_..." -o attachment.pdf 200 — raw bytes, original content-type and content-disposition headers GET /v1/wait Long-poll (up to 55s) for the next message matching the filter. auth: Authorization: Bearer ae_... - namespace (query, optional): Restrict to one namespace slug. - tag (query, optional): Exact match on the tag. - tag_prefix (query, optional): Prefix match on tag; ignored if tag is set. - since (query, optional): Default "now" — only mail that arrives after the call. - timeout (query, optional): Seconds, 1-55, default 30. curl -s "https://api.email/v1/wait?tag=signup&timeout=30" \ -H "Authorization: Bearer ae_..." 200 (message arrived) { "message": { "subject": "Your code", "extracted": { "otp_codes": ["482913"], ... } }, "timed_out": false } 200 (timed out) { "message": null, "timed_out": true, "hint": "No email arrived in 30s. Retry ..." } GET /v1/namespaces List namespaces on this account. auth: Authorization: Bearer ae_... (no parameters) curl -s https://api.email/v1/namespaces -H "Authorization: Bearer ae_..." 200 { "namespaces": [ { "slug": "swift-otter-42", "domain": "swift-otter-42.api.email", "created_at": "2026-08-01T12:00:00.000Z" } ] } POST /v1/namespaces Create another namespace, up to the plan's limit. auth: Authorization: Bearer ae_... - slug (body, optional): Omit for a random slug. curl -s -X POST https://api.email/v1/namespaces \ -H 'content-type: application/json' -H "Authorization: Bearer ae_..." -d '{"slug":"acme"}' 201 { "slug": "acme", "domain": "acme.api.email" } GET /v1/keys List API keys on this account (prefixes only, never the key itself). auth: Authorization: Bearer ae_... (no parameters) curl -s https://api.email/v1/keys -H "Authorization: Bearer ae_..." 200 { "keys": [ { "id": "66b3...", "prefix": "ae_AbCdEfGh", "created_at": "2026-08-01T12:00:00.000Z" } ] } POST /v1/keys Create an additional API key. auth: Authorization: Bearer ae_... (no parameters) curl -s -X POST https://api.email/v1/keys -H "Authorization: Bearer ae_..." 201 { "id": "66b3...", "api_key": "ae_...", "prefix": "ae_AbCdEfGh", "hint": "Store api_key now — it is shown only once." } DELETE /v1/keys/{id} Revoke an API key. Idempotent; refuses to revoke your only active key. auth: Authorization: Bearer ae_... - id (path, required): Key id, from GET /v1/keys. curl -s -X DELETE https://api.email/v1/keys/66b3... -H "Authorization: Bearer ae_..." 200 { "revoked": true } GET /v1/usage Current month email count and plan limits. auth: Authorization: Bearer ae_... (no parameters) curl -s https://api.email/v1/usage -H "Authorization: Bearer ae_..." 200 { "plan": "free", "month": "2026-08", "emails_received": 12, "limits": { "emails_per_month": 100, "namespaces": 1, "retention_days": 1 } } POST /v1/billing/checkout Start a Stripe Checkout session to upgrade to dev or pro. auth: Authorization: Bearer ae_... - plan (body, required): "dev" or "pro". - email (body, optional): Required only if the account has no email on file yet. curl -s -X POST https://api.email/v1/billing/checkout \ -H 'content-type: application/json' -H "Authorization: Bearer ae_..." -d '{"plan":"dev"}' 200 { "url": "https://checkout.stripe.com/c/pay/..." } POST /v1/billing/portal Open the Stripe billing portal to change or cancel a plan. auth: Authorization: Bearer ae_... (no parameters) curl -s -X POST https://api.email/v1/billing/portal -H "Authorization: Bearer ae_..." 200 { "url": "https://billing.stripe.com/p/session/..." } POST /v1/stripe/webhook Called by Stripe itself, not by API clients. Verified by the "stripe-signature" header. auth: none - stripe-signature (header, required): Set by Stripe; verified against STRIPE_WEBHOOK_SECRET. 200 { "received": true } MCP TOOLS (POST https://api.email/mcp, Streamable HTTP transport) ------------------------------------------------------------------- signup Create a free api.email account: a namespace plus an api_key. auth: none - email (optional): Optional; must be a valid address if given. > tool_call signup {} < { "account_id": "66b1...", "namespace": "swift-otter-42", "api_key": "ae_...", "plan": "free", "example_address": "x7f2@swift-otter-42.api.email", "hint": "Store api_key now — it is shown only once. ..." } create_inbox Generate a fresh unique address in your namespace. Any tag works without registration. auth: connection must carry a valid Authorization: Bearer ae_... header - tag (optional): Omit for a random tag. > tool_call create_inbox { "tag": "signup-flow" } < { "address": "signup-flow@swift-otter-42.api.email", "tag": "signup-flow", "namespace": "swift-otter-42", "hint": "Use this address ..., then call wait_for_message." } get_usage Current month email count and the plan limits for the authenticated account. auth: connection must carry a valid Authorization: Bearer ae_... header (no arguments) > tool_call get_usage {} < { "plan": "free", "month": "2026-08", "emails_received": 12, "limits": { "emails_per_month": 100, "namespaces": 1, "retention_days": 1 } } list_messages List received emails, newest first. Filter by tag, tag_prefix, namespace, since. auth: connection must carry a valid Authorization: Bearer ae_... header - namespace (optional): Restrict to one namespace slug. - tag (optional): Exact tag match. - tag_prefix (optional): Prefix match on tag. - since (optional): Epoch ms or ISO 8601. - limit (optional): 1-100, default 10. > tool_call list_messages { "tag": "signup-flow" } < { "messages": [ { "id": "...", "subject": "Your code", "extracted": { "otp_codes": ["482913"] }, ... } ], "count": 1 } get_message Fetch one email by id (from list_messages/wait_for_message), including extracted OTP codes and links. auth: connection must carry a valid Authorization: Bearer ae_... header - id (required): Message id. - include_headers (optional): Include raw email headers. > tool_call get_message { "id": "66b2..." } < { "message": { "id": "66b2...", "subject": "Your code", "extracted": { "otp_codes": ["482913"], "links": [], "verification_links": [] }, ... } } wait_for_message Long-poll for the next email matching the filter (up to 55s). since defaults to now. auth: connection must carry a valid Authorization: Bearer ae_... header - namespace (optional): Restrict to one namespace slug. - tag (optional): Exact tag match. - tag_prefix (optional): Prefix match on tag. - since (optional): Epoch ms or ISO 8601; defaults to now. - timeout (optional): Seconds, 1-55, default 30. > tool_call wait_for_message { "tag": "signup-flow", "timeout": 30 } < { "message": { "subject": "Your code", "extracted": { "otp_codes": ["482913"] }, ... }, "timed_out": false } ERRORS ------ REST error envelope: { "error": { "code", "message", "hint" } }, HTTP status matches the table below. MCP tool errors instead set isError: true with content[0].text = JSON.stringify({ error: message, hint }) — no numeric status and no "code" field, check isError. invalid_request (400): Request body or query parameters failed validation. — Check the field named in "message" and retry. unauthorized (401): Missing or invalid "Authorization: Bearer ae_..." header. — Get a key from POST /v1/signup or POST /v1/keys. not_found (404): The id or route does not exist, or belongs to another account. — Re-fetch the id from a list endpoint; ids are account-scoped. rate_limited (429): Too many requests to a rate-limited endpoint (e.g. signup: 10/hour/IP). — Wait for the window to pass, then retry. namespace_limit_reached (403): Your plan's namespace limit is already used. — Upgrade the plan, or reuse an existing namespace. slug_taken (409): The requested namespace slug is already in use. — Pick a different slug, or omit it for a random one. duplicate_key (409): API key hash collision (astronomically rare). — Retry the request. last_key (409): Attempted to revoke your only active API key. — Create a new key first, then revoke the old one. subscription_exists (400): The account already has an active subscription. — Use POST /v1/billing/portal instead. email_required (400): Checkout needs an email and the account has none on file. — Pass {"email": "..."} in the request body. no_billing_account (400): No Stripe customer exists yet for this account. — Call POST /v1/billing/checkout first. billing_unavailable (503): Billing is not configured on this server. — Server operator: set the STRIPE_* env vars. method_not_allowed (405): Wrong HTTP method on /mcp (only POST is supported). — Use POST for MCP requests. internal_error (500): Unexpected server error. — Retry once; if it persists, report it. LIMITS & RETENTION ------------------ Enforced at the SMTP layer: once monthly usage hits the plan's email cap, incoming mail is rejected at RCPT TO with SMTP 552 (nothing already stored is affected; delivery resumes next month or after an upgrade). Each message is capped at 10MB, also rejected with 552 if exceeded. Namespaces beyond the limit are rejected at creation with 403 namespace_limit_reached. Messages are deleted automatically once they pass retention_days. Check current usage with GET /v1/usage or the get_usage tool. free: 100 emails/month, 1 namespace(s), 1 day(s) retention dev: 5000 emails/month, 3 namespace(s), 7 day(s) retention pro: 25000 emails/month, 10 namespace(s), 30 day(s) retention BILLING ------- POST /v1/billing/checkout { "plan": "dev" | "pro", "email"? } -> { url } — Stripe Checkout session; completes the upgrade once Stripe's webhook fires. POST /v1/billing/portal -> { url } — Stripe billing portal (requires checkout to have run once already). Both 503 billing_unavailable if the server has no Stripe keys configured. POST /v1/stripe/webhook is called by Stripe itself, never by API clients.