Use Case

Email Verification Testing in Cypress

TL;DR
Verdict Cypress needs no special integration here. The API is plain REST, so two custom commands in commands.js put a real verification email inside your end-to-end run.

Two custom commands

The first creates an inbox. The second waits for a message to land in it. Everything else is your normal test.

// cypress/support/commands.js
const API = 'https://emptyinbox.me/api';

const auth = () => ({
  Authorization: `Bearer ${Cypress.env('EMPTYINBOX_API_KEY')}`,
});

Cypress.Commands.add('createInbox', () => {
  return cy
    .request({ method: 'POST', url: `${API}/inbox`, headers: auth() })
    .then((res) => res.body.trim()); // plain text, not JSON
});

// Recursive retry: each attempt re-queries, so a fast delivery returns fast
// and a slow one still gets the full budget.
Cypress.Commands.add('waitForEmail', (address, attempts = 15) => {
  const poll = (left) =>
    cy.request({ url: `${API}/messages`, headers: auth() }).then((res) => {
      const hit = res.body.find((m) => m.inbox === address);
      if (hit) return hit;
      if (left === 0) throw new Error(`no email for ${address} in time`);
      return cy.wait(2000).then(() => poll(left - 1));
    });

  return poll(attempts);
});

Using them in a signup test

describe('signup', () => {
  it('verifies a new account with a real email', () => {
    cy.createInbox().then((address) => {
      cy.visit('/signup');
      cy.get('[name=email]').type(address);
      cy.get('[name=password]').type('correct horse battery staple');
      cy.contains('button', 'Create account').click();

      cy.waitForEmail(address).then((message) => {
        expect(message.subject).to.contain('Verify');

        const code = message.text_body.match(/\b\d{6}\b/)[0];
        cy.get('[name=code]').type(code);
        cy.contains('button', 'Confirm').click();
      });

      cy.contains('Welcome').should('be.visible');
    });
  });
});

If the mail carries a link instead of a code, extract the URL from text_body and pass it to cy.visit. Use html_body when the link only exists in the HTML part of the message.

Why not cy.wait with a fixed delay

A fixed wait is a guess about the slowest delivery you will ever see. Set it low and the suite is flaky; set it high and every run pays the worst case even when the mail arrived in two seconds. The recursive retry above returns as soon as the message exists and only spends the full budget when something is genuinely wrong.

Configuration

Register once and put the key in the environment. Cypress picks up any variable prefixed with CYPRESS_, so CYPRESS_EMPTYINBOX_API_KEY is readable as Cypress.env('EMPTYINBOX_API_KEY') with no config file change.

curl -X POST https://emptyinbox.me/api/auth/register \
  -H "Content-Type: application/json" \
  -d '{"username": "ci-cypress"}'

# {"api_key": "ei...", "inbox_quota": 5}

One credit is spent per inbox, and messages delete themselves after 7 days, so there is no teardown step and no storage to manage.

Frequently asked questions

Do I need a Cypress plugin for this?

No. The API is plain REST over HTTPS, so cy.request reaches it directly. There is no Node task, no plugin install, and nothing to keep in step with Cypress releases.

Why does createInbox call trim on the response?

The inbox endpoint returns the new address as plain text rather than JSON, so the body is the address itself and trailing whitespace should be removed before use.

How do I test a link in the email rather than a code?

Pull the URL out of text_body with a regular expression and hand it to cy.visit. If your template only puts the link in the HTML part, read html_body instead.

Will parallel Cypress runs interfere with each other?

No, as long as each test creates its own inbox. Addresses are randomly generated and messages are filtered by the inbox field, so runs stay isolated.

Put a real inbox in your Cypress run

Free tier includes 5 inboxes. No credit card required.

Get API Key