14-day free trial on every plan

Test every email your app sends. In Playwright, Cypress and CI.

Every test run gets its own inbox. Signup links, OTP codes and receipts land in it within seconds — parsed, extracted and ready to assert on. Nothing is ever delivered to a real person.

  • Inbox in 30 seconds
  • No servers or DNS
  • Nothing is ever delivered to a real person
// runs in
  • Playwright
  • Cypress
  • Jest
  • Vitest
  • GitHub Actions
  • any CI
// captures from
  • SendGrid
  • Postmark
  • Amazon SES
  • Resend
  • Mailgun
  • Nodemailer
  • any SMTP

// how it works

Your app sends. mailQA catches. Your test asserts.

Three steps and no infrastructure. Point your app at mailQA once, and every email it sends becomes data your tests — and your team — can read.

01 · your app sends

Point your mailer at mailQA

Send to an address on your subdomain over the public internet, or point SMTP at the sink with your organization's credentials. Keep SendGrid, Postmark, SES or Resend as they are.

hostsmtp.mailqa.io
port587 (or 2525)
useracme
tlsSTARTTLS

// or send to qa@acme.mailqa.io

02 · mailQA catches and extracts

Every message becomes data

MIME parsed, SPF, DKIM and DMARC verified on arrival, links and one-time codes extracted. The raw .eml is kept as the source of truth, and nothing is forwarded anywhere.

Verify your email address

noreply@acme.dev · via Postmark · 0.8s

codes[]links[]spf · dkim · dmarc

03 · your test asserts

Wait for the mail, not a timer

waitForLink() and waitForCode() resolve the moment the message lands and reject with a typed timeout if it never does. The team watches the same inbox fill up live in the dashboard.

›inboxes.create({ name })14ms
›waitForCode({ inbox })→ 481330
›expect(code).toMatch(/^\d{6}$/)ok
signup.spec.ts● running…✓ passed 1.2s

// 14 days free

Stop manually checking test inboxes.

Create an inbox from your test and automate the entire email workflow — signup, verification, one-time codes, resets, invites. The first 14 days are free.

Start free trial
  • $0 today
  • Cancel anytime

// why mailQA

Built for the four doubts every team has about email tests.

Flaky waits, mail escaping to a real customer, having to change the mailer, and nobody but the test seeing what arrived. Each one has an answer in the product, not in a workaround.

No sleep(). No flake.

waitForLink() and waitForCode() resolve the moment mail lands — typically well under a second. Your suite stops polling a mail log and hoping.

Nothing reaches a real inbox.

mailQA captures and stops. A misconfigured staging environment cannot email a customer through it — on any plan, ever.

Keep your provider and templates.

Stay on SendGrid, Postmark, SES or Resend. Send to anyone@your-org.mailqa.io — or point SMTP at the sink — and nothing else changes.

The whole team sees it live.

Rendered HTML, plain text, raw .eml, headers and auth verdicts, streamed to the dashboard as they arrive over server-sent events. Everyone in the organization sees every inbox.

Auth verified on arrival.

SPF, DKIM and DMARC are checked the moment a message lands on the MX address, and the verdicts sit next to the parsed body in the API and the dashboard.

Two ways in, one inbox.

A real MX address on your subdomain, or the SMTP sink that catches everything your app sends in local dev, CI and staging. Both land in the same inbox model.

// in your test suite

Fits inside the test you already have.

One import. The test creates its own inbox, drives your app normally, and gets the email back as data — no shared mailbox, no sleep(), no message left over from the last run.

  • A fresh address per run — nothing left over from last time.
  • One call to wait — the link, the code and the parsed body come back typed.
  • Creating an inbox is idempotent, so a CI fixture re-runs cleanly. Any language works over the REST API.
Read the SDK reference

tests/signup.spec.ts· @playwright/test

const inbox = await mailqa.inboxes.create({ name: 'signup' });

await page.goto('https://example.com/signup');
await page.fill('[name="email"]', inbox.address);
await page.click('button[type="submit"]');

const link = await mailqa.waitForLink({ inbox: inbox.id });

await page.goto(link);
await expect(page.getByText('Email verified')).toBeVisible();

@mailqa/clientPlaywright guide →✓ 1 passed (1.2s)

// pricing

Start free. Keep it only if it earns its place.

Every plan starts with a 14-day free trial. Cancel before it ends and you pay nothing — and there is no mail infrastructure to tear down either.

Basic

One project, a small suite.

$4.99/ mo

billed monthly

Start free trial
  • 10 inboxes
  • 1,000 messages / month
  • 200 messages kept per inbox
  • 10 MB max message size
  • 7-day retention
  • 2 team members
  • SMTP sink + real MX address
  • Catch-all addresses
  • Your own receiving domain
MOST POPULAR

Pro

Teams testing email flows every day.

$9.99/ mo

billed monthly

Start free trial
  • 50 inboxes
  • 50,000 messages / month
  • 5,000 messages kept per inbox
  • 25 MB max message size
  • 30-day retention
  • 10 team members
  • SMTP sink + real MX address
  • Catch-all addresses
  • Your own receiving domain

Team

Many environments, a large team.

$19.99/ mo

billed monthly

Start free trial
  • 500 inboxes
  • 500,000 messages / month
  • 25,000 messages kept per inbox
  • 50 MB max message size
  • 90-day retention
  • 100 team members
  • SMTP sink + real MX address
  • Catch-all addresses
  • Your own receiving domain
  • Nothing is ever delivered to a real person — there is no forwarding path.
  • $0 today. Your card is first charged when the 14-day trial ends — cancel before then and it never is.
  • Change or cancel from the dashboard, any time. Limits follow the moment you do.
  • Payments are processed by Stripe; the card number never touches mailQA.

Compare every feature, plan by plan →

// questions

The four things everyone asks first.

What is mailQA?

A mail server that captures instead of delivering. Your application sends email to mailQA rather than to a real mailbox; you read what arrived in a dashboard and assert on it from a test suite. Nothing is ever forwarded or relayed onward, so a misconfigured staging environment cannot reach a real customer through it.

How does mail get into an inbox?

Two ways, one inbox model. Point your app at the SMTP sink — smtp.mailqa.io, port 587 or 2525, with your organization’s credentials — and everything it sends is captured, whoever the recipient is. Or let it send to a real address on your subdomain, qa@your-org.mailqa.io, over the public internet. Both land in the same inbox and read identically. On Pro and above that address can be on a domain of your own — qa@test.yourcompany.com — by adding a single MX record.

How do I stop writing sleep(5000) in my tests?

Use the @mailqa/client SDK. waitForMessage, waitForCode and waitForLink are promises that resolve the moment a matching message lands and reject with a typed timeout if it never does — so a test is as fast as your app is, and a failure says what was being waited for. The waiting semantics and the runner recipes are in the SDK guide.

Can I use it to send real email to real people?

No. mailQA captures and stops; there is no forwarding path and no relay to enable. It is for the environments you test in — local development, CI and staging — and the Acceptable Use Policy says so explicitly.

// get started

Your first inbox is 30 seconds away.

No servers. No DNS. No mail infrastructure to run — and nothing ever delivered by mistake.

14-day free trial · cancel anytime · $0 today