playwright

MailSlurp Alternative for Playwright OTP Testing

9 min read

You installed MailSlurp because someone in a GitHub issue said "just use MailSlurp." You wrote your first OTP test. And halfway through, you looked at the code and thought: this is a lot for one 6-digit number.

Same. Let's talk about it.

This is an honest comparison between MailSlurp and otpmagiclink, for one specific job: testing OTP and magic-link auth flows in Playwright. Not general email testing — MailSlurp wins that. Just auth codes.

Broader on the topic? Read How to Test OTP Flows in Playwright — the ground-up guide.

Who this is for

You write Playwright (or Cypress) tests. Your app has signup, sign-in, or password reset. There's an OTP or magic link in the mix. You're picking a tool to catch those codes in tests.

Not for you: outbound campaigns, HTML rendering across Gmail/Outlook, spam scoring, big attachments. MailSlurp is the right answer there. Don't switch. This post is the narrower job.

What MailSlurp does really well

  • Absurd feature surface. Real inboxes with SMTP/IMAP, custom domains, phone numbers, email verification, HTML rendering tests, spam scoring, attachments, webhooks, AI extractors. If you can name an email scenario, MailSlurp has a controller for it.
  • Track record. Been in market since 2020. 234k weekly npm downloads. Real customers with real logos.
  • SDKs everywhere. JS, Python, Java, C#, Go, Ruby, PHP, Kotlin. Drive it from anything.
  • Actual deliverability testing. Placement tests across Gmail, Outlook, Yahoo — genuinely valuable if you're on a marketing team.

If any of that matters to you: MailSlurp is the safe pick. Bookmark this post for the next dev who isn't you.

Where it gets rough for the OTP case

Here's what a MailSlurp OTP test looks like today:

import { MailSlurp } from "mailslurp-client";
import { test, expect } from "@playwright/test";

const mailslurp = new MailSlurp({
  apiKey: process.env.MAILSLURP_API_KEY!,
});

test("signup with OTP", async ({ page }) => {
  // 1. Create a fresh inbox (counts against your monthly quota)
  const inbox = await mailslurp.createInbox();

  await page.goto("/signup");
  await page.fill("[name=email]", inbox.emailAddress);
  await page.click("button[type=submit]");

  // 2. Wait for the email
  const email = await mailslurp.waitController.waitForLatestEmail({
    inboxId: inbox.id,
    timeout: 30_000,
    unreadOnly: true,
  });

  // 3. Extract the OTP with a regex you have to maintain
  const match = email.body!.match(/\b\d{6}\b/);
  if (!match) throw new Error("OTP not found");
  const otp = match[0];

  await page.fill("[name=otp]", otp);
  await expect(page).toHaveURL("/dashboard");

  // 4. Clean up so you don't hit permanent-inbox limits
  await mailslurp.inboxController.deleteInbox({ inboxId: inbox.id });
});

That test works. Now look at what you just signed up to babysit:

  1. Inbox lifecycle. Create → use → delete on every test. Miss one cleanup, hit the 50-inbox limit on free, wonder why CI stops working.
  2. Wait-controller boilerplate. waitController.waitForLatestEmail({ inboxId, timeout, unreadOnly }). That's a lot of typing for "give me the code."
  3. A regex you own now. Every email template tweak → latent bug.
  4. A 30-second timeout budget. SMTP is slow. Your CI just paid for 30 seconds per test.
  5. A too-powerful API key. MailSlurp's key can send email, buy phone numbers, hit external APIs. Leak it in a CI log and it's a bad Tuesday.

The same test, with @otpmagiclink/playwright

import { SandboxClient } from "@otpmagiclink/playwright";
import { test, expect } from "@playwright/test";

const sandbox = new SandboxClient({
  apiKey: process.env.OTP_API_KEY!,
});

test("signup with OTP", async ({ page }) => {
  await page.goto("/signup");
  await page.fill("[name=email]", "test@yourapp.com");
  await page.click("button[type=submit]");

  const otp = await sandbox.waitForOtp("test@yourapp.com");

  await page.fill("[name=otp]", otp);
  await expect(page).toHaveURL("/dashboard");
});

That's it. No inbox to create. No cleanup. No regex. No wait-controller. Any email string works — the sandbox scopes by project, not by inbox.

Side-by-side

MailSlurp FreeMailSlurp Pro ($49.99/mo)otpmagiclink Freeotpmagiclink Pro ($19/mo)
Monthly volume500 inbound emails5,000 inboundUnlimited (1 project)Unlimited (all projects)
Inbox managementManual create/deleteManual create/deleteNone — use any stringNone
OTP extractionRegex you writeRegex you writeBuilt-in waitForOtp()Built-in
Magic-link extractionRegex you writeRegex you writeBuilt-in waitForMagicLink()Built-in
Time-travel / clock control❌❌✅ advanceClock(3600)✅
Parallel test isolationManual (separate inboxes)ManualAutomatic (per-identifier scoping)Automatic
Playwright integration guideGenericGenericFirst-classFirst-class
SMS OTP captureRequires phone number ($7-35/mo per number)Requires phone number✅ Included✅ Included
API key blast radiusFull account (send, phones, external)Full accountScoped to sandbox reads/writes onlyScoped
Public marketing tests, HTML rendering✅✅❌ (out of scope)❌

What this costs, actually

Say your CI runs 2,000 tests per month across a small team, split across staging and prod-mirror projects.

  • MailSlurp Free — 500 emails won't cut it. You'll upgrade.
  • MailSlurp Pro — $49.99/mo. Comfortable. Also expensive for the features you'll actually touch.
  • otpmagiclink Free — 1 project, good for solo evaluation. Not a team's home.
  • otpmagiclink Pro — $19/mo. Unlimited projects, unlimited runs.

Yearly: $600 (MailSlurp Pro) vs. $228 (otpmagiclink Pro). Same tests. ~2.6× cheaper. No volume ceiling to think about.

Scale up to 20,000 tests/mo:

  • MailSlurp Team — $129.99/mo. Includes 5,000 emails, then $0.99 per 1,000 for the next 15,000 = $144.84/mo = $1,738/yr.
  • otpmagiclink Pro — still $19/mo. $228/yr.

The gap gets worse for MailSlurp as you scale, not better.

Where MailSlurp still beats us (honestly)

Buying the wrong tool is worse than buying an expensive one. MailSlurp beats us on:

  • Marketing / campaign email testing. HTML rendering, spam scoring, placement across mail clients. We literally don't do this.
  • Real inbound receiving. Support workflows, forwarding rules, IMAP. Different product.
  • Attachments. File uploads/downloads. Not something an OTP test needs.
  • Non-Node languages. MailSlurp has native SDKs for Python, Java, C#, Go, etc. We're Node today. (Our REST API works from anywhere, but no first-party client for other langs yet.)
  • Trust. 5+ years in market, enterprise contracts, on-call teams. We're early.

Decision matrix

PickIf…
MailSlurpYou test marketing emails, need HTML rendering, use non-Node languages, or need real IMAP inbox behavior.
otpmagiclink90% of what you test is OTP/magic-link auth flows in Playwright or Cypress, and you want the least code + lowest bill.

Still not sure? Free tier is unlimited runs for 1 project. Wire it into one CI job. Uninstall if it's not a fit — no card, no gotchas.

Migration is search-and-replace

Already have MailSlurp tests? The diff is embarrassingly small:

- const inbox = await mailslurp.createInbox();
- // ... use inbox.emailAddress ...
- const email = await mailslurp.waitController.waitForLatestEmail({
-   inboxId: inbox.id,
-   timeout: 30_000,
-   unreadOnly: true,
- });
- const otp = email.body!.match(/\d{6}/)![0];
- await mailslurp.inboxController.deleteInbox({ inboxId: inbox.id });
+ const otp = await sandbox.waitForOtp('any-string@yourapp.com');

Five lines out. One line in. No cleanup step to forget.

Try it

Grab a free API key at otpmagiclink.com, install the SDK, drop a waitForOtp() into your next test:

npm install --save-dev @otpmagiclink/playwright

Full setup in the Playwright quick start.

Ship OTP + magic-link tests today

@otpmagiclink/playwright is a one-line SDK for waiting on OTPs and magic links in Playwright, Cypress, or any Node test runner. Free tier included.