Testing Password Reset Emails End-to-End
The reset email is the step most E2E suites skip or mock. With the app's outbound mail landing in the sandbox, the test follows the real link instead.
Two ways to get the mail there: point the app's SMTP transport at send.smtp.dev with a sandbox account's credentials (shown in the CI guide), or let the app send through its normal provider to an address on a domain whose MX points at the sandbox.
The Test
inboxOf and waitForMessage are the polling helpers from the CI guide.
import { expect, test } from '@playwright/test'
test('password reset', async ({ page }) => {
const address = 'reset-user@example.com'
await page.goto('/forgot-password')
await page.getByLabel('Email').fill(address)
await page.getByRole('button', { name: 'Send reset link' }).click()
const message = await waitForMessage(
await inboxOf(address),
m => m.subject === 'Reset your password',
)
const body = message.text || (message.html ?? []).join('')
const link = body.match(/https?:\/\/\S*reset\S*/)?.[0]
expect(link).toBeTruthy()
await page.goto(link)
await page.getByLabel('New password').fill('correct-horse-battery')
await page.getByRole('button', { name: 'Set password' }).click()
await expect(page).toHaveURL(/signin/)
})
The message's text field is the plain text part; html is an array of HTML parts. If your template only wraps the URL in a button, match the href instead: body.match(/href="([^"]+)"/)?.[1].
Keeping It Stable
- One address per worker.
reset+worker1@example.comdelivers toreset@example.comwith the alias intact into, so parallel workers never read each other's mail. Match on recipient and subject, not arrival order. - Poll tight. One-second intervals across a few workers sit nowhere near the 4096 requests per minute limit.
- Or skip polling. Subscribe over SSE and resolve when a
Messageevent for the account arrives.
SPF, DKIM, DMARC
Send from staging to a sandbox address and read the SPF, DKIM, and DMARC results on the received message before you change production DNS.
Email Clients
Connect a real client over IMAP to see how multipart emails render outside the browser. Host, port, and login settings for any client.