Getting started

Quickstart

tempy.email gives you disposable inboxes on demand — throwaway mailboxes you spin up from your test suite or backend to test signup flows, drive E2E suites, and verify the emails your app actually sends. One HTTP API, one key, and a waitForMessage call that blocks until your email lands — no polling, no flake.

What it's for

  • Automate signup / email-verification flows without a real inbox.
  • Assert on transactional email in E2E and QA pipelines.
  • Wait for a specific message with waitForMessage(), or receive mail server-to-server over webhooks if you have a public endpoint.
  • Reply to messages programmatically to test round-trips.

Get an API key

Sign in with GitHub or Google to create an account and issue a key. New accounts start free with 10 creditscreating a mailbox spends 1 credit; reads, waits, replies and deletes are always free.

Get an API key

Fastest path: the TypeScript SDK

The official SDK wraps the API with a typed client, the waitForMessage long-poll, and helpers like extractCode() / extractLink(). Here's a complete Playwright test — landing to green in one file:

shell
npm i @tempy/api-client   # or: pnpm add @tempy/api-client
signup.spec.ts
import { test, expect } from '@playwright/test';
import { createTempyClient, extractCode } from '@tempy/api-client';

const tempy = createTempyClient({ apiKey: process.env.TEMPY_KEY! });

test('user can sign up and verify their email', async ({ page }) => {
  // a fresh, unique inbox for this test run
  const inbox = await tempy.createMailbox();

  await page.goto('/signup');
  await page.getByLabel('Email').fill(inbox.email);
  await page.getByRole('button', { name: 'Create account' }).click();

  // block until the verification email lands — or fail after 30s
  const msg = await tempy.waitForMessage(inbox.email, {
    subjectContains: 'verify',
    timeout: 30
  });
  expect(msg, 'no verification email arrived').not.toBeNull();

  // pull the 6-digit code straight out of the email body
  await page.getByLabel('Code').fill(extractCode(msg!)!);
  await expect(page.getByText('Welcome')).toBeVisible();
});

waitForMessage() returns the message as soon as one matches your filter, or null when the timeout elapses — so your test fails deterministically instead of hanging. Pass since (a timestamp) when you reuse a fixed address, so a stale message from a previous run can't produce a false positive.

Authentication (plain HTTP)

No SDK? Every endpoint except GET /health requires your key in the X-Api-Key header. The API is server-to-server only — never ship your key to a browser or mobile client. Proxy through your own backend, exactly like the tempy.email web app does.

shell
# set your key once
export TEMPY_KEY="sk_live_..."

curl https://api.tempy.email/v1/account \
  -H "X-Api-Key: $TEMPY_KEY"

Base URL is https://api.tempy.email. The legacy alias https://tempy.email/api is kept permanently for older integrations.

curl walkthrough

The full loop with plain curl: create a mailbox, wait for a message, then reply to it.

1. Create a mailbox

shell
curl -X POST https://api.tempy.email/v1/mailbox \
  -H "X-Api-Key: $TEMPY_KEY" \
  -H "Content-Type: application/json" \
  -d '{"local_part": "qa-signup"}'   # optional: pick a stable address
200 · application/json
{
  "email": "qa-signup@17mur6.tempy.email",
  "web_url": "https://tempy.email/i/17mur6/qa-signup",
  "expires_at": "2026-08-29T12:10:00Z",
  "seconds_remaining": 600
}

Save the email value — that's the {address} path param for every following call. Point your signup form at it.

2. Wait for the message

Long-poll the mailbox: this call blocks until a message matching your filter arrives, then returns it. Filters (subject_contains, from, since, unread) are applied server-side. On timeout you get 204 No Content.

shell
# block up to 30s for the verification email, filtered server-side
curl -G https://api.tempy.email/v1/mailbox/$ADDRESS/messages/wait \
  -H "X-Api-Key: $TEMPY_KEY" \
  --data-urlencode "subject_contains=verify" \
  --data-urlencode "timeout=30"
# → 200 with the message, or 204 No Content if the timeout elapses
200 · application/json
{
  "id": "7fae1b57-...",
  "from": "no-reply@github.com",
  "subject": "Verify your email",
  "body_text": "Your code is 558213",
  "received_at": "2026-08-29T12:00:03Z"
}

Prefer push? Pass webhook_url when you create the mailbox and tempy POSTs each new message to it as JSON (HMAC-signed). Handy for always-on backends — but in CI, where your runner has no public URL, wait is the tool. You can also GET /messages (the full list, same filters) if you'd rather pull.

3. Reply to a message

shell
curl -X POST \
  https://api.tempy.email/v1/mailbox/$ADDRESS/messages/$MSG_ID/reply \
  -H "X-Api-Key: $TEMPY_KEY" \
  -H "Content-Type: application/json" \
  -d '{"text": "Thanks, got the code."}'
200 · application/json
{ "success": true, "message_id": "0102018f..." }

Error handling

Every error returns a stable machine-readable code alongside a human error message, so your tests branch on the reason, not a string. For example no_credits (402, includes a buy link), mailbox_expired vs mailbox_not_found (404 — a too-short TTL is not the same as a bug), address_taken (409), and rate_limited (429). The SDK throws a TempyApiError carrying { status, code, error }.

Next steps

See the full endpoint reference for every route, parameter, and response shape, plus the incoming-message webhook payload.