How to Use a Temporary Email API Without Building Flaky Tests
Reviewed by Once Email engineering and security review
Article guide
Why this article is worth your time
- Original analysis
- We trace one automated signup test from inbox creation through bounded polling, message selection, assertion and cleanup, including failure evidence that is useful without retaining message bodies.
- Trend context
- Email links and codes remain common in automated signup and recovery tests, while parallel CI jobs make shared inboxes, fixed sleeps, unlimited polling and message-body logging increasingly unreliable.
- Practical value
- Readers get a provider-neutral workflow, executable pseudocode, a bounded retry budget, status-code policy, transaction matching criteria, safe logging fields and teardown guidance.
On this page
An email test can pass for the wrong reason. A shared inbox may contain yesterday's code, a fixed 10-second sleep may work on a quiet morning, and a retry loop without a deadline may keep a CI worker occupied long after the application has failed.
A reliable temporary-email API test is a small state machine: create, trigger, poll, match, assert and clean up. Each stage needs a clear owner, a deadline and evidence that does not leak the message itself.
Before automating the flow, use the broader email testing checklist to decide which delivery, rendering and security behaviours belong in the test suite.
Give every test run its own inbox
Create a new inbox for one test or one tightly related scenario. Do not let parallel workers read the same address. Record the returned inbox identifier in the test context, not just the address; subsequent requests should refer to that opaque identifier.
Create the inbox immediately before the action that sends mail. This narrows the time window and prevents an old message from satisfying a weak assertion. If a test runner can retry a failed job, include its run identifier in local diagnostic metadata rather than trying to choose a memorable email address.
Trigger one observable action
Ask the system under test to perform exactly one action: send a confirmation link, deliver a sign-in code or issue a receipt. Capture the application's request or event identifier when it provides one. That identifier is stronger evidence than a subject-line match alone.
Do not test an unsolicited third-party system. Automated inboxes are for applications you own or are authorised to assess. They are not a mechanism for account farming, bypassing a platform's controls or monitoring another person's correspondence.
Poll with a deadline and backoff
Mail delivery is asynchronous, so an immediate empty list is normal. Poll gently and stop decisively. A useful starting budget is a 60-second deadline with waits of 1, 2, 3, 5, 8 and then 10 seconds. Add a little random jitter when many workers start together.
deadline = now + 60 seconds
delay = 1 second
while now < deadline:
messages = list_messages(inbox_id)
candidate = find_expected(messages)
if candidate exists: return candidate
sleep(delay + jitter)
delay = min(delay * 1.6, 10 seconds)
fail("expected email did not arrive before deadline")
Respect 429 Too Many Requests and any Retry-After value. Retrying more aggressively after a rate limit makes the test less likely to recover. Retry temporary 5xx and network failures only within the original deadline; do not silently turn a one-minute test into a ten-minute test.
Match the transaction, not only the subject
Subject lines are written for people and can change. Prefer a combination of evidence:
- the message arrived after the action began;
- the recipient is the inbox created for this run;
- the sender domain is expected;
- a correlation identifier or one-time link belongs to the test transaction;
- there is exactly one candidate, or the test explicitly chooses the newest valid one.
Treat message HTML as untrusted input. Do not execute scripts, load remote images or open links in a normal browsing profile. Extract the target URL, parse it and assert its registered destination before a controlled test client follows it.
Keep secrets out of test output
An API key, verification code and magic link are credentials even when short-lived. Put API keys in the CI secret store and send them in an authorization header. Never place them in query strings, screenshots, fixture files or repository configuration.
On failure, log bounded metadata: inbox identifier suffix, timestamps, message count, redacted sender domain, HTTP status and request ID. Avoid dumping headers, bodies, attachments or complete addresses. A useful test report explains where the state machine stopped without becoming another mailbox archive.
Clean up in a finally block
Deletion must run whether the assertion passes or fails. Put inbox cleanup in the test framework's finally, teardown or after-each hook. Cleanup reduces accidental retention, keeps later tests isolated and makes quota usage easier to understand.
If deletion fails temporarily, report it separately from the product assertion. Do not hide the original failure. A scheduled server-side expiry remains valuable as a backstop, but it should not replace deliberate cleanup.
Plan quotas before parallelising
Estimate calls per scenario: one inbox creation, several list requests, one detail request and one deletion. Ten workers polling every second can exhaust a shared limit without increasing delivery speed. Bound worker concurrency, share the documented rate budget across the test process and display monthly consumption in the account dashboard.
Once Email's planned Developer tier separates the free browser allowance from API automation. Its authentication, error, quota and pricing contracts will be published after live key handling, metering and subscription revocation pass production testing. This keeps product claims aligned with capabilities that users can actually verify.
Diagnose the stage, not just “email missing”
Before enabling a test in CI, define a small result contract. Record the stage, a bounded duration, HTTP status, provider request ID when one exists, candidate count and a non-secret run ID. Do not record the mailbox address, code, link, subject, body, attachment name, API key or full query string.
Classify the outcome before retrying: a rejected trigger belongs to the application under test; delivery pending means no matching message arrived before the deadline; 429 or 503 must honour Retry-After within the original time budget; multiple candidates mean ambiguous correlation; a matching message with a wrong controlled destination is an assertion failure; and cleanup failure must be reported separately without hiding the original result.
The preflight check is equally important: verify that the provider actually offers documented API access, authentication, deletion, expiry and rate-limit behaviour before writing the adapter. Once Email does not currently expose a public production API, so examples in this guide are provider-neutral design patterns, not callable Once Email endpoints.
The best email test is not the one that retries forever. It is the one that creates an isolated inbox, waits politely, proves that the right transaction arrived, records safe evidence and leaves no mailbox behind.
Use an SDK without hiding the test design
Once Email now publishes tested prerelease SDK candidates for TypeScript, Python, Java, Go, .NET, PHP and Ruby. Start at the SDK page, choose the language already used by the test service, then open that language's source directory. Do not introduce a second runtime only to poll a mailbox.
The candidates are downloaded from an immutable GitHub Release rather than a language registry. Before adding one to a project, compare the archive against SHA256SUMS, read its bundled README and pin the version. A repository branch is useful for review, but it is not an immutable dependency. npm, PyPI, Maven Central, NuGet, Packagist and RubyGems commands are intentionally absent until those registry releases exist.
An SDK removes repetitive HTTP serialization; it does not choose a safe deadline, identify the correct message or own cleanup for the test. Keep one inbox per authorized run, one polling owner and one deadline. Classify 429 by Retry-After, keep 503 distinct from an empty inbox, reject ambiguous message matches and delete the inbox in finally. Review the current receive-only API contract whenever the SDK version or OpenAPI contract changes.
Related guides
Email Verification Codes: A Safer Way to Copy, Check and Use Them
Treat an email verification code as a short-lived secret: confirm the request, inspect the destination, copy only the code and clear it when the task is finished.
Verification Email Not Arriving? A Safe Troubleshooting Checklist
Work through address mistakes, sender delays, retries, filtering and mailbox limits without repeatedly requesting codes or weakening account security.