API

Endpoint reference

Base URL https://api.tempy.email. Every route below (except GET /health) requires the X-Api-Key header. All bodies and responses are JSON.

Meta

Health, version, and public stats. Only /health is unauthenticated.

GET /health public

Liveness/readiness probe (the only unauthenticated endpoint).

Sample response
{
  "status": "ok",
  "redis": true,
  "smtp": true
}
GET /v1/version

Deployed build version.

Sample response
{
  "version": "1.4.2+abc1234"
}
GET /v1/stats

Aggregate public usage stats (per-domain, per-country counters).

Sample response
{
  "mailboxes_created": 1284322,
  "by_domain": { "17mur6.tempy.email": 90211 }
}

Account

Credits, plan, and the audit log behind the dashboard.

GET /v1/account

Current API key's account, plan, and credit balance.

Sample response
{
  "github_id": "1043221",
  "api_key_last4": "9f3a",
  "plan": "standard",
  "unlimited": false,
  "credits_remaining": 4200,
  "credits_expire_at": "2026-12-01T00:00:00Z",
  "created_at": "2026-01-14T08:22:00Z"
}
GET /v1/usage

Credit balance + paginated audit log.

ParamInTypeNotes
fromquerystring (date-time)
toquerystring (date-time)
limitqueryinteger (1–200, default 50)
offsetqueryinteger (default 0)
Sample response
{
  "credits_remaining": 4200,
  "credits_expire_at": "2026-12-01T00:00:00Z",
  "events": [
    {
      "id": "evt_01H...",
      "event": "mailbox.created",
      "address": "sleek.steel.amet@17mur6.tempy.email",
      "from": null,
      "to": null,
      "subject": null,
      "credits_used": 1,
      "created_at": "2026-08-25T12:00:00Z"
    }
  ]
}

Mailbox

Create and manage disposable mailboxes.

POST /v1/mailbox Spends 1 credit (free on unlimited plans).

Create a disposable mailbox.

ParamInTypeNotes
domainbodystring?Optional; must be one of the allowed random domains.
local_partbodystring?Optional custom local part (before the @). 2–64 chars: lowercase letters, digits, dots. Returns 409 (address_taken) if taken. Great for stable, deterministic test fixtures.
webhook_urlbodystring (uri)?Public http/https URL to POST new messages to (SSRF-validated).
webhook_formatbody"json" | "xml"Default json.
Sample response
{
  "email": "sleek.steel.amet@17mur6.tempy.email",
  "web_url": "https://tempy.email/i/17mur6/sleek.steel.amet",
  "created_at": "2026-08-25T12:00:00Z",
  "expires_at": "2026-08-25T12:10:00Z",
  "seconds_remaining": 600,
  "is_expired": false,
  "webhook_url": null,
  "webhook_format": null
}
GET /v1/mailbox/{address}

Mailbox status and remaining time.

ParamInTypeNotes
address*pathstringFull mailbox address, e.g. sleek.steel.amet@17mur6.tempy.email
Sample response
{
  "email": "sleek.steel.amet@17mur6.tempy.email",
  "web_url": "https://tempy.email/i/17mur6/sleek.steel.amet",
  "created_at": "2026-08-25T12:00:00Z",
  "expires_at": "2026-08-25T12:10:00Z",
  "seconds_remaining": 540,
  "is_expired": false
}
DELETE /v1/mailbox/{address}

Deactivate a mailbox early.

ParamInTypeNotes
address*pathstring
Sample response
{
  "success": true,
  "message": "Mailbox deactivated"
}
POST /v1/mailbox/{address}/extend

Extend a mailbox's TTL.

ParamInTypeNotes
address*pathstring
minutes*bodyinteger (1–44640, default 10)Minutes to extend by (max 31 days total).
Sample response
{
  "email": "sleek.steel.amet@17mur6.tempy.email",
  "expires_at": "2026-08-25T12:20:00Z",
  "seconds_remaining": 1140,
  "is_expired": false
}

Messages

Read and reply to messages in a mailbox.

GET /v1/mailbox/{address}/messages/wait

Long-poll until a matching message arrives — the CI anti-flake primitive. Returns 200 with the message, or 204 No Content on timeout.

ParamInTypeNotes
address*pathstring
timeoutqueryinteger (1–60, default 30)Seconds to block before giving up (204).
fromquerystring?Case-insensitive substring match on the From header.
subject_containsquerystring?Case-insensitive substring match on the subject.
sincequerystring (date-time)?Only match messages after this instant. Pass it when reusing a fixed address to avoid matching stale mail.
unreadqueryboolean?Only match unread messages.
Sample response
{
  "id": "7fae1b57-...",
  "from": "no-reply@github.com",
  "to": "qa-signup@17mur6.tempy.email",
  "subject": "Verify your email",
  "body_text": "Your code is 558213",
  "body_html": "<p>Your code is <b>558213</b></p>",
  "received_at": "2026-08-29T12:00:03Z",
  "direction": "inbound",
  "is_read": false
}
GET /v1/mailbox/{address}/messages

List messages for a mailbox, optionally filtered server-side.

ParamInTypeNotes
address*pathstring
fromquerystring?Substring match on From.
subject_containsquerystring?Substring match on subject.
sincequerystring (date-time)?Only messages after this instant.
unreadqueryboolean?Only unread messages.
Sample response
{
  "email": "sleek.steel.amet@17mur6.tempy.email",
  "count": 1,
  "messages": [
    {
      "id": "msg_01H...",
      "from": "no-reply@github.com",
      "to": "sleek.steel.amet@17mur6.tempy.email",
      "subject": "Verify your email",
      "body_text": "Your code is 123456",
      "received_at": "2026-08-25T12:01:30Z",
      "direction": "inbound",
      "is_read": false
    }
  ]
}
GET /v1/mailbox/{address}/messages/{messageId}

Get a single message, including sanitized HTML body.

ParamInTypeNotes
address*pathstring
messageId*pathstring
Sample response
{
  "id": "msg_01H...",
  "from": "no-reply@github.com",
  "to": "sleek.steel.amet@17mur6.tempy.email",
  "subject": "Verify your email",
  "body_text": "Your code is 123456",
  "body_html": "<p>Your code is <b>123456</b></p>",
  "received_at": "2026-08-25T12:01:30Z",
  "direction": "inbound",
  "is_read": true,
  "attachments": [
    { "filename": "logo.png", "content_type": "image/png", "size_bytes": 20481 }
  ]
}
POST /v1/mailbox/{address}/messages/{messageId}/read

Mark a message as read.

ParamInTypeNotes
address*pathstring
messageId*pathstring
Sample response
{
  "success": true,
  "message": "Marked as read"
}
POST /v1/mailbox/{address}/messages/{messageId}/reply

Send a plain-text reply to a message via SES.

ParamInTypeNotes
address*pathstring
messageId*pathstring
text*bodystringPlain-text reply body.
Sample response
{
  "success": true,
  "message_id": "0102018f..."
}
GET /v1/mailbox/{address}/messages/{messageId}/attachments/{filename}

Redirect (302) to a presigned S3 URL for an attachment.

ParamInTypeNotes
address*pathstring
messageId*pathstring
filename*pathstring
Sample response
HTTP/1.1 302 Found
Location: https://s3.amazonaws.com/tempy-attachments/...&X-Amz-Signature=...

Webhooks

When a mailbox is created with a webhook_url, the API POSTs to it on each new email.

POST {your webhook_url} newMessage

Delivered to a mailbox's configured webhook_url when a message arrives.

This is how the tempy.email web app receives live email: its server registers a webhook_url per mailbox, then fans the payload out to the browser over SSE. The POST body is a Message object (same shape as the single-message endpoint).

Request body (Message)
{
  "id": "msg_01H...",
  "from": "no-reply@github.com",
  "to": "sleek.steel.amet@17mur6.tempy.email",
  "subject": "Verify your email",
  "body_text": "Your code is 123456",
  "received_at": "2026-08-25T12:01:30Z",
  "direction": "inbound",
  "is_read": false
}

Error responses

Failures (400, 401, 402, 404, 500) return a JSON error object:

FieldDescription
codeStable machine-readable code to branch on, e.g. no_credits, mailbox_expired, mailbox_not_found, address_taken, rate_limited.
errorHuman-readable error message.
docsLink to the relevant docs page.
buyLink to buy more credits (on 402 responses).