Proxy Mail QA

Tests and CI

Sign-up tests that wait for the real email.

Your end-to-end tests sign up like a real user, then get the code or link in one call. Check the email itself, give parallel tests their own accounts, and see every run’s emails on the pull request.

Free plan · npm, Python and REST · Playwright, Cypress, Selenium and more

How it works

One call where the test used to stall.

  1. Create an address

    One call makes a test address with a fictional persona and a password, or gives the value for every field of your form. In CI, one address can serve a whole run, a plus-tag per test.

  2. Sign up in your app

    Your test fills in the form with Playwright, Cypress, Selenium or anything else, and notes the time it submitted.

  3. Wait for the code or link

    The wait returns as soon as the email arrives, with only the code or link. Mail from before the test submitted is ignored.

  4. Finish the flow

    Type the code or open the link, and check the user is signed in. If nothing came, the error says why as far as we can tell.

In your tests

A few lines in the test you already have.

The npm package for Node.js, a Python helper with nothing to install, and the REST API for everything else. Full examples for each tool are in the developer docs.

Playwright with the npm package

signup.spec.ts
import { test, expect } from '@playwright/test';
import { ProxyMailQA } from 'proxymail-qa';

const qa = new ProxyMailQA(); // reads PROXYMAIL_QA_API_KEY

test('sign-up sends a working code', async ({ page }) => {
  const { address, password } = await qa.createAddress({ password: true });
  await page.goto('/sign-up');
  await page.getByLabel('Email').fill(address);
  await page.getByLabel('Password').fill(password);

  const submitted = new Date();
  await page.getByRole('button', { name: 'Sign up' }).click();

  const { value: code } = await qa.waitForCode(address, { since: submitted });
  await page.getByLabel('Code').fill(code);
  await expect(page.getByText('Welcome')).toBeVisible();
});

pytest with the Python helper

test_signup.py
import re

from playwright.sync_api import expect
from proxymail_qa import create_address, now, wait_for_link


def test_sign_up_verifies_the_email(page):
    account = create_address()
    page.goto("/signup")
    page.get_by_label("Email").fill(account["address"])
    page.get_by_label("Password").fill(account["password"])

    submitted = now()
    page.get_by_role("button", name="Sign up").click()

    page.goto(wait_for_link(account["address"], since=submitted))
    expect(page).to_have_url(re.compile("verified"))

What tests can check

More than “did the code arrive”.

Codes and links

Verification and login codes, and verify, magic sign-in, invite and reset links, with only the mail sent after the test submitted.

Nothing arrived, or one did

Prove an unsubscribed user got no newsletter, and catch a sign-up that sends two verification emails instead of one.

The email’s sending and build

SPF, DKIM and DMARC, links to localhost or plain http, a link that goes to production instead of staging, a missing expiry, whether it fits a phone, and the text and language you expect.

An account pool

Tag accounts with their state and let each test borrow one. Your own hooks make one when none is free, confirm its state and put it back. Parallel tests never share one.

A report on the pull request

Which emails arrived and how fast, which test each came from, the codes and links, what failed, and what changed since the last runs: slower emails, extra emails, flaky tests.

Why mail is missing

When a wait comes back empty, the error says why as far as we can tell, such as an email provider in sandbox mode that only sends to verified addresses.

On the pull request

Every run’s emails, where you review the code.

Add the Proxy Mail QA action before your tests. The npm package and the Python helper add what they see to the run, and the action posts the report when the job ends, passed or not. With the Playwright fixture and reporter, or the pytest hooks, it also says which test each email came from and which tests failed.

.github/workflows/e2e.yml
jobs:
  e2e:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      pull-requests: write
    steps:
      - uses: actions/checkout@v7
      - uses: aaquib90/proxymail-qa-action@v1
        with:
          api-key: ${{ secrets.PROXYMAIL_QA_API_KEY }}
      - run: npm ci
      - run: npx playwright test
        env:
          PROXYMAIL_QA_API_KEY: ${{ secrets.PROXYMAIL_QA_API_KEY }}

Runs from other CI systems

Made for CI

Safe to run on every push.

  • Its own key

    Give CI its own project or key, kept in your CI system’s secrets. Revoking it affects nothing else.

  • One address per run

    Mail to name+anything@ arrives at name@, and a wait on a tagged address finds only its own mail, so a busy pipeline stays well inside the daily limits.

  • Parallel workers

    A wait is one request however long it holds. Keys make 60 requests a minute on Free and 600 on Team.

  • The report keeps no email text

    A run keeps the addresses, codes, timings, check results and each link’s site and path, with token-like parts hidden. Never an email’s text or subject, or a link’s query. Runs are kept for 90 days.

  • Forks get no secrets

    On a pull request from a fork, the action warns and starts no run, so your key never reaches code you haven’t reviewed.

  • Test sites only

    Proxy Mail QA is for development, staging, QA, preview and local versions of apps you build or may test, never live ones.

FAQ

Questions

Which test tools does it work with?

Anything that can make an HTTP request. The npm package covers Node.js test runners such as Playwright, Cypress and Puppeteer, the Python helper covers pytest with Selenium or Playwright, and small helpers for XCUITest, Espresso, Flutter, Maestro, Detox and Appium check emails, use the account pool and add to the run's report too. Everything else uses the REST API.

Do I need the Team plan?

No. The npm package, the Python helper, the REST API, the GitHub Action, runs and the account pool are on every plan. Free keys make 60 requests a minute and up to 50 new addresses a day, and plus-tags let one address serve a whole run. Team raises the request limit to 600 a minute.

Does the report work outside GitHub Actions?

Yes. Start a run with the API (qa.startRun() in the npm package, start_run() in Python), send its id with each call, finish it, and get the report as Markdown to post wherever you review code.

What fails a run?

An email a test waited for that never came, one that came when none should have, a failed email check, or a failed test the reporter recorded. The action can fail the job then too, even when the tests passed, with fail-job set to true.

Can my coding agent use the same account pool?

Yes. Agents connected to the MCP server check out and return accounts from the same project’s pool, so an agent and your test suite never use the same account at once.

More features

Make your next sign-up test wait for the real email.

Create a free account, make an API key in Project settings, and add one wait to a test you already have.

Create a free accountOpen the app