Base URL
https://api.1mail.dev/v1 · read-only · no keys · machine-readable contract: /openapi.json (client + types + schemas generate from it).
Endpoints
GET/v1/inboxes/{address}/messages
List an inbox, newest first. Any syntactically valid address answers 200 — never-existed and all-expired are the same state.
limit— page size, default 100, max 1000.since_id— only messages strictly newer than this id: your poll cursor.older_than_id— page back from this id.wait— hold the call for mail instead of polling (see below).
Polling: the strong ETag is the newest message id — send If-None-Match and an unchanged poll answers 304 in one indexed read. Poll at most every 2 s.
Waiting: send wait=20000 with your since_id cursor and the call holds until mail newer than the cursor lands — a signup flow is one request instead of a loop (0..30000 ms; a bare wait without a cursor returns only future arrivals). On timeout you get 200 with an empty list: retry to keep waiting. A held call counts as one read.
curl "https://api.1mail.dev/v1/inboxes/test-runner-42@1mail.dev/messages?since_id=0198…&wait=20000"GET/v1/inboxes/{address}/messages/{id}
One full parsed message: text, unsanitized html (treat as untrusted!), curated headers, attachment metadata.
404 when the id is unknown or expired — the same state. The id is the UUID list returns.
curl "https://api.1mail.dev/v1/inboxes/test-runner-42@1mail.dev/messages/0190…-…/1.json"GET/v1/inboxes/{address}/messages/{id}/raw
The untouched original as message/rfc822, attachment disposition — never rendered on our origin.
Assert on the raw bytes your app sent.
curl -OJ "https://api.1mail.dev/v1/inboxes/test-runner-42@1mail.dev/messages/0190…/raw"Errors
RFC 9457 problem details (application/problem+json): 400 bad address syntax · 404 unknown/expired message · 429 rate limited · 500. Every response carries advisory RateLimit-Limit / -Remaining / -Reset; a 429 adds Retry-After. Honor them.
Recommended cadence
Waiting for mail? Send wait=20000 with your since_id cursor — the call holds until mail newer than the cursor lands (or answers an empty 200 after 20 s; retry to keep waiting). A held call counts as one read.
Polling instead? At most every 2 seconds — 304s replay your cached body, so re-read your own expires_at and stop when the message ages out.