
The unreliable version of an email test is short: create an address, sleep for five seconds, read the first message, and hope it belongs to this run.
The reliable version is still simple, but it treats delivery as asynchronous. It creates a unique address, records a start time, polls within a deadline, matches the intended message, validates the product action, and reports enough context when something fails.
Keep the API key out of the URL
Send the GPTMail key in the X-API-Key header. Do not put it in ?api_key= query parameters. URLs are routinely stored in browser history, access logs, monitoring systems, and referrer data.
X-API-Key: YOUR_API_KEYStore the key in your CI secret manager and mask it in logs. A public daily key is convenient for exploration; controlled automated suites should use a key whose quota and use you can track.
The current endpoints and response examples are maintained on the GPTMail API documentation page.
One address per run
Generate a new address for every independently executable test. Do not derive correctness from an inbox shared by a whole test suite.
Record the address, scenario ID, environment, and test start time. These values let you reject stale messages and diagnose collisions without logging message secrets.
Poll against a deadline, not a fixed sleep
Email delivery time varies. A fixed five-second sleep is both wasteful when delivery is fast and flaky when it is slow.
Use a bounded loop:
deadline = now + 90 seconds
delay = 2 seconds
while now < deadline:
messages = list inbox
candidate = newest message matching sender, subject, and start time
if candidate exists:
read candidate and continue the product test
wait delay with a small random jitter
delay = min(delay * 1.5, 10 seconds)The deadline belongs to the test contract. A password-reset flow may need a different limit from a bulk newsletter preview. Keep the limit explicit so a slow dependency does not silently stretch the entire CI job.
Match precisely
“First message in the inbox” is not a useful selector. Match several signals:
- recipient equals the address created for this run;
- creation time is later than the trigger time;
- sender belongs to the expected service;
- subject or message type matches the flow;
- the message ID has not already been consumed.
When the sender can issue several codes, select the newest matching message. Preserve its ID in the test output so retries cannot accidentally reuse it.
Read only after a list match
Inbox listing should be the cheap discovery step. Fetch the full message only for the selected candidate. This reduces unnecessary work and avoids parsing unrelated promotional mail.
Validate the business outcome, not merely the presence of a six-digit string. Submit the code or open the expected link, then assert that the account reaches the correct state.
Handle rate limits as a signal
If the API returns a quota or rate-limit response, stop the current attempt and surface the reset information. Aggressive retries turn a clear capacity signal into noise and can delay unrelated tests.
Limit concurrency at the test runner, especially when a suite creates many addresses at once. A small worker pool is easier to debug than hundreds of simultaneous polling loops.
Cleanup without hiding failures
Cleanup should run after evidence is captured. On success, remove messages or let short retention handle them. On failure, retain the address and message ID in protected CI artifacts long enough for investigation, then delete them.
Never print verification codes, private links, or complete HTML bodies into public build logs.
Make retries idempotent
Retry the test as a new run with a new address. Do not assume that repeating the same request against the same account and inbox starts from a clean state. The sender may invalidate old codes, throttle repeated requests, or reject duplicate invitations.
If a network request times out, distinguish “no response received” from “operation did not happen.” Check the observable state before issuing a destructive retry.
Report the point of failure
A useful automation failure identifies one of these stages:
- address generation failed;
- product action failed before email enqueue;
- no matching message arrived before the deadline;
- message arrived but content did not match;
- verification action failed;
- cleanup failed.
That classification turns an intermittent “email test failed” into work a team can own.
For the larger test matrix, read A Real Email Test Plan. For delivery diagnosis, use Verification Email Not Arriving: Find the Broken Step.