Create a disposable inbox in one API call. Your app sends the OTP. We hand it to your test — parsed, typed, and on time.
import { test, expect } from '@playwright/test'; import { MailFixture } from 'mailfixture'; const mail = new MailFixture(); test('signup sends a one-time code', async ({ page }) => { // a real inbox: k3n9v0q2x7@mxsink.sh const inbox = await mail.createInbox(); await page.goto('/signup'); await page.fill('#email', inbox.address); await page.click('text=Send code'); // long-polls until the email lands — no sleep() const otp = await inbox.waitForOtp(); await page.fill('#otp', otp); await expect(page.locator('h1')).toHaveText('Welcome'); });
waitForMessage() holds the connection open until the email lands — usually under a second after your app sends it. No sleep(5000), no polling every 500ms, no “flaky on CI, fine locally.”
-await page.waitForTimeout(5000);-for (let i = 0; i < 10; i++) {-msgs = await pollInbox(); if (msgs.length) break;-await sleep(1000);-}+ const msg = await inbox.waitForMessage({ timeout: 30_000 });
{
"subject": "Your Acme code",
"to_addrs": ["qa-7f3a@tests.acme.dev"],
"extracted": {
"otp": { "best": "482913", "candidates": […] },
"links": [
{ "class": "verify",
"url": "https://acme.dev/verify?t=…" },
{ "class": "unsubscribe",
"url": "https://acme.dev/unsub?u=…" }
]
}
}
Every message arrives pre-parsed: the one-time code, the magic link, the verification link — classified and sitting in extracted, next to SPF/DKIM/DMARC verdicts so your suite can assert the mail authenticates, not just arrives. Your assertion is one line, and it doesn't break when marketing redesigns the email. Codes arriving by text instead? Paid plans get dedicated US phone numbers, and SMS runs through the same extractor.
Shared test-mail domains end up on blocklists — then your signup form rejects your own tests. Point one MX record at us and every fixture lives on tests.yourdomain.com: unblockable, because it's yours.
Run a test, watch the inbox fill in real time. Rendered HTML, plain text, headers, raw source — and the extracted OTP one click from your clipboard when you're debugging by hand.
mailfixture is a remote MCP server. Connect Claude Code — or any MCP client — and your agent creates inboxes, triggers your signup flow, and reads the OTP back by itself. Same API key, every plan, nothing to install.
$ claude mcp add --transport http mailfixture https://mailfixture.com/mcp \ --header "Authorization: Bearer $MAILFIXTURE_API_KEY" > test the signup flow end to end create_inbox() → k3n9v0q2x7@mxsink.sh wait_for_otp() → 482913 ✓ verified
Your whole team shares one plan — we count messages, not people. Go over on a paid plan? Soft overage at $3.50 per 1,000 messages — nothing shuts off mid-CI-run.