Define what a passing email test means
A successful inbox request does not prove that your application sent the right email. Use a fresh test address, record when the test starts, and add a unique marker to the subject in your staging mail template. Match the sender, marker and timestamp together. Then inspect the expected message instead of taking the first item in an inbox.
Run this flow against an application you own or are authorized to test. Temporary inboxes are unsuitable for production account recovery or sensitive customer data. An API key grants API access; knowing a temporary address should not be treated as proof of mailbox ownership.
Prepare the test runner
- Save your key as
CLEANTEMPMAIL_API_KEYin your CI secret store or local process environment. Do not commit it, put it in a query string, or ship it in a browser bundle. - Create an address with
POST /api/generate-email. Optionalprefixanddomainfields go in a JSON body; send the key inX-API-Key. Read the returneddata.email, rather than constructing an address from an old domain list. - Record an integer Unix timestamp immediately before triggering the message in your own application. Put a unique run identifier in its subject. A fresh address per parallel job prevents cross-test collisions.
- Poll
GET /api/emails?email=…&summary=1. Once a matching ID appears, fetchGET /api/email/{id}only if the assertion needs the body. Summary mode reduces transferred data; each poll is still a request.
Python: bounded polling without extra dependencies
Download wait_for_email.py beside your test. It uses the Python 3.10+ standard library. After your test has triggered its email, call:
import os
import time
import uuid
from wait_for_email import wait_for_email
address = os.environ["TEST_EMAIL_ADDRESS"] # Generated for this run.
marker = "qa-" + uuid.uuid4().hex
started = int(time.time())
# Trigger your own application's email here, using address and marker.
message = wait_for_email(
address, os.environ["CLEANTEMPMAIL_API_KEY"],
"[email protected]", marker, since=started,
timeout=90, max_attempts=18, interval=5,
)
assert message["id"]
Replace the example sender with the address used by your test application. The trigger is deliberately application-specific: connect it to your signup API, test fixture or browser test. Starting the helper without sending a matching message should time out.
JavaScript: the same contract in Node.js
Download wait-for-email.mjs for Node.js 22+. Import it into your test runner; it is not a frontend script.
import {randomUUID} from 'node:crypto';
import {waitForEmail} from './wait-for-email.mjs';
const address = process.env.TEST_EMAIL_ADDRESS;
const marker = `qa-${randomUUID()}`;
const since = Math.floor(Date.now() / 1000);
// Trigger your own application's email here, using address and marker.
const message = await waitForEmail({
address, key: process.env.CLEANTEMPMAIL_API_KEY,
sender: '[email protected]', marker, since,
timeoutMs: 90000, maxAttempts: 18, intervalMs: 5000,
});
if (!message.id) throw new Error('Expected a matching message');
Assert the body without leaking the verification code
The summary response contains message metadata, not the full HTML or text. Fetch the matching ID for a body assertion. Extract the code using the format of your own template, reject multiple unexpected matches, and submit it only to your test application's expected origin. Do not log an OTP, magic link, complete inbox response or authorization header. Capture status, duration and a synthetic run ID when a test fails.
A six-digit number found anywhere in a message is not automatically the right OTP. For magic links, check the scheme, host and expected path before navigation. Do not open arbitrary links returned by an inbox in your test environment.
Choose a useful failure boundary
These helpers allow at most 18 polls over a 90-second waiting budget, with a per-request timeout. They stop on invalid credentials and confirmed quota exhaustion, and back off on rate limits and temporary server errors. They do not guarantee delivery within 90 seconds. A timeout should produce a controlled test failure, followed by a check of your sender logs and domain availability.
Continue with request budgeting, error handling, or the API reference for generation, message detail and response fields.