Disposable email in tests
api.internal.temp-mail.io/api/v3/email/new, explained
This is the endpoint people find in network tabs and scraper repos when they want a throwaway inbox without paying for one. Here is what it actually returns, how it differs from the paid official API, and how to wire it into a real signup and email-verification test.
POST api.internal.temp-mail.io/api/v3/email/new is temp-mail.io's undocumented internal endpoint. Send it a POST request with no API key and an optional JSON body, and it returns a fresh disposable inbox as { email, token }. You read that inbox by polling GET /api/v3/email/{address}/messages. It is not the officially supported API, which lives on api.temp-mail.io and requires a premium key (see the official docs).
$ curl -s -X POST api.internal.temp-mail.io/api/v3/email/new \
-H "Content-Type: application/json" -d '{}'
{"email":"rhej2o9c@gmeenramy.com","token":"8RGfvwj0gY9nVh98Rq3i"}That is a real response from the live endpoint on the date above. No account, no header, no key. The token is the handle for that mailbox; the email is the address you hand to whatever app you are signing up for.
What the round trip looks like
The reason this endpoint matters to anyone writing tests is the loop below. A disposable inbox lets your test become the email recipient, so a signup that ends in "check your email" is no longer a dead end. Three actors, seven messages:
Email-verification test with a disposable inbox
Internal endpoint vs the official API
The confusing part of this topic is that there are two APIs with nearly identical names. Most articles only mention the paid one. The two behave very differently, and which you should use depends entirely on how much you can afford for the loop to break.
api.internal.temp-mail.io
The internal endpoint (this one)
- No API key, no account, free.
- Undocumented; the website's own backend.
/api/v3/email/newreturns{ email, token }.- Can change shape or rate-limit without notice.
- Best for: a few CI signup tests where a fallback exists.
api.temp-mail.io
The official, supported API
- Requires a premium account and an
X-API-Keyheader. - Documented with versioned endpoints and rate-limit headers.
- Has a contract you can build on and a support channel.
- Best for: production-grade automation you cannot let drift.
Rule of thumb: use the internal endpoint to get a test green today, but isolate it behind one helper so that moving to the paid API (or to your own catch-all mailbox) is a one-file change, not a suite-wide rewrite.
Wiring it into a Playwright signup test
Five concrete steps turn the endpoint into a passing end-to-end test. Each step has the code that does the work; together they are a complete, self-contained spec you can paste into any Playwright project.
Mint a disposable inbox
Create the address before you touch the browser, so the inbox exists by the time the app tries to deliver mail.
const BASE = "api.internal.temp-mail.io/api/v3";
const res = await fetch(`${BASE}/email/new`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ min_name_length: 10, max_name_length: 10 }),
});
const { email, token } = await res.json();Sign up with that address in the browser
Run your normal registration flow. The only difference is the email field gets the throwaway address.
await page.goto("/signup");
await page.getByLabel("Email").fill(email);
await page.getByLabel("Password").fill("Test1234!");
await page.getByRole("button", { name: "Create account" }).click();
await expect(page.getByText("Check your email")).toBeVisible();Poll the inbox for the verification email
Check the messages endpoint on an interval. Give it a real timeout and a clear failure message so a throttled provider does not masquerade as a broken test.
async function waitForEmail(address: string, timeoutMs = 60000) {
const start = Date.now();
while (Date.now() - start < timeoutMs) {
const r = await fetch(`${BASE}/email/${address}/messages`);
const messages = await r.json();
if (messages.length > 0) return messages[messages.length - 1];
await new Promise((res) => setTimeout(res, 3000));
}
throw new Error("no verification email after 60s");
}Extract the code from the email body
Prefer body_text, strip HTML if you must, then match the code. A 6-digit number is the most common shape; keep fallbacks for 4 and 8 digits.
const msg = await waitForEmail(email);
const text = msg.body_text || msg.body_html.replace(/<[^>]*>/g, " ");
const code =
text.match(/(?:code|verification|OTP)[:\s]+(\d{4,8})/i)?.[1] ??
text.match(/\b(\d{6})\b/)?.[1];Submit the code and assert the account is active
Type the code back into the app and check the state you actually care about: a logged-in dashboard, not just a toast.
await page.getByLabel("Verification code").fill(code);
await page.getByRole("button", { name: "Verify" }).click();
await expect(page).toHaveURL(/\/dashboard/);
await expect(page.getByText("Welcome")).toBeVisible();The exact code Assrt ships against this endpoint
I did not invent the patterns above. They are lifted from the helper that Assrt's own engine uses to test email flows. The file is assrt-mcp/src/core/email.ts and its top-of-file comment names the endpoints directly:
/**
* Disposable email service using temp-mail.io internal API.
*
* Endpoints:
* POST api.internal.temp-mail.io/api/v3/email/new -> { email, token }
* GET api.internal.temp-mail.io/api/v3/email/{email}/messages -> [messages]
*/
const BASE = "api.internal.temp-mail.io/api/v3";
export class DisposableEmail {
static async create(): Promise<DisposableEmail> {
const data = await fetchJson<{ email: string; token: string }>(
`${BASE}/email/new`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ min_name_length: 10, max_name_length: 10 }),
}
);
if (!data.email || !data.token) throw new Error("Invalid response");
return new DisposableEmail(data.email, data.token);
}
// ...waitForEmail() and waitForVerificationCode() poll /messages
}The real waitForVerificationCode() polls every 3 seconds for up to 60 seconds and tries seven regex patterns in order, most specific first, ending with a bare 6-digit-then-4-digit-then-8-digit fallback. That ordering is the unglamorous detail that makes it work across the dozen ways apps format a one-time code. Because Assrt is open source and outputs standard Playwright, this helper is just TypeScript you can read, copy, and own; there is no proprietary format hiding the behavior.
Why owning the helper matters more than the endpoint
An undocumented endpoint is a fine way to get a test green this afternoon and a terrible single point of failure for a suite that runs a hundred times a day. temp-mail.io can rename a field, tighten the rate limit, or expire the host. None of that is a contract you can complain about, because there is no contract.
The protection is structural, not magical: keep every call to this endpoint behind one small helper, give it a timeout and a loud failure message, and never let a test reach into the inbox directly. When the provider throttles you, the test fails with no verification email after 60s, which points at the inbox, instead of a mysterious selector timeout that points at your app. Swapping to the paid API, to a different provider, or to your own catch-all mailbox then touches exactly one file.
This is also the case for generating tests as plain Playwright rather than a closed format. When the inbox helper lives in your repo as readable TypeScript, you can see precisely how a code gets extracted and change it. A tool that hides email verification behind an opaque step gives you nothing to fix when temp-mail.io moves. That difference, real code you keep versus a black box you rent, is the whole reason Assrt generates standard files in the first place.
Stop hand-rolling the email-verification helper
If signup and verification flows are the tests you keep skipping, walk through your stack with us and see how Assrt generates them as Playwright code you own.
Frequently asked questions
Frequently asked questions
What is api.internal.temp-mail.io/api/v3/email/new?
It is the undocumented internal endpoint that temp-mail.io's own web app calls to mint a fresh disposable inbox. Send it a POST request, with no API key, and it returns a JSON object containing an email address and a token. You then read that inbox by polling GET api.internal.temp-mail.io/api/v3/email/{address}/messages. It is not part of the officially supported, documented API, which lives on a different host.
Is it the same as the official Temp Mail API?
No. The official, supported API is documented at docs.temp-mail.io and lives on api.temp-mail.io. It requires an active premium account and an API key passed in an X-API-Key header on every request. The api.internal.temp-mail.io host is the private backend the website itself uses; it is free and unauthenticated, but it is undocumented and can change or rate-limit you without notice.
Does the endpoint need an API key or authentication?
No. As of June 2026 a bare POST with an empty JSON body returns a working address and token with no key, no header, and no account. That is exactly why it shows up in scrapers and test helpers: it is the path of least resistance. The tradeoff is that there is no contract behind it, so treat it as best-effort infrastructure, not a dependency you can lean your CI on without a fallback.
What request body does /api/v3/email/new accept?
An empty body ({}) works and gives you a random address. The endpoint also accepts optional fields: domain and name to request a specific local part and domain, or min_name_length and max_name_length to control the length of the random local part. Assrt's own helper sends {"min_name_length": 10, "max_name_length": 10} so every generated address is the same shape, which keeps test logs readable.
How do I read the inbox and extract a verification code?
After creating the address, poll GET api.internal.temp-mail.io/api/v3/email/{address}/messages on an interval. It returns an array of messages, each with subject, from, created_at, body_text, and body_html. Pull body_text (or strip the HTML), then run a regex for the code. A 6-digit match is the most common shape; fall back to 4 and 8 digit patterns and to phrases like "code: 123456".
Is it safe to depend on this endpoint in CI?
Treat it as best-effort. It is undocumented, shares one mailbox space across every anonymous caller, and rate-limits aggressively under load. For a handful of signup tests it is fine. For a large suite, wrap it in a single helper with a timeout and a clear failure message so that when temp-mail.io throttles you, your test fails with "no verification email after 60s" instead of a confusing selector error. Owning the helper is the point: you can swap the provider without touching any test.
How does Assrt use this endpoint?
Assrt is an open-source test framework that generates real Playwright code. Its engine ships a DisposableEmail class in assrt-mcp/src/core/email.ts that calls this exact endpoint to test signup and verification flows automatically. When Assrt discovers a registration form on your app, it can mint a throwaway inbox, complete the signup, wait for the verification email, extract the code, and assert the account was activated, then hand you the generated Playwright test to keep.
Keep reading
Hybrid API and UI testing in Playwright
Set state through API calls instead of clicking through the UI, then keep your browser assertions focused on real behavior.
Managing E2E test data on staging
How to create, isolate, and tear down test data so signup and verification runs do not collide.
Testing Sign in with Apple
An identity flow that fights automation, and the patterns that make it testable end to end.
Comments (••)
Leave a comment to see what others are saying.Public and anonymous. No signup.