Cypress Email Testing: Verify Emails in E2E Tests
Cypress is great at driving the browser, but the verification email lives outside it. This guide adds Cypress email testing with a custom command that provisions a disposable inbox and reads it through an API — part of our email testing for developers guide, alongside the Playwright and Selenium versions.
The approach
Create one inbox per test, drive the flow, then poll the inbox API until the message arrives. Wrap it in custom commands so specs read cleanly.
Custom commands
// cypress/support/commands.js
Cypress.Commands.add('createInbox', () => {
return cy.request({
method: 'POST',
url: API + '/emails/generate',
headers: { 'X-API-Key': Cypress.env('MAIL_KEY') },
}).its('body')
})
Cypress.Commands.add('waitForEmail', (id, tries = 20) => {
const poll = (n) => cy.request({
url: API + '/emails/' + id + '/messages',
headers: { 'X-API-Key': Cypress.env('MAIL_KEY') },
}).then((res) => {
if (res.body.messages.length) return res.body.messages[0]
if (n === 0) throw new Error('no email arrived')
cy.wait(1500)
return poll(n - 1)
})
return poll(tries)
})
The test
it('verifies the signup email', () => {
cy.createInbox().then((inbox) => {
cy.visit('/signup')
cy.get('#email').type(inbox.address)
cy.contains('Sign up').click()
cy.waitForEmail(inbox.id).then((msg) => {
const code = msg.text.match(/[0-9]{6}/)[0]
cy.get('#code').type(code)
})
cy.contains('Welcome').should('be.visible')
})
})
The recursive poll keeps Cypress's command queue happy without arbitrary cy.wait on a fixed timer. The same command also tests password-reset emails — just match the link instead of a code.
Tips
Give each test its own address, prefer a webhook when you can, and keep CI timeouts tight. Create a mailbox and read the OpenAPI docs to wire it up.