Temp Mail API
A small REST API for disposable email inboxes. Create an inbox, poll it for messages, and read the verification code — the whole loop an end-to-end test needs. Built for Playwright, Cypress and CI pipelines that have to receive a real signup or one-time-passcode email without a shared, stateful mailbox.
Like the rest of TempBooth it is receive-only: the API can create inboxes and read what arrives, and can never send. Access is by API key during the current invite-only launch.
30-second quickstart
Every request carries your key as a bearer token. Base URL: https://temp-mailx.com/api/v1
# 1. Create an inbox (returns its address and a read token for just this inbox)
curl -s -X POST https://temp-mailx.com/api/v1/inboxes \
-H "Authorization: Bearer $TEMPBOOTH_KEY"
# -> { "inbox": { "id": "...", "address": "abc123@harlowfield.com", ... },
# "token": "tbi_..." }
# 2. Poll the inbox for messages (the inbox token is enough to read)
curl -s https://temp-mailx.com/api/v1/inboxes/$INBOX_ID/messages \
-H "Authorization: Bearer $INBOX_TOKEN"
# 3. Read one message — the verification code is extracted for you
curl -s https://temp-mailx.com/api/v1/inboxes/$INBOX_ID/messages/$MESSAGE_ID \
-H "Authorization: Bearer $INBOX_TOKEN"
# -> { "message": { "subject": "Your login code",
# "verificationCode": "483920", "text": "...", ... } }
Why a temporary-inbox API
Signup and login flows that email a code are the hardest part of an end-to-end test. Hard-coding a shared mailbox is flaky and leaks state between runs; polling a real IMAP account is slow and serialises your suite. A disposable inbox per test run is isolated, parallel-safe, and disposable by design — and because TempBooth extracts the code from the body for you, your test does not need brittle regexes over email HTML.
Official client libraries
You can call the API with any HTTP client, but the official libraries add the polling helpers most tests want — waitForMessage and waitForVerificationCode — so you are not writing sleep loops.
# JavaScript / TypeScript (Node 18+, Deno, Bun) npm install tempbooth # Python (3.8+, zero dependencies) pip install tempbooth
Both are MIT-licensed and open source.
Authentication
Send your credential in the Authorization header as Bearer <secret>. There are two kinds:
- API key (
tbk_live_…) — creates inboxes and can read any inbox it created. Keep it in an environment variable or CI secret, never in client-side code. - Inbox token (
tbi_…) — returned when you create an inbox, and scoped to that one inbox. Hand it to a single test run or worker without exposing your key.
Secrets are shown once and stored only as hashes — keep them somewhere safe. A revoked key stops working immediately.
Endpoints
| Method & path | Auth | Purpose |
|---|---|---|
| POST /api/v1/inboxes | key | Create an inbox; returns the inbox and a one-time inbox token. |
| GET /api/v1/inboxes/:id | key or token | Inbox status, address and counts. |
| GET /api/v1/inboxes/:id/messages | key or token | List received messages (newest first). |
| GET /api/v1/inboxes/:id/messages/:messageId | key or token | Full message: text, sanitised HTML, and the extracted verificationCode. |
| GET /api/v1/inboxes/:id/events | key or token | Server-Sent Events stream of new-message notifications. |
| GET /api/v1/domains | public | The domains the API issues addresses on. No key required. |
Optionally pass {"domainId":"…"} to POST /inboxes to choose a specific domain from GET /domains; otherwise one is picked for you. The full machine-readable contract is published as an OpenAPI 3.1 spec.
Recipe: Playwright
Create an inbox, drive your signup with its address, then wait for the code:
import { test, expect } from '@playwright/test';
import { TempBooth } from 'tempbooth';
const tb = new TempBooth({ apiKey: process.env.TEMPBOOTH_KEY! });
test('sign up with email verification', async ({ page }) => {
const { inbox } = await tb.createInbox();
await page.goto('https://your-app.example/signup');
await page.getByLabel('Email').fill(inbox.address);
await page.getByRole('button', { name: 'Create account' }).click();
const code = await inbox.waitForVerificationCode({ timeoutMs: 30_000 });
await page.getByLabel('Verification code').fill(code);
await page.getByRole('button', { name: 'Verify' }).click();
await expect(page.getByText('Welcome')).toBeVisible();
});
Recipe: Cypress
Cypress runs specs in the browser, so do the API calls in a Node task and hand the results back to the spec. In cypress.config.js:
const { TempBooth } = require('tempbooth');
const tb = new TempBooth({ apiKey: process.env.TEMPBOOTH_KEY });
module.exports = defineConfig({
e2e: {
setupNodeEvents(on) {
on('task', {
tbCreateInbox: () =>
tb.createInbox().then(r => ({ id: r.inbox.id, address: r.inbox.address, token: r.token })),
tbWaitCode: ({ id, token }) =>
tb.waitForVerificationCode(id, { token, timeoutMs: 30000 }),
});
},
},
});
Then in the spec:
it('verifies email', () => {
cy.task('tbCreateInbox').then((inbox) => {
cy.visit('/signup');
cy.get('input[name=email]').type(inbox.address);
cy.contains('Create account').click();
cy.task('tbWaitCode', { id: inbox.id, token: inbox.token }).then((code) => {
cy.get('input[name=code]').type(code);
cy.contains('Verify').click();
cy.contains('Welcome').should('be.visible');
});
});
});
Live events instead of polling
Rather than poll, open an SSE stream and react when mail arrives. Set the same bearer header (a browser EventSource can't, so use a fetch- or library-based SSE client):
curl -N https://temp-mailx.com/api/v1/inboxes/$INBOX_ID/events \
-H "Authorization: Bearer $INBOX_TOKEN"
# event: message-received
# data: {"id":"..."} <- then GET the message
Limits & honest constraints
Read these before you build against the API — they are deliberate, not bugs:
- Receive-only. There is no send endpoint and never will be. The API reads mail that arrives; it cannot originate mail, reply, or forward.
- Inboxes and messages expire. An inbox stops accepting mail after its
expiresAt, and stored content is deleted atcontentDeletedAt(a little later). Treat both as ephemeral — read the code within the test run; do not expect a message to still be there an hour later. - Disposable-domain rejections. The API issues addresses on rotating disposable domains. Some sites deliberately block signups from known disposable-mail domains; if the target does that, mail will simply never arrive (the inbox stays empty and your
waitFor…call times out). That is the destination's policy, not an API failure. For flows you control, allow-list the API's domains in your own app. - Rate limits & quota. Each key has a per-minute request limit and a rolling 24-hour inbox-creation quota, plus per-IP and global backstops. Over a limit you get
429with aRetry-Afterheader. All errors share one shape:{ "error": "<code>", "message": "…" }. - Code extraction is best-effort.
verificationCodeis extracted heuristically from the body. It handles the common phrasings, but if a sender wraps the code unusually it may benull— fall back to readingtext/htmlyourself. - Pausing. Inbox creation can be paused by the operator during an incident; you will get a
503withapi_paused. Retry later.
Getting a key
Keys are issued manually during the invite-only launch — there is nothing to sign up for and no email to hand over on the site. Request one at abuse@tempbooth.com with a sentence about what you're building, and you'll get a key and its quota.