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.
/health public Liveness/readiness probe (the only unauthenticated endpoint).
{
"status": "ok",
"redis": true,
"smtp": true
}/v1/version Deployed build version.
{
"version": "1.4.2+abc1234"
}/v1/stats Aggregate public usage stats (per-domain, per-country counters).
{
"mailboxes_created": 1284322,
"by_domain": { "17mur6.tempy.email": 90211 }
}Account
Credits, plan, and the audit log behind the dashboard.
/v1/account Current API key's account, plan, and credit balance.
{
"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"
}/v1/usage Credit balance + paginated audit log.
| Param | In | Type | Notes |
|---|---|---|---|
| from | query | string (date-time) | — |
| to | query | string (date-time) | — |
| limit | query | integer (1–200, default 50) | — |
| offset | query | integer (default 0) | — |
{
"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.
/v1/mailbox Spends 1 credit (free on unlimited plans).Create a disposable mailbox.
| Param | In | Type | Notes |
|---|---|---|---|
| domain | body | string? | Optional; must be one of the allowed random domains. |
| local_part | body | string? | 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_url | body | string (uri)? | Public http/https URL to POST new messages to (SSRF-validated). |
| webhook_format | body | "json" | "xml" | Default json. |
{
"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
}/v1/mailbox/{address} Mailbox status and remaining time.
| Param | In | Type | Notes |
|---|---|---|---|
| address* | path | string | Full mailbox address, e.g. sleek.steel.amet@17mur6.tempy.email |
{
"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
}/v1/mailbox/{address} Deactivate a mailbox early.
| Param | In | Type | Notes |
|---|---|---|---|
| address* | path | string | — |
{
"success": true,
"message": "Mailbox deactivated"
}/v1/mailbox/{address}/extend Extend a mailbox's TTL.
| Param | In | Type | Notes |
|---|---|---|---|
| address* | path | string | — |
| minutes* | body | integer (1–44640, default 10) | Minutes to extend by (max 31 days total). |
{
"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.
/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.
| Param | In | Type | Notes |
|---|---|---|---|
| address* | path | string | — |
| timeout | query | integer (1–60, default 30) | Seconds to block before giving up (204). |
| from | query | string? | Case-insensitive substring match on the From header. |
| subject_contains | query | string? | Case-insensitive substring match on the subject. |
| since | query | string (date-time)? | Only match messages after this instant. Pass it when reusing a fixed address to avoid matching stale mail. |
| unread | query | boolean? | Only match unread messages. |
{
"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
}/v1/mailbox/{address}/messages List messages for a mailbox, optionally filtered server-side.
| Param | In | Type | Notes |
|---|---|---|---|
| address* | path | string | — |
| from | query | string? | Substring match on From. |
| subject_contains | query | string? | Substring match on subject. |
| since | query | string (date-time)? | Only messages after this instant. |
| unread | query | boolean? | Only unread messages. |
{
"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
}
]
}/v1/mailbox/{address}/messages/{messageId} Get a single message, including sanitized HTML body.
| Param | In | Type | Notes |
|---|---|---|---|
| address* | path | string | — |
| messageId* | path | string | — |
{
"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 }
]
}/v1/mailbox/{address}/messages/{messageId}/read Mark a message as read.
| Param | In | Type | Notes |
|---|---|---|---|
| address* | path | string | — |
| messageId* | path | string | — |
{
"success": true,
"message": "Marked as read"
}/v1/mailbox/{address}/messages/{messageId}/reply Send a plain-text reply to a message via SES.
| Param | In | Type | Notes |
|---|---|---|---|
| address* | path | string | — |
| messageId* | path | string | — |
| text* | body | string | Plain-text reply body. |
{
"success": true,
"message_id": "0102018f..."
}/v1/mailbox/{address}/messages/{messageId}/attachments/{filename} Redirect (302) to a presigned S3 URL for an attachment.
| Param | In | Type | Notes |
|---|---|---|---|
| address* | path | string | — |
| messageId* | path | string | — |
| filename* | path | string | — |
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.
{your webhook_url} newMessageDelivered 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).
{
"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:
| Field | Description |
|---|---|
| code | Stable machine-readable code to branch on, e.g. no_credits, mailbox_expired, mailbox_not_found, address_taken, rate_limited. |
| error | Human-readable error message. |
| docs | Link to the relevant docs page. |
| buy | Link to buy more credits (on 402 responses). |