Testing & CI
Deterministic inboxes for your CI and end-to-end suites. Real delivery, instant OTP reads, no shared mailbox and no flaky waits.
The pattern
- Spin up a fresh inbox per runUse your account key and a unique prefix: a UUID fragment, the build number, the commit SHA. Isolation is what removes flakiness.
- Drive your flowHave the app under test sign up or request a code using that address. Nothing is mocked; the mail really travels.
- Poll for the codeCall /latest every 2–3s until otp_codeis non-null, with a hard timeout so a missing mail fails loudly.
- Tear downDelete the inbox, or let retention purge it. Both work; deleting frees the slot immediately.
Full working code
Recipes has ready pytest fixtures, a Playwright spec and a GitHub Actions job you can paste as-is.
The four calls
# 1. isolated, short-lived inbox for this run
curl -X POST https://mailflat.net/api/v1/inboxes \
-H "X-API-Key: mf_live_…" -H "Content-Type: application/json" \
-d '{"prefix":"ci-run-8f3","retention_hours":2}'
# → { "address": "ci-run-8f3@a7f2c.mailflat.net" }
# 2. (your test triggers the signup that sends mail here)
# 3. poll until the code lands
curl https://mailflat.net/api/v1/inboxes/ci-run-8f3@a7f2c.mailflat.net/latest \
-H "X-API-Key: mf_live_…"
# → { "email": { "subject": "Your code", "otp_code": "482913" } }
# 4. burn it now, or let retention purge it
curl -X DELETE https://mailflat.net/api/v1/inboxes/ci-run-8f3@a7f2c.mailflat.net \
-H "X-API-Key: mf_live_…"
Line by linewhat each step of this curl example does
- prefix: ci-run-8f3
- Unique per run so parallel shards never collide.
- retention_hours: 2
- Old runs purge themselves even if teardown never runs.
- /latest
- One message, cheap to poll. Stop as soon as otp_code is set.
- DELETE
- Optional but tidy, keeps the agent inbox list readable.
| Endpoint | Use in CI |
|---|---|
| POST /api/v1/inboxes | One per test or per run |
| GET /api/v1/inboxes/{addr}/latest | The polling call |
| GET /api/v1/inboxes/{addr}/messages | Assert on the whole thread, not just the code |
| POST /api/v1/inboxes/{addr}/send | Test your own inbound handling by sending mail at it |
| DELETE /api/v1/inboxes/{addr} | Teardown |
| DELETE /api/agent/inboxes?group=ci | Batch teardown of a whole tagged group (session token) |
Keeping suites fast and honest
Parallel shardsSafe, no bot detection and no shared mailbox
We do not fingerprint or throttle by bot heuristics, so shards can run flat out. The only limit that matters is 500 requests/hour per IP. With 2–3s polling and a real timeout, a suite of hundreds of tests stays well inside it.
Prefer webhooks for long suitesStop polling entirely
If your runner can accept an inbound request, register a webhook and let the message come to you. It removes the polling budget question completely and reacts instantly.
Do not enable encryption on a CI accountServers cannot extract OTPs from ciphertext
End-to-end encryption is designed for human privacy. With it on, otp_codecomes back empty and your assertions will fail for a reason that looks like a delivery bug. Inboxes created through the agent API are always plain text, but the account-level toggle still affects inboxes made in the web pool.
From the dashboard insteadTesting tab, no code
Open Testing → Test inboxes, pick a retention, create an inbox and watch mail arrive with a copyable OTP chip (4-second auto-refresh). Tear down removes the whole batch. Use the row's Send test email button for a quick outbound check.