Skip to main content

On This Page

Testing Email Verification Flows with Playwright and a Disposable Inbox API

3 min read
Share

These articles are AI-generated summaries. Please check the original sources for full details.

Testing Email Verification Flows with Playwright and a Disposable Inbox API

Christian Potvin introduces a robust method for testing sign-up flows by generating a unique disposable inbox for every individual test. This approach solves the persistent issue of state isolation where parallel tests intercept each others’ verification emails. By using a hosted API, developers can ensure deterministic results without managing local SMTP infrastructure.

Why This Matters

In production-grade testing, mocking email services often fails to capture the nuances of third-party integrations like Auth0 or AWS Cognito. While shared inboxes or IMAP polling are common fallbacks, they introduce significant fragility due to rate limits and non-deterministic state, whereas per-test isolation ensures that each worker in a parallel suite operates in a completely clean environment.

Key Insights

  • MinuteMail API provides a free tier of 100 calls per day for creating isolated test mailboxes (Potvin, 2026).
  • Transactional emails from providers like SendGrid or Cognito typically arrive within a 2–5 second window, necessitating asynchronous polling.
  • Per-test inbox isolation solves the shared-state problem where concurrent test workers read stale or incorrect emails.
  • Playwright fixtures enable automatic lifecycle management, ensuring mailboxes expire without requiring manual cleanup steps.
  • The MinuteMail Pro plan scales to 10,000 calls per day for high-volume enterprise CI/CD pipelines.

Working Examples

Helper utility to create a new mailbox via the MinuteMail REST API.

const BASE_URL = 'https://api.minutemail.co/v1'; const API_KEY = process.env.MINUTEMAIL_API_KEY!; export async function createMailbox(ttlMinutes = 10): Promise<Mailbox> { const response = await fetch(`${BASE_URL}/mailboxes`, { method: 'POST', headers: { 'Authorization': `Bearer ${API_KEY}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ ttl: ttlMinutes }) }); if (!response.ok) throw new Error(`Failed: ${response.status}`); return response.json(); }

Playwright fixture that provides a fresh inbox to every test automatically.

export const test = base.extend<EmailFixtures>({ inbox: async ({}, use) => { const mailbox = await createMailbox(10); await use({ mailbox, waitForEmail: (options) => waitForEmail(mailbox.id, options) }); } });

End-to-end test using the isolated inbox to extract an OTP and complete registration.

test('user can complete verification', async ({ page, inbox }) => { await page.goto('/register'); await page.fill('[data-testid=email]', inbox.mailbox.address); await page.click('[data-testid=submit]'); const email = await inbox.waitForEmail({ timeout: 45000 }); const otp = email.body.match(/\b\d{6}\b/)?.[0]; await page.fill('[data-testid=otp]', otp!); await page.click('[data-testid=verify]'); await expect(page).toHaveURL('/dashboard'); });

Practical Applications

  • Use Case: Testing Cognito or Firebase Auth flows that require real SMTP delivery. Pitfall: Mocking the service and failing to catch delivery configuration errors.
  • Use Case: Running E2E suites with high parallelism (e.g., —workers=8). Pitfall: Using a shared Gmail account which results in IMAP connection rate-limiting and race conditions.

References:

Continue reading

Next article

Beyond the Consumer Model: Moving to Zero-Knowledge Secret Operations for AI Agents

Related Content