Testing Email Flows with a Disposable Inbox (Developer Guide)
By Denis · Published 11 Sep 2026. Updated 22 Sep 2026.
Almost every app sends email as a side effect of something the user did: signup confirmation, one-time code, password reset, shipping notice. Testing those flows by hand means a real inbox, a lot of tab-switching, and mail you'll be deleting for weeks. Testing them with a disposable inbox means the email lands somewhere your test can read it and disappears on its own.
There are two halves to the problem, and they need different tools: reading mail your app's signup flow sends (verification, OTP, reset), and capturing mail your app sends over SMTP (transactional mail in general). Tempx has first-class paths for both.
Half one: the signup/OTP/reset flow
The pattern is always the same: run the flow with a disposable address, then wait for the message and assert on it. The "wait for the message" part.
The supported way to automate the read side is the MCP server. Among its tools, the one that matters here is wait_for_email, a blocking call that hangs on the server until a matching message arrives, instead of you writing a poll-sleep-poll loop:
// any MCP client (Claude Code, ChatGPT, or a plain streamable-HTTP client)
// after pairing once in the browser:
const msg = await tools.wait_for_email({
inbox: "test-signup-8f3@tempx.uk",
subject_contains: "Confirm your account",
timeout_seconds: 60,
});
// msg.subject / msg.preview carry the content; assert on them
For scripts you own that want raw JSON, Tempx also allows authenticated reads of a private inbox with its bearer token:
curl -s -H "Authorization: Bearer <private-inbox token>" \
"https://tempx.uk/api/inbox"
# => { "address": "...", "retentionHours": 1, "messages": [... ] }
One caution: this REST surface is Tempx's internal product contract (it powers the web app and the extensions), not a public API, fine for your own scripts, but the stable integration surfaces are SMTP capture, MCP and the Resend-compatible adapter. And read it politely: the rate limit is 240 reads per minute per client, which a tight poll loop in a big CI fan-out can hit.
Inbox choice comes next. Use private inboxes for anything asserting on real content, a named inbox is readable (and clearable) by anyone who guesses the name, and a flaky test that fails because a stranger deleted the mail is a bad afternoon. And use a fresh address per test run: parallel runs sharing one inbox is the classic source of "sometimes two codes arrive and my regex grabs the wrong one."
Half two: capturing what your app sends
For mail your application sends over SMTP, don't read inboxes at all, point the app at the capture gateway and never let test mail reach a real mailbox:
# any SMTP client, any language - staging or CI config
host: smtp.tempx.uk
port: 587
security: STARTTLS
username: <from /account#smtp> # shown once, validated live by the gateway
password: <shown once>
Everything the app "sends" is captured into the inbox paired with those credentials (same parsing, same sanitization as any other message) and the gateway never relays. A misconfigured test run cannot email a real person. That property is worth more than it sounds: it means you can run the full signup flow of a production build against staging credentials without a checklist of "make sure MAIL_HOST is overridden."
If your stack already speaks Resend, an adapter exists for that too: point the SDK's base URL at https://tempx.uk/resend with a Tempx key, and sends get captured instead of delivered (30 sends per 5 minutes per key).
What to assert
"Email arrived" is the weakest useful assertion. In practice the checks that catch real bugs:
- The link points at the right host. Verification links that point at
localhost(or at production from a staging build) are the classic environment-variable bug, and it's invisible until you look at the actual URL in the mail. - The one-time code is present and in the expected format (length, charset), and still valid when used.
- The reset flow invalidates previous links, request twice, use the first one, expect rejection.
- The plain-text part exists and isn't a placeholder, clients that render text-only still exist.
- Delivery timing, if the OTP expires in 5 minutes, arrival within 60 seconds is part of the contract.
wait_for_emailreturns arrival timing you can assert on.
For inspecting what a failing send contained, the MIME decoder and header analyzer tools answer most "what did the app send" questions without re-running anything.
Things that will still go wrong
- Blocklists are irrelevant here, you control the sender. This is testing, not signup evasion: your own app accepts any recipient domain you point it at. The blocklist problem only appears when you're signing up to someone else's site.
- Retention. Private inbox mail is deleted 1 hour after arrival. A test that inspects mail "later" needs to inspect it now, or use a named inbox's 7-day window when the content is non-sensitive.
- Parallelism. Fresh inbox per worker; one shared poller per process if you're anywhere near the read limit.
- HTML assertions. The inbox sanitizes HTML for display; assert against the raw parts (MIME decoder) rather than the rendered preview if markup itself is the thing under test.
The per-framework wiring (Playwright fixtures, Cypress commands, plain CI scripts) is written up in the Playwright guide and its siblings, the shape above is the part that doesn't change between them.
Graduating checks into CI
Every assertion above starts life as an exploratory check and earns permanence by catching something. The graduation path keeps suites healthy: when a scenario proves valuable (expired-code rejection, single-use links, arrival timing) freeze it into a scripted test with a fresh inbox per run, versioned with the code, running on every commit. The long tail stays exploratory: the fifteenth odd state, the one-off audit, the "what happens if" evening. Scripts guard the known; agents and humans hunt the unknown.
The lane discipline matters as much as the tests: email specs are slow, so they live in a scheduled lane (nightly, pre-release) with generous timeouts, while the every-commit lane stays sub-minute. A suite that mixes both becomes a suite people bypass, and a bypassed suite protects nothing.
Flaky email tests: the three real causes
A flaking email suite almost always hits one of three things, each with a distinct fix. Shared inboxes, two runs reading one address interleave codes; fix: fresh address per run, no exceptions. Imprecise matching, a subject regex that also matches the marketing mail, or a digit-run regex that reads the order number; fix: match on sender plus subject fragment, anchor extraction to the code's label. Timeout denial, a delivery that takes fifty seconds against a forty-five-second wait; fix: widen the wait in the scheduled lane, and treat slow delivery as a finding, not a flake, arrival inside the code's validity window is part of the contract.
The meta-fix is evidence: every failure report should carry the address, the matched pattern, and the timeline. With those three, "flaky" becomes diagnosable in one look, without them, the honest label for most "flakes" is "unmeasured".
What this buys you in production
The quiet arithmetic of email testing: the flows are few (signup, verify, reset, notify), the assertions are cheap, and the failures in production are expensive, lockouts, undelivered receipts, and the support load of "your mail never came". An hour of setup with disposable inboxes converts that entire class of failure into a red test in a scheduled lane, seen by the person who caused it instead of the person affected by it.
And the capture side closes the safety half: staging and CI can exercise full mail-sending flows (load tests included) with zero risk of a misconfiguration emailing a real customer, because the gateway cannot relay. The combination is the whole argument: real delivery where you assert, guaranteed non-delivery where you blast, and automatic cleanup on both sides.
Drafted with AI assistance and reviewed against Tempx's editorial rules (no invented statistics, no competitor claims).
Related: Testing email with SMTP and MCP · Testing OTP emails · SMTP capture integration
Create a temporary email address
Free, instant, no sign-up, messages auto-delete.
Go to your inbox