Testing OTP emails with Playwright

Goal: an e2e test that signs up with a real, receivable email address, waits for the verification code, and completes the flow. No mail server to run, no IMAP polling.

Setup

# one-time: get a free API key at https://inboxdome.com/api
export INBOXDOME_KEY=idk_live_...

Helper

// tests/helpers/inboxdome.ts
const BASE = 'https://inboxdome.com';
const KEY = process.env.INBOXDOME_KEY!;

async function api(path: string, init: RequestInit = {}) {
  const res = await fetch(`${BASE}${path}`, {
    ...init,
    headers: { Authorization: `Bearer ${KEY}`, 'Content-Type': 'application/json', ...init.headers },
  });
  if (!res.ok) throw new Error(`InboxDome ${path} → ${res.status}`);
  return res.json();
}

export async function createInbox() {
  const { inbox } = await api('/v1/inboxes', { method: 'POST', body: '{}' });
  return inbox as { id: string; emailAddress: string };
}

export async function waitForOtp(inboxId: string, timeoutSeconds = 30) {
  const { otp } = await api(`/v1/inboxes/${inboxId}/wait?for=otp&timeoutSeconds=${timeoutSeconds}`);
  return otp.value as string;
}

The test

// tests/signup.spec.ts
import { test, expect } from '@playwright/test';
import { createInbox, waitForOtp } from './helpers/inboxdome';

test('signup with email verification', async ({ page }) => {
  const inbox = await createInbox();

  await page.goto('https://your-app.example.com/signup');
  await page.getByLabel('Email').fill(inbox.emailAddress);
  await page.getByLabel('Password').fill('S3cure!pass');
  await page.getByRole('button', { name: 'Create account' }).click();

  // Blocks until your app's email lands (max 30s)
  const code = await waitForOtp(inbox.id);

  await page.getByLabel('Verification code').fill(code);
  await page.getByRole('button', { name: 'Verify' }).click();
  await expect(page.getByText('Welcome')).toBeVisible();
});

Tips

API reference →