Proxy Mail QA

Test sign-up emails in Cypress

To test email verification in Cypress, register a few tasks that create a fresh address and wait for its email, then call them from your spec: sign up, wait for the link or code, finish the flow. Tasks run in Node, so the proxymail-qa package works there as it does anywhere. It runs on the Free plan, locally and in CI.

Updated

Set up

Install the proxymail-qa package and put an API key in PROXYMAIL_QA_API_KEY in the environment Cypress runs in. The Free plan is enough.

Terminal
npm install --save-dev proxymail-qa

Register the tasks

cypress.config.ts
import { defineConfig } from 'cypress';
import { ProxyMailQA } from 'proxymail-qa';

export default defineConfig({
  e2e: {
    setupNodeEvents(on) {
      const qa = new ProxyMailQA(); // reads PROXYMAIL_QA_API_KEY
      on('task', {
        createAddress: () => qa.createAddress({ persona: true }),
        waitForLink: ({ address, since }: { address: string; since: string }) => qa.waitForLink(address, { type: 'verify', since }),
        waitForCode: ({ address, since }: { address: string; since: string }) => qa.waitForCode(address, { since }),
        deleteAddress: (address: string) => qa.deleteAddress(address).then(() => null),
      });
    },
  },
});

The test

cypress/e2e/signup.cy.js
it('verifies the email', () => {
  cy.task('createAddress').then(({ address }) => {
    const since = new Date().toISOString();
    cy.visit('/signup');
    cy.get('[name=email]').type(address);
    cy.contains('Sign up').click();
    cy.task('waitForLink', { address, since }, { timeout: 35000 }).then(({ value }) => cy.visit(value));
    cy.task('deleteAddress', address);
  });
});

For a code, call the waitForCode task the same way and type its value into the form. since makes the wait ignore anything sent before the form was submitted, so a retried test never reads an old email. Match the selectors and routes to your app.

Add tasks for the checks you want, such as one verification email rather than two, or the link going to staging:

TypeScript
countEmails: ({ address, since }: { address: string; since: string }) => qa.countEmails(address, { since, type: 'link' }),
checkEmail: ({ address, since }: { address: string; since: string }) => qa.checkEmail(address, { since, type: 'link', expectHost: 'staging.example.com' }),

checkEmail returns verdicts on SPF, DKIM and DMARC, links to localhost or plain http, where the link goes and more, never the email itself; assert that its failed list is empty.

Run it in CI

Store the key as a CI secret named PROXYMAIL_QA_API_KEY. In GitHub Actions, the Proxy Mail QA action also posts a report of the run’s emails on the pull request: which arrived, how fast, and which never came.

.github/workflows/e2e.yml
      - uses: aaquib90/proxymail-qa-action@v1
        with:
          api-key: ${{ secrets.PROXYMAIL_QA_API_KEY }}
      - run: npx cypress run
        env:
          PROXYMAIL_QA_API_KEY: ${{ secrets.PROXYMAIL_QA_API_KEY }}

More in the Cypress docs, Node.js client and Running in CI.

Try it on your next sign-up test

Free plan, no credit card. Create an address and watch the code or link arrive.

Create a free accountOpen the app

Questions

Why use cy.task instead of cy.request?

A wait can hold for up to 30 seconds while the email arrives, and the package handles retries, time filters and clear errors. In a task it runs in Node with your API key, which never reaches the browser. cy.request works too, against the REST API.

How long does a wait take?

Usually a few seconds: it returns as soon as the email arrives, and gives up after 30. Cypress allows a task 60 seconds by default; the example sets 35, so a missing email fails the test sooner.

What if the email never arrives?

The task fails with a message saying why as far as Proxy Mail QA can tell: most often the app's email provider is in sandbox or test mode and only sends to verified addresses. Fix the sending side rather than waiting longer.

Built your app with an AI tool? Pick yours