receive-only · built for CI

Email fixtures for your test suite.

Create a disposable inbox in one API call. Your app sends the OTP. We hand it to your test — parsed, typed, and on time.

100 messages/mo free · no per-seat pricing, ever
signup.spec.ts
import { test, expect } from '@playwright/test';
import { MailFixture } from 'mailfixture';

const mail = new MailFixture();

test('signup sends a one-time code', async ({ page }) => {
  const inbox = await mail.createInbox();  // qa-7f3a@tests.acme.dev

  await page.goto('/signup');
  await page.fill('#email', inbox.address);
  await page.click('text=Send code');

  const otp = await inbox.waitForOtp();    // long-polls. no sleep()
  await page.fill('#otp', otp);

  await expect(page.locator('h1')).toHaveText('Welcome');
});
WORKS WITH Playwright Cypress pytest Selenium …or plain curl
01 · LONG-POLLING BUILT IN

Delete your retry loop.

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.”

the diff your teammates will thank you for
- 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 });
GET /v1/messages/msg_8Ki2 · 200
{
  "subject": "Your Acme code",
  "to": "qa-7f3a@tests.acme.dev",
  "extracted": {
    "otp": "482913",
    "links": [
      { "kind": "verification",
        "url": "https://acme.dev/verify?t=…" },
      { "kind": "unsubscribe",
        "url": "https://acme.dev/unsub?u=…" }
    ]
  }
}
02 · FIRST-CLASS EXTRACTION

OTPs are fields, not regex homework.

Every message arrives pre-parsed: the one-time code, the magic link, the verification link — classified and sitting in extracted. Your assertion is one line, and it doesn't break when marketing redesigns the email.

03 · CUSTOM DOMAINS

Never “disposable email detected.”

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.

DNS · two records, five minutes
MXtests → mx.mailfixture.com (10)
TXT_mf.tests → mf-verify=7f3a91c2
tests.acme.dev verified — fixtures armed
qa-7f3a@tests.acme.dev waiting for messages…
Your Acme code 0.9s after send
482913 copy otp
delivered → parsed → asserted· 1 passed, 0 flaky
04 · A DASHBOARD WORTH WATCHING

See the email your test saw.

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.

PRICING

No per-seat pricing. Ever.

Your whole team shares one plan — we count messages, not people. Go over? Soft overage at $2 per 1,000 messages. Nothing shuts off mid-CI-run.

Free
$0
100 msgs/mo
shared domain
Solo
$15/mo
2,500 msgs/mo
custom domain
MOST TEAMS
Team
$49/mo
15,000 msgs/mo
3 domains · webhooks
Scale
$149/mo
75,000 msgs/mo
10 domains