A temp mail API for testing lets your automated tests do what a human tester used to do by hand: create a fresh inbox, trigger a signup against it, wait for the verification email, pull the code out, and finish the flow — all in code, with no mailbox open in a browser. If your product sends a confirmation email during signup, that email is a wall your end-to-end (E2E) tests slam into. A disposable-mail REST API is how you get through it reliably, in CI, on every run.
This post covers why programmatic inboxes are the right primitive, the exact shape of an automated verification test, and a realistic Node example that polls for a message and extracts a code with a regex. The code here is illustrative — treat the API reference as the source of truth for request and response shapes.
Quick answer
Use a temp mail API in tests when your product requires email verification, password reset, invite acceptance, or any other email-dependent step. The best pattern is to create a unique inbox per test run, trigger the user flow with that address, poll for the expected message with a timeout, extract the code or link from the full message body, then delete or abandon the inbox. This avoids shared-mailbox race conditions, stale test accounts, and manual CI steps.
For CI and E2E tests, polling is usually simpler and more deterministic than webhooks. Use webhooks when the mail should trigger a background system instead of unblocking a test runner.
Why E2E tests need programmatic inboxes
Any signup worth testing has an email step, and that step is where naive test setups break down. The problems are specific:
- You cannot read a human's mailbox from code. A real Gmail account has no clean, stable API for your test runner to grab "the latest verification email" without OAuth ceremony and rate limits.
- Shared mailboxes create race conditions. Point ten parallel test runs at one inbox and they collide — run A reads run B's code, and your suite goes flaky in a way that is maddening to debug.
- Hardcoded test accounts drift. Reusing the same address means stale state: a user that already exists, a verification already consumed, a "this email is taken" error that has nothing to do with the code under test.
- Manual steps do not belong in CI. A pipeline cannot pause for someone to copy a six-digit code out of Outlook.
A disposable-mail API solves all four by giving every test run its own unique, throwaway inbox that your code fully controls. Each address is isolated, so parallel runs never cross wires; each is fresh, so there is no stale state; and each is discarded after the assertion, so nothing accumulates. This is the same disposability that makes temp mail useful for signups generally — here it is just driven by a test runner instead of a person.
The automated verification flow
Every email-verification test, regardless of framework, follows the same five beats:
- Create an inbox. Call the API to mint a new disposable address. Hold onto both the address and its inbox ID.
- Trigger the signup. Drive your app — through Playwright, Cypress, an HTTP client, whatever — using that address as the user's email.
- Receive the message. Wait for the verification email to land. Either poll the messages endpoint on an interval, or let a webhook push the message to you.
- Extract the code or link. Fetch the full message body and run a regular expression to pull out the numeric code or the confirmation URL.
- Assert and finish. Submit the code (or visit the link), then assert the account reaches a verified state.
The only real decision is beat three: poll or push.
Polling vs. webhooks
Both are first-class; pick by context.
- Poll inside test runners and CI. Your test is already blocking and waiting for a result, so a bounded retry loop — check the messages endpoint every second or two, up to a timeout — fits naturally and keeps everything in one process.
- Use a webhook for push-based automation outside a test loop: background workers, staging services, or anything that should react to an inbound email without holding a loop open. TempMaily webhooks POST the message to your endpoint the moment it arrives, so you skip polling entirely. Register the endpoint once and let deliveries come to you.
For a test suite, polling is almost always the pragmatic choice. For a long-running service that reacts to mail, reach for the webhook.
The API shape at a glance
TempMaily's API is a small, predictable REST surface under /v1, authenticated with a Bearer API key in the Authorization header. The three endpoints you need for a verification test map exactly onto the flow above:
- Create an inbox — mint a new disposable address and get back its ID and email.
- List messages — fetch the messages currently in an inbox (this is what you poll).
- Get a message — retrieve a single message's full content, including the text and HTML body you run your regex against.
Plus webhooks for push delivery when you want messages to come to you instead. Exact paths, parameters, pagination, and response fields are documented at /docs/api — always reconcile your client against that, since the snippet below is intentionally simplified to show the shape, not to be copy-pasted verbatim.
API access is a Premium feature ($9.90/mo), which also gets you the dedicated and custom domains worth using in tests so your own app does not reject the address as disposable.
A realistic Node example
Here is the poll-and-extract pattern in plain Node. It creates an inbox, then polls until the verification email arrives, then pulls a six-digit code out of the body. Swap the endpoint details to match the reference.
const API = "https://tempmaily.co/v1";
const KEY = process.env.TEMPMAILY_API_KEY; // Premium API key
const headers = {
Authorization: `Bearer ${KEY}`,
"Content-Type": "application/json",
};
// 1. Create a fresh disposable inbox for this test run.
async function createInbox() {
const res = await fetch(`${API}/inboxes`, { method: "POST", headers });
if (!res.ok) throw new Error(`createInbox failed: ${res.status}`);
return res.json(); // -> { id, address } e.g. "[email protected]"
}
// 3. Poll the messages endpoint until a matching email lands (or we time out).
async function waitForMessage(inboxId, { timeoutMs = 30000, intervalMs = 2000 } = {}) {
const deadline = Date.now() + timeoutMs;
while (Date.now() < deadline) {
const res = await fetch(`${API}/inboxes/${inboxId}/messages`, { headers });
const { messages = [] } = await res.json();
const hit = messages.find((m) => /verify/i.test(m.subject));
if (hit) {
// 4. Fetch the full message so we have the complete body to parse.
const full = await fetch(`${API}/messages/${hit.id}`, { headers });
return full.json(); // -> { subject, text, html, ... }
}
await new Promise((r) => setTimeout(r, intervalMs));
}
throw new Error("Verification email never arrived within timeout");
}
// 4. Pull the code out of the body with a tightly scoped regex.
function extractCode(message) {
const body = message.text || message.html || "";
const codeMatch = body.match(/\b(\d{6})\b/);
if (codeMatch) return codeMatch[1];
// Or extract a confirmation link instead of a numeric code:
const linkMatch = body.match(/https?:\/\/[^\s"']*\/verify[^\s"']*/i);
if (linkMatch) return linkMatch[0];
throw new Error("No verification code or link found in message");
}
// The full flow, as it would sit inside a test.
async function runVerification(triggerSignup) {
const inbox = await createInbox(); // 1
await triggerSignup(inbox.address); // 2 (drive your app here)
const message = await waitForMessage(inbox.id); // 3
const code = extractCode(message); // 4
return code; // 5 -> submit + assert
}
Wired into a runner, triggerSignup is where Playwright or Cypress fills your signup form with inbox.address, and the returned code is what you type into the verification field before asserting the account is confirmed. The webhook variant replaces waitForMessage entirely: your endpoint receives the message payload and resolves a promise your test is awaiting.
What to watch for
A few things separate a test that passes once from one that stays green in CI.
- Scope your regex tightly.
\d{6}is fine until an unsubscribe footer or a year in a copyright line matches first. Anchor to nearby text ("Your code is"), or match within the specific element that holds the code. - Always bound the poll. A loop with no timeout turns one slow mail delivery into a hung pipeline. Set a deadline and fail loudly.
- Parse the right body. Some emails put the code only in the HTML part, some only in text. Check both, and remember TempMaily sanitizes HTML — parse the delivered body, not a rendered screenshot.
- Use a domain your app accepts. If your product blocklists disposable domains, a shared free address will bounce off your own validation. Test with a Premium dedicated or custom domain so the flow behaves like a real user's. This is the flip side of what we cover in can temporary emails be traced — here you want the address to read as legitimate.
- Keep secrets out of the repo. The API key is a Bearer credential; load it from an environment variable or your CI secret store, never a committed file.
- Do not assert on a stale inbox. Free inboxes expire; in CI, create a fresh one per run rather than reusing an ID across suites.
The bottom line
A temp mail API for testing turns the email-verification step from the flakiest part of your E2E suite into just another assertion. Create an isolated inbox per run, drive the signup, poll (or receive a webhook) for the message, extract the code with a scoped regex, and finish the flow — no human, no shared mailbox, no race conditions. It is the difference between "we skip the email step in CI" and "our signup is tested end to end on every commit."
Programmatic access is part of TempMaily Premium. Start with the full API reference for exact endpoints and payloads, and if you are new to disposable inboxes in general, what is a temporary email covers the fundamentals the API is built on.