How to Test OTP Emails Automatically
One-time passwords close the loop on login, 2FA and sensitive actions — which makes them the most valuable email your app sends, and the one teams most often test by hand. Here is the automated version.
The loop you are automating
Every email OTP flow is the same five beats: submit the identifier (email or username), the app sends a code, your test reads the mailbox, extracts the digits, feeds them back and asserts success. None of those steps needs a human — the only missing piece used to be a mailbox the test could read. A disposable Tempx inbox is exactly that: pick any name, mail arrives through our MX, and GET /api/inbox?address=… returns it as JSON with no authentication.
Where the code lives
The inbox API returns, per message: subject, from, and preview — the first 140 characters of the text part. OTP templates almost always open with the code, so the preview is the sweet spot. Two rules make extraction robust:
- Anchor to context, not just digits.
/code is (\d{6})/beats/\d{6}/the day your mail footer grows an order number. If you control the template, put a stable prefix in the text part. - Take the message by subject filter so a stray marketing mail can never satisfy the assertion.
// otp-test.mjs — request, poll, extract, verify
const ADDR = "otp-" + Date.now().toString(36) + "@tempx.uk";
await api.post("/login/otp", { email: ADDR }); // 1+2. request the code
let code = null; // 3. poll the inbox
for (let i = 0; i < 30 && !code; i++) {
const res = await fetch("https://tempx.uk/api/inbox?address=" + ADDR);
const { messages } = await res.json();
const msg = messages.find((m) => /your login code/i.test(m.subject));
if (msg) code = (msg.preview.match(/code is (\d{6})/) || [])[1];
if (!code) await new Promise((r) => setTimeout(r, 2000));
}
if (!code) throw new Error("no OTP arrived"); // 4. extract (above)
const done = await api.post("/login/otp/verify", // 5. feed it back
{ email: ADDR, code });
if (!done.ok) throw new Error("OTP rejected");
The three flakiness traps
- Shared inboxes. One address for the whole suite means one test can consume another's code. Unique address per test — it is free.
- Fixed sleeps.
sleep(10)is simultaneously too slow and not slow enough. Poll every 2 seconds with a 45-second deadline; assert the arrival time too if delivery speed matters to you. - Ignored rate limits. Inbox reads cap at 240/minute — fine for dozens of parallel tests at 2-second polls, but stagger a 100-worker fan-out or share one poller per worker.
Let an agent do the clicking
If your E2E layer is an AI agent rather than a script, Tempx's MCP server has a tool built for this: wait_for_email blocks server-side until a matching message lands (5–175 s), so the agent's loop is submit → wait → read → continue with no polling code at all. See the MCP integration.
Framework-specific versions of the loop: Playwright and Cypress.
Create a temporary email address
Free, instant, no sign-up — messages auto-delete.
Go to your inbox