Signup and password-reset flows depend on email, which makes them awkward to test end-to-end. Most teams either mock the email step entirely (so a broken email template or a misconfigured SMTP relay never shows up in CI) or wire up a real mailbox and fight with IMAP polling and leftover test messages.
A temp mail API gives every test run a brand-new, disposable inbox. The test waits for the real confirmation email to arrive, reads it like any other HTTP response, and throws the inbox away when it's done — no mocking, no shared mailbox, no cleanup script.
What you need
- An AxioAPI key, stored as the
AXIOAPI_KEYenvironment variable. Never commit it to source control — read it from the environment or your CI secret store. - Playwright Test (the same
requestfixture used for UI assertions also makes the API calls — no extra HTTP client needed). - A staging environment that actually sends email.
The test
The flow is three calls: create an inbox, submit it in the signup form, poll for the confirmation email.
// signup.spec.js import { test, expect } from '@playwright/test'; const API = 'https://axioapi.com/api/v1'; const headers = { Authorization: `Bearer ${process.env.AXIOAPI_KEY}`, Accept: 'application/json' }; async function waitForMail(request, inbox, timeoutMs = 60000) { const deadline = Date.now() + timeoutMs; while (Date.now() < deadline) { const res = await request.get(`${API}/temp-mail/inboxes/${inbox}/messages`, { headers }); const { items } = (await res.json()).data; if (items.length > 0) return items[0]; await new Promise((resolve) => setTimeout(resolve, 3000)); } throw new Error('No email arrived in time'); } test('signup sends a confirmation email', async ({ page, request }) => { const created = await request.post(`${API}/temp-mail/inboxes`, { headers }); const { id, address } = (await created.json()).data; try { await page.goto('https://staging.example.com/signup'); await page.fill('[name=email]', address); await page.click('button[type=submit]'); const message = await waitForMail(request, id); expect(message.subject).toContain('Confirm'); } finally { await request.delete(`${API}/temp-mail/inboxes/${id}`, { headers }); } }); How it works
- The create call returns
data.idanddata.address— use the address as the test's throwaway email, keep the id to read and delete the inbox later. - The list call returns
data.items, a summary of each message. To read a full body (for an HTML confirmation link, for example), callGET /api/v1/temp-mail/inboxes/{inbox}/messages/{message}. -
DELETEremoves the inbox and is free, so it belongs in afinallyblock — the test cleans up even when the assertion fails.
Keep it reliable and cheap
Every poll is billed, so wait a few seconds between polls and set a timeout instead of looping forever — check the current prices on the Temp Mail API page before you wire this into a large suite. Use a fresh inbox per test so tests never read each other's mail, and rotate the address if your app rejects one it has already seen. Disposable inboxes are for testing only — never use them for an account you need to keep.
If you want to try it without writing any code first, there's a free email validator tool backed by the same API.
![Why Object.keys(user) Returns string[], Not (keyof User)[]: TypeScript Means It](https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2q4w48b9bzs43tynyhsf.png)
