Mailosaur is the mature incumbent: client previews, enterprise SSO, SMS numbers beyond the US. If your suite creates an inbox, waits for an OTP, and asserts — you're paying for a lot of product you'll never call. Here's the honest breakdown.
Rows up there still favor Mailosaur, and we left them in. Their entry plan includes six times our entry volume — if your suite genuinely receives thousands of messages a day from one seat, their Personal plan is strong value. Client previews are a real capability we don't have and aren't building. And on SMS — which we now do — their coverage is broader: our numbers are US-only, theirs reach other countries. If your app verifies phones in Berlin, they win that row too.
The rows that favor us are about shape, not size. Test tooling gets used by whoever is debugging the flaky signup spec at 6pm — per-seat pricing means that person might not have a login. On MailFixture everyone gets their own sign-in, on every plan: invite the whole team and each person signs in with their own magic link. A QA inbox with a bouncer at the door helps nobody.
And the last row is a philosophy, not a feature gap. Mailosaur can reply and forward because their customers asked for it — legitimate uses exist. We refuse to ship an SMTP client at all: mail comes in, never out. Our IPs can't land on a spam blocklist, and no bug of ours can ever send email with your domain on it. For a tool that exists to absorb your production email flows, we think that guarantee is worth more than the features it costs us. It's also why we can charge $3.50 per 1,000 messages for overage instead of cutting you off mid-run.
The concepts map one-to-one: a Mailosaur server becomes a MailFixture domain, an email address becomes an inbox, and your polling loop becomes a long-poll the server holds open. Same test, fewer moving parts:
const mail = new MailFixture(); // reads MAILFIXTURE_API_KEY const inbox = await mail.createInbox({ ttlSeconds: 900 }); await page.fill('#email', inbox.address); await page.click('text=Send code'); const otp = await inbox.waitForOtp({ timeout: 30_000 }); // server-side long-poll await page.fill('#otp', otp);
waitForOtp, keep your assertions.