Proxy Mail QA

Test Better Auth emails

Better Auth doesn't send email itself: you write the functions that send its verification, password reset, magic link and email OTP messages through your own provider. That makes testing the email essential, since a typo in your sending code or a provider in sandbox mode means nothing arrives. Sign up with a fresh Proxy Mail QA address and the code or link shows up on its row.

Updated

Defaults below are from Better Auth’s documentation on 17 October 2026. Check it for current details.

What you send, and when

Email Your function Notes
Address verification emailVerification.sendVerificationEmail Sent at sign-up with sendOnSignUp: true
Password reset emailAndPassword.sendResetPassword A link to set a new password
Magic link sendMagicLink (magic link plugin) Expires after 300 seconds by default; works once
Email OTP sendVerificationOTP (email OTP plugin) 6 digits, 300 seconds, 3 attempts by default; for sign-in, verification or a password reset

Better Auth’s docs suggest not awaiting the send, to avoid timing attacks. On serverless platforms that means using waitUntil or similar, or the function can end before the email goes.

Test it by hand

  1. Create an address in Proxy Mail QA, with a persona and password if your form asks.
  2. Sign up in your app with it.
  3. The link or code appears on the address’s row within seconds, with a countdown when the email says how long it lasts.

Try the edges too: sign in before verifying (refused with requireEmailVerification), open a magic link twice (the second time fails), and wait past five minutes.

Automate it

otp.spec.ts
import { test, expect } from 'proxymail-qa/playwright';

test('email OTP sign-in works', async ({ page, qa }) => {
  const { address } = await qa.createAddress();
  const requested = new Date();
  await page.goto('/sign-in');
  await page.getByLabel('Email').fill(address);
  await page.getByRole('button', { name: 'Send code' }).click();

  const { value } = await qa.waitForCode(address, { since: requested });
  await page.getByLabel('Code').fill(value);
  await expect(page).toHaveURL(/dashboard/);
});

For links, use qa.waitForLink(address, { type: 'verify', since }) or type: 'magic_signin' and open them. More in Test sign-up emails in Playwright.

Try it on your next sign-up test

Free plan, no credit card. Create an address and watch the code or link arrive.

Create a free accountOpen the app

Questions

How long do Better Auth magic links and codes last?

Five minutes by default: the magic link plugin's expiresIn and the email OTP plugin's expiresIn are both 300 seconds. A magic link works once. An email OTP allows 3 wrong attempts before a new one is needed.

Why didn't the verification email arrive?

Check that emailVerification.sendOnSignUp is on and that your sendVerificationEmail function calls your provider. Then check the provider isn't in sandbox mode, where it only sends to verified addresses.

Can users sign in before verifying?

Not when emailAndPassword.requireEmailVerification is on: sign-in fails with a 403 until the address is verified. Test both: an unverified sign-in is refused, and it works once the link is opened.

See every email testing problem we solve

Better Auth is a trademark of its owner. Proxy Mail QA is independent and isn't affiliated with or endorsed by them.