Up to 30 days
Developer messages expire from the moment they arrive. Reading a message never resets its clock.
Create a mailbox, receive messages, and stream verification codes. A straightforward API for your next integration.
API access is included with the Developer website plan.
Manage your keys and scopes in account settings.
Send your key in the authorization header.
Authorization: Bearer YOUR_API_KEYCreate first, then send. Only mail received while a mailbox is active is stored. Other mail to a valid receiving domain is accepted and discarded.
One request creates an address, ready to receive mail.
usernameoptionaldomain_idoptionalGET /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 names use 3–5 alternating consonant/vowel letters plus 3–5 digits, such as bav472 or tavid20485. Developer allows up to 50 total mailboxes.
curl --request POST \
"$TMB_API_ORIGIN/v1/inboxes/private" \
--header "Authorization: Bearer $TMB_API_KEY" \
--header "Content-Type: application/json" \
--data '{"username":"your-name"}' TMB_API_ORIGIN to your API origin (locally http://localhost:3000) and TMB_API_KEY to your account API key.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.
| Endpoint | Purpose |
|---|---|
GET /v1/domains | Active domains and expiry warnings |
POST /v1/inboxes/private | Create an owned private mailbox; premium or your custom domains only |
DELETE /v1/inboxes/:id | Delete a mailbox, its messages and attachments |
DELETE /v1/inboxes/:id/messages/:messageId | Delete a message and its attachments |
GET /v1/inboxes/:id/messages | Paginated received mail |
GET /v1/inboxes/:id/messages/:messageId | Plain text, sanitized HTML, attachment metadata |
GET /v1/inboxes/:id/messages/:messageId/attachments/:attachmentId | Download the original attachment bytes |
GET /v1/inboxes/:id/events | Server-sent events with resumable cursor |
GET /v1/inboxes/:id/otp/events | Developer-only OTP stream for an owned private mailbox; closes after 60 seconds |
POST /v1/abuse-reports | Report unsafe content |
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.
/v1/inboxes/:id/otp/eventsmessages:readRun this request with your API key and mailbox ID.
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.
The response streams Server-Sent Events as they happen.
{
"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.
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.
Scores come from extraction rules, not verified accuracy. The service does not authenticate the sender or check a code with its issuer.
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.
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.
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.
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"}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.
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.
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.
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'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 listUse /mcp inside Claude Code to check the connection. See the Claude Code MCP guide.
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'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.
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.
Run npm install @modelcontextprotocol/[email protected], then save as otp.mjs and run node otp.mjs.
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(() => {});
}Save as otp.mjs and run with Node.js 22 or newer. No package is required.
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');Run python3 -m pip install requests, save as otp.py, then run python3 otp.py.
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 = NoneFull 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.
The limits and lifetimes to build around.
Developer messages expire from the moment they arrive. Reading a message never resets its 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.
Use the returned next_cursor to fetch the next page. Keep going until the cursor
is empty.
429 Respect Retry-After before trying again.
503 A required service is unavailable. Retry with backoff.