Developers
TMailr API
A small HTTP API for disposable inboxes and developer sandboxes. Create an address, let your app send to it, and read what arrived with the one-time code already pulled out. JSON in, JSON out, over https://api.tmailr.com.
Overview
Everything is JSON over HTTPS. There are two ways to hold an address: a token, handed back when you create something and passed as ?token= or a Bearer header, and an API key, which owns many sandboxes at once and is what CI uses. The temporary inbox needs no account and no key; a key is what makes a run repeatable.
- Disposable inbox — one throwaway address that expires within the hour.
- Sandbox — a catch-all that lasts a year; every
+suffixlands in the same place, so one sandbox covers a whole test suite. - Extracted code — every message carries
otp, the verification code or sign-in link if there is one, so you never parse a body.
Authentication
Create a key once and keep the secret in your CI secrets. It is shown a single time; we store a hash and the first few characters, enough to recognise it in a list and not enough to use.
/v1/keyscreate a key (no auth; per-IP limited)POST /v1/keys
{ "name": "ci" }
-> 201
{ "key": { "id": "...", "name": "ci", "prefix": "tk_ab12" },
"secret": "tk_ab12..." } // shown onceSend the secret on every keyed call:
Authorization: Bearer tk_ab12...
/v1/keys/currentthe key's own details/v1/keys/currentrevoke itYou can also see, name and revoke keys in your account.
Quick start
The whole flow: make a sandbox, let the app under test send to any +suffixon it, then wait for what arrived. No key needed to try by hand; a key makes it repeatable.
# 1) an API key, kept in CI secrets. The secret is shown once.
curl -sX POST https://api.tmailr.com/v1/keys \
-H 'content-type: application/json' -d '{"name":"ci"}'
# -> { "key": { "prefix": "tk_..." }, "secret": "tk_..." }
# 2) a sandbox to capture this run's mail
curl -sX POST https://api.tmailr.com/v1/sandboxes \
-H 'authorization: Bearer tk_...' \
-H 'content-type: application/json' -d '{"name":"checkout"}'
# -> { "sandbox": { "id": "SID", "address": "checkout@etymail.com" },
# "catchAll": "checkout+anything@etymail.com" }
# 3) after your app sends mail, read what arrived
curl -s https://api.tmailr.com/v1/resources/SID/messages -H 'authorization: Bearer tk_...'
# -> { "messages": [ { "subject": "Verify", "otp": "123456" } ] }Disposable inboxes
A throwaway address with no account. The response carries a token; keep it and use it to read, extend or burn the inbox.
/v1/inbox-domainswhich domains you can ask for/v1/inboxescreate one (name and domain optional)POST /v1/inboxes
{ "name": "invoices", "domain": "kahmail.com" } // both optional
-> 201
{ "inbox": { "id": "...", "address": "invoices@kahmail.com",
"kind": "guest", "expiresAt": "2026-01-01T12:00:00Z" },
"token": "..." }/v1/inboxes/current/messages?token=…list, newest first/v1/inboxes/current/extend?token=…add another hour, up to 24/v1/inboxes/current?token=…burn it nowLive updates: GET /v1/inboxes/current/events?token=… is a Server-Sent Events stream that pushes each message as it lands.
Developer sandboxes
A catch-all built for a test suite: longer-lived than a throwaway inbox, and every address on it works without creating anything first. Owned by an API key so a suite that makes one per run does not have to keep a token per run.
/v1/sandboxesBearer key; name optionalPOST /v1/sandboxes Authorization: Bearer tk_...
{ "name": "checkout" }
-> 201
{ "sandbox": { "id": "SID", "address": "checkout@etymail.com" },
"token": "...",
"catchAll": "checkout+anything@etymail.com" }/v1/keys/current/resourcesevery sandbox this key owns/v1/resources/:id/messagesBearer key; newest 100Paging: when there are more, the response carries nextBefore; pass it back as ?before= and loop until it is null. Ask for a different size with ?limit=, up to 200.
Reading messages
Each message in a listing looks like this:
{
"id": "...",
"fromAddr": "no-reply@shop.com",
"fromName": "Shop",
"subject": "Verify your email",
"receivedAt": "2026-01-01T12:00:00.123Z",
"otp": "123456", // the extracted code, or null
"otpKind": "code", // "code" | "link" | null
"hasAttachments": false,
"sizeBytes": 4821
}/v1/messages/:id?token=…one message, with SPF/DKIM/DMARC results, text body, links and attachment list/v1/messages/:id/html?token=…sanitized HTML; images blocked unless ?images=1/v1/messages/:id/raw?token=…the original .eml/v1/messages/:id/attachments/:idx?token=…one attachment, served download-onlyForeign HTML is sanitized before you ever see it and served with a strict Content-Security-Policy; attachments are always Content-Disposition: attachment and dangerous file types are refused outright.
Webhooks
Rather than poll, point a webhook at your own URL and we POST each message as it lands, with the parsed body and any one-time code already extracted.
/v1/inboxes/current/webhook{ url }; returns a signing secret onceX-TMailr-Signature: t=<unix>,v1=<hex> hex = HMAC-SHA256(secret, "<t>.<raw body>")
Your endpoint is a public URL, so verify every delivery against the raw body before any JSON parsing, and reject a timestamp older than a few minutes. Both SDKs do this for you, with a five-minute window and a constant-time comparison:
import { TMailr } from '@tmailr/sdk';
// rawBody is the exact bytes received, before any JSON parsing
if (!TMailr.verifyWebhook(secret, req.headers['x-tmailr-signature'], rawBody)) {
return res.status(400).end();
}
const message = JSON.parse(rawBody); // now safe to useAn endpoint that keeps failing is switched off rather than retried forever; set it again to turn it back on, which is also how you rotate a leaked secret.
SDKs
Two thin, dependency-free clients over this API, so a test can send mail and read what arrived without reinventing the polling loop. Publishing to npm and PyPI is in progress; the intended install:
npm i @tmailr/sdk # TypeScript / Node (publishing soon) pip install tmailr # Python (publishing soon)
The core is createSandbox, waitForMessageand verifyWebhook (and their snake_casePython twins). See the Quick start above for the full loop.
Free tools API
The checks behind the free tools, callable directly. Deterministic DNS and parse observations, cached five minutes, metered per IP. No key needed.
/v1/tools/domain-check?domain=example.comMX, SPF with the ten-lookup count, DMARC policy, MTA-STS, TLS-RPT/v1/tools/check-address?email=user@gmial.comdisposable domains, role mailboxes and typos/v1/tools/analyze-headers{ headers } — delivery path, per-hop delays and auth resultsEach returns the underlying records and observations as JSON, the same data the tool pages render in plain English. See tmailr.com/tools for the interactive versions.
Rate limits & errors
Creation and abuse-prone endpoints are limited per IP; a message that arrives when the API is disrupted is refused at the SMTP stage with a temporary error, so senders retry and nothing is silently lost. Standard HTTP status codes throughout:
200 / 201 ok 400 malformed request 403 refused: a rate or abuse limit, or an address you may not use 404 no such resource — also what a bad key or token gets 409 that name is already taken 429 too many free-tool checks; wait and retry
A refusal never reveals whether something exists: an address that is not yours and one that never existed both answer 404. Separately, mail sent to an inbox that is already full gets a temporary452 at the SMTP stage, so the sender retries rather than losing it.
Questions or something missing? The developer sandbox page has a live create button, and the tools live at tmailr.com/tools.