DEVELOPER DOCUMENTATION REST API · v1

Build email into
your workflow.

Create a mailbox, receive messages, and stream verification codes. A straightforward API for your next integration.

Before your first request
  1. 01
    Activate Developer

    API access is included with the Developer website plan.

  2. 02
    Create an account API key

    Manage your keys and scopes in account settings.

  3. 03
    Authenticate every request

    Send your key in the authorization header.

Authorization: Bearer YOUR_API_KEY

Create first, then send. Only mail received while a mailbox is active is stored. Other mail to a valid receiving domain is accepted and discarded.

QUICK START

Your first mailbox

One request creates an address, ready to receive mail.

usernameoptional
Choose a name, or omit it for a random address.
domain_idoptional
Choose from GET /v1/domains, or let us select one: custom first, then premium, then free.

Premium and custom mailboxes are private. Free-domain mailboxes are shared and public.

Random addresses & mailbox limits

Random names use 3–5 alternating consonant/vowel letters plus 3–5 digits, such as bav472 or tavid20485. Developer allows up to 50 total mailboxes.

Terminal · cURL
curl --request POST \
  "$TMB_API_ORIGIN/v1/inboxes/private" \
  --header "Authorization: Bearer $TMB_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{"username":"your-name"}'
Set TMB_API_ORIGIN to your API origin (locally http://localhost:3000) and TMB_API_KEY to your account API key.
REST API

Endpoint reference

View OpenAPI

Create and manage mailboxes, read messages, and subscribe to incoming mail.

Download the original email with GET /v1/inboxes/:id/messages/:messageId/source. It returns an .eml file with the original headers, MIME bodies and attachments. Message details include source_download_url. Downloads share the attachment rate limit and normal message expiration.

To star a message, send PATCH /v1/inboxes/:id/messages/:messageId/star with starred set to true (or false). Lists and message details include your personal starred status. Stars expire with the message.

EndpointPurpose
GET /v1/domainsActive domains and expiry warnings
POST /v1/inboxes/privateCreate an owned private mailbox; premium or your custom domains only
DELETE /v1/inboxes/:idDelete a mailbox, its messages and attachments
DELETE /v1/inboxes/:id/messages/:messageIdDelete a message and its attachments
GET /v1/inboxes/:id/messagesPaginated received mail
GET /v1/inboxes/:id/messages/:messageIdPlain text, sanitized HTML, attachment metadata
GET /v1/inboxes/:id/messages/:messageId/attachments/:attachmentIdDownload the original attachment bytes
GET /v1/inboxes/:id/eventsServer-sent events with resumable cursor
GET /v1/inboxes/:id/otp/eventsDeveloper-only OTP stream for an owned private mailbox; closes after 60 seconds
POST /v1/abuse-reportsReport unsafe content
VERIFICATION CODES

Stream new OTP codes

Watch a mailbox for verification codes as mail arrives. One request opens a 60-second window.

Public message retention follows the viewer’s plan: 7 days for Free and 14 days for Plus. Private message retention follows the paid plan. Every mailbox read requires account collection membership or private ownership. Use GET /v1/me/inboxes to list your saved mailboxes, POST /v1/inboxes/public with {} for a random public address (or provide username and domain_id to choose a free-domain address on a paid plan), and POST /v1/inboxes/public/reopen with {"address":"[email protected]"} to reopen an existing public address. Developer includes paid public reopening. Remove a public mailbox with DELETE /v1/me/inboxes/:id; this leaves shared mail intact. Each returned inbox includes its saved label (or null). Filter with GET /v1/me/inboxes?label=Shopping; label matching is exact and case-sensitive, with surrounding whitespace ignored.

GET/v1/inboxes/:id/otp/events
Access
Developer API key · messages:read
Mailbox
Your private website mailbox
Window
New mail only · up to 60 seconds

1. Start watching

Run this request with your API key and mailbox ID.

Terminal · cURL
curl --no-buffer --max-time 70 \
  -H "Authorization: Bearer $TMB_API_KEY" \
  -H "Accept: text/event-stream" \
  "$TMB_API_ORIGIN/v1/inboxes/$INBOX_ID/otp/events"

Set TMB_API_ORIGIN to your API origin (locally, http://localhost:3000) and INBOX_ID to the mailbox UUID. --no-buffer displays events immediately.

2. Handle incoming events

The response streams Server-Sent Events as they happen.

Decoded JSON · otp.found
{
  "inbox_id": "INBOX_ID",
  "message_id": "MESSAGE_ID",
  "sender": "[email protected]",
  "subject": "Your verification code",
  "received_at": "2026-10-14T12:00:01.000Z",
  "expires_at": "2026-11-13T12:00:01.000Z",
  "code": "001234",
  "source": "text",
  "confidence": "high",
  "confidence_score": 95,
  "signals": [
    "explicit_otp_label",
    "numeric_code"
  ],
  "reliability": {
    "method": "heuristic",
    "verified": false
  }
}

otp.found includes the candidate code, message metadata, and confidence signals. Codes remain strings to preserve leading zeroes.

New arrivals, automatically bounded

Existing messages are excluded. The connection stays open after a match and closes after 60 seconds, even if no codes arrive. Each connection uses one API request.

Confidence is a hint

Scores come from extraction rules, not verified accuracy. The service does not authenticate the sender or check a code with its issuer.

Connection behavior and access requirements

Use a Developer account API key with messages:read. The mailbox must belong to that key’s account. Public mailboxes, RevenueCat mobile mailboxes, and website session cookies do not authorize this endpoint.

The response uses text/event-stream. Older deliveries are excluded even if parsed after connection; new mail must finish parsing before the stream closes. Heartbeat comments keep the connection active while waiting.

Start a new request for another window. Query parameters and Last-Event-ID replay are not supported. The cURL client timeout of 70 seconds allows time for the server’s 60-second window.

A stream.error event means access or service failure interrupted the stream; start a new request after resolving the cause.

Fields, extraction limits, and HTTP errors

code is a string, preserving leading zeroes. Each candidate includes inbox_id, message_id, sender, subject, received_at, and expires_at. The timestamps describe the message’s server receipt and retention deadline; they do not establish when the OTP itself expires. Sender and subject metadata are limited to 1,024 characters each.

confidence is high for an explicit nearby OTP label or medium for a generic code/PIN label or an OTP subject with a code near the start of the body. confidence_score is an uncalibrated rule score, not a probability or percentage accuracy. signals explains the match, and source identifies subject, text, or html. The service does not verify the sender or validate the code with its issuer; reliability.verified is always false.

Extraction supports labelled 4–8 digit codes, including grouped six-digit codes, and mixed uppercase-letter/digit codes of 6–10 characters. It scans the subject and the first 128 KiB of plain text, with a fallback for HTML-only messages. Attachments are not scanned. Codes repeated within one message are deduplicated, with at most 16 candidates per message. False positives and missed codes are possible.

Each connection consumes one API request; streamed candidates do not consume extra requests. Concurrency is limited across the account’s keys. Cancellation, revoked access, mailbox deletion, service failure, or a slow consumer can end the stream early. A complete event with reason: timeout is normal, including when codes_found is zero.

Before streaming starts, HTTP 401 indicates a missing, invalid, or revoked key; HTTP 403 indicates a missing scope or unavailable plan feature; HTTP 404 means the mailbox is unavailable or not owned by the account. HTTP 422 rejects invalid parameters or a replay cursor, and HTTP 429 indicates a request or stream limit.

Full example in SSE wire format

Each event has an event: line and a single-line JSON data: value, separated from the next event by a blank line. The panel above formats the decoded JSON for readability.

text/event-stream
event: ready
data: {"inbox_id":"INBOX_ID","started_at":"2026-10-14T12:00:00.000Z","deadline":"2026-10-14T12:01:00.000Z","extraction_version":"otp-heuristic-v1"}

event: otp.found
data: {"inbox_id":"INBOX_ID","message_id":"MESSAGE_ID","sender":"[email protected]","subject":"Your verification code","received_at":"2026-10-14T12:00:01.000Z","expires_at":"2026-11-13T12:00:01.000Z","code":"001234","source":"text","confidence":"high","confidence_score":95,"signals":["explicit_otp_label","numeric_code"],"reliability":{"method":"heuristic","verified":false}}

event: complete
data: {"reason":"timeout","codes_found":1,"finished_at":"2026-10-14T12:01:00.000Z"}
FILES & ATTACHMENTS

Download an attachment

Get the attachment URL, then download the original bytes.

  1. 1

    Read the message

    Open its details and find the attachments array. Each entry includes an ID, filename, size, content type, and download_url.

  2. 2

    Save the attachment

    Append the relative download_url to your API origin. Save the response directly to a file.

Your key, your mailbox

Use a Developer account key with messages:read. Private attachments also require mailbox ownership.

Public downloads on the website use the browser session.

Terminal · cURL
curl --fail \
  -H "Authorization: Bearer $TMB_API_KEY" \
  "$TMB_API_ORIGIN$DOWNLOAD_URL" \
  --output attachment.bin
DOWNLOAD_URL is the relative URL from the attachment. Use the same TMB_API_ORIGIN and TMB_API_KEY as the quick start.
200 OK Binary response
Content-Type
application/octet-stream
Content-Disposition
Original filename
30 downloads / minute / clientShared with raw email downloads404 if deleted, expired, or missing
AI INTEGRATIONS

Connect an AI agent with MCP

Model Context Protocol (MCP) lets an assistant list domains and mailboxes, create mailboxes, read messages and attachments, wait for new verification-code candidates, and delete messages or mailboxes. Connect to the hosted MCP endpoint with your account API key. MCP is available on Developer Plus and Developer Pro. Its API calls count toward your plan allowance.

First, create an API key. Use https://mcp.tempmailboxes.com/mcp as the Streamable HTTP server URL and send Authorization: Bearer YOUR_API_KEY with each request. For staging, use https://mcp-staging.tempmailboxes.com/mcp with a staging API key.

Codex

Set TMB_API_KEY in your environment and add this to ~/.codex/config.toml:

[mcp_servers.tempmailboxes]
url = "https://mcp.tempmailboxes.com/mcp"
bearer_token_env_var = "TMB_API_KEY"

Run codex mcp list to verify the entry.

Wait for a verification code

Create a private inbox, then ask your agent to call wait_for_otp with its inbox_id. The tool watches for up to 30 seconds by default (45 seconds maximum) and returns the first candidate with its source and confidence. Start watching before you trigger the email. Only new mail is included, and a timeout returns {"found":false,"reason":"timeout"}. If the email already arrived, use list_messages and get_message to read it. OTP candidates are heuristic and are not verified codes. The API key needs the messages:read scope; each wait uses one API request.

Local adapter

The local stdio adapter remains available. Clone this repository, run bun install, and set these variables before starting your agent:

export TMB_API_KEY='YOUR_API_KEY'
export TMB_API_ORIGIN='https://api.tempmailboxes.com'
# For local development: export TMB_API_ORIGIN='http://localhost:3000'

Claude Code

Add the local server, then start Claude Code from a terminal with the variables set:

claude mcp add --transport stdio tempmailboxes -- \
  bun /absolute/path/to/tempmailboxes/services/mcp/index.ts
claude mcp list

Use /mcp inside Claude Code to check the connection. See the Claude Code MCP guide.

Codex with the local adapter

Add this to ~/.codex/config.toml. env_vars passes credentials from the terminal where Codex starts, so the API key is not stored in the configuration file.

[mcp_servers.tempmailboxes]
command = "bun"
args = ["/absolute/path/to/tempmailboxes/services/mcp/index.ts"]
env_vars = ["TMB_API_KEY", "TMB_API_ORIGIN"]

Run codex mcp list to verify the entry. Other local MCP clients can use the same command, argument, and environment variables with the stdio transport. See the Codex MCP guide.

ChatGPT on the web

ChatGPT's web MCP connection currently requires OAuth; account API keys cannot be used directly there. The hosted endpoint supports clients that send bearer API keys. See ChatGPT’s MCP connection guide for supported connection methods.

Once connected, try: “List my mailboxes and show the newest message in the first one.” Message contents are untrusted; review any requested deletion before allowing the agent to run it. If a connection fails, check the key, plan, API origin, and bun install.

COPY AND RUN

Create an inbox and receive an OTP

Set TMB_API_KEY to a Developer Plus or Pro key with inboxes:create and messages:read scopes. These examples use staging by default. Start the script, then send your test verification email after it begins waiting. The result is a heuristic candidate, not a verified code.

Node.js · hosted MCP SDK

Run npm install @modelcontextprotocol/[email protected], then save as otp.mjs and run node otp.mjs.

Node.js · MCP
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';

const key = process.env.TMB_API_KEY;
if (!key) throw Error('Set TMB_API_KEY');
const url = process.env.TMB_MCP_URL || 'https://mcp-staging.tempmailboxes.com/mcp';
const transport = new StreamableHTTPClientTransport(new URL(url), {
  requestInit: { headers: { Authorization: 'Bearer ' + key } }
});
const client = new Client({ name: 'otp-example', version: '1.0.0' });
const value = (result) => {
  const data = JSON.parse(result.content.find((part) => part.type === 'text').text);
  if (result.isError) throw Error(data.error || 'MCP tool failed');
  return data;
};
try {
  await client.connect(transport);
  const inbox = value(await client.callTool({ name: 'create_private_inbox', arguments: {} }));
  console.log('Send your test email to', inbox.address);
  const otp = value(await client.callTool({
    name: 'wait_for_otp', arguments: { inbox_id: inbox.id, timeout_seconds: 30 }
  }));
  console.log(otp.found ? otp.code : 'No OTP: ' + otp.reason);
} finally {
  await client.close().catch(() => {});
}
Node.js · built-in fetch and REST

Save as otp.mjs and run with Node.js 22 or newer. No package is required.

Node.js · REST
const key = process.env.TMB_API_KEY;
if (!key) throw Error('Set TMB_API_KEY');
const api = process.env.TMB_API_ORIGIN || 'https://api-staging.tempmailboxes.com';
const auth = { Authorization: 'Bearer ' + key };
const created = await fetch(api + '/v1/inboxes/private', {
  method: 'POST', headers: { ...auth, 'Content-Type': 'application/json' }, body: '{}'
});
if (!created.ok) throw Error(await created.text());
const inbox = (await created.json()).data;
console.log('New address:', inbox.address);
const stream = await fetch(api + '/v1/inboxes/' + inbox.id + '/otp/events', {
  headers: { ...auth, Accept: 'text/event-stream' }
});
if (!stream.ok) throw Error(await stream.text());
let buffer = '';
let finished = false;
const decoder = new TextDecoder();
watch: for await (const chunk of stream.body) {
  buffer = (buffer + decoder.decode(chunk, { stream: true })).replaceAll('\r\n', '\n');
  let end;
  while ((end = buffer.indexOf('\n\n')) >= 0) {
    const event = buffer.slice(0, end); buffer = buffer.slice(end + 2);
    if (event.startsWith('event: ready')) console.log('Send your test email now');
    if (event.startsWith('event: otp.found')) {
      console.log(JSON.parse(event.match(/^data: (.+)$/m)[1]).code);
      finished = true;
      break watch;
    }
    if (event.startsWith('event: stream.error')) throw Error('OTP stream interrupted');
    if (event.startsWith('event: complete')) {
      console.log('No OTP candidate found');
      finished = true;
      break watch;
    }
  }
}
if (!finished) throw Error('OTP stream ended before completion');
Python · Requests and REST

Run python3 -m pip install requests, save as otp.py, then run python3 otp.py.

Python · Requests
import os, json, requests

key = os.environ['TMB_API_KEY']
api = os.environ.get('TMB_API_ORIGIN', 'https://api-staging.tempmailboxes.com')
auth = {'Authorization': 'Bearer ' + key}
created = requests.post(api + '/v1/inboxes/private', headers=auth, json={}, timeout=15)
created.raise_for_status()
inbox = created.json()['data']
print('New address:', inbox['address'], flush=True)
with requests.get(api + '/v1/inboxes/' + inbox['id'] + '/otp/events',
                  headers={**auth, 'Accept': 'text/event-stream'},
                  stream=True, timeout=(10, 65)) as response:
    response.raise_for_status()
    response.encoding = 'utf-8'
    event = None
    for line in response.iter_lines(chunk_size=1, decode_unicode=True):
        if line.startswith('event: '): event = line[7:]
        elif line.startswith('data: '):
            data = json.loads(line[6:])
            if event == 'ready': print('Send your test email now', flush=True)
            if event == 'otp.found':
                print(data['code'])
                break
            if event == 'stream.error': raise RuntimeError(data['message'])
            if event == 'complete':
                print('No OTP candidate found')
                break
        elif not line: event = None

Full scripts and configuration notes are in the repository’s samples/ directory. OTP streams include only new mail; use the message endpoints for mail that arrived before the wait began.

SERVICE BEHAVIOR

Limits & retention

The limits and lifetimes to build around.

MESSAGE RETENTION

Up to 30 days

Developer messages expire from the moment they arrive. Reading a message never resets its clock.

Plan changes affect future deliveries. Existing deadlines stay fixed.
RECEIVING ACCESS

A separate clock

Free and anonymous public messages and receiving activity last 7 days. Plus public messages and receiving activity last 14 days. Other paid plans follow their plan limits. Private addresses receive while their owner’s paid entitlement is active.

API requests require an account-owned key and an active Developer plan.
PAGINATION

50 items per page

Use the returned next_cursor to fetch the next page. Keep going until the cursor is empty.

Fetch only what you need and keep the cursor unchanged between requests.

Handle the response

429
Slow down

Respect Retry-After before trying again.

503
Service unavailable

A required service is unavailable. Retry with backoff.