Quickstart: Cypress
Same four minutes as the Playwright version, one twist: Cypress specs run in the browser, so the SDK lives in cypress.config and your test reaches it through cy.task. Your API key never touches browser code.
1 · Install
The SDK is a single dev dependency with zero transitive baggage.
$ npm install -D mailfixture
2 · Authenticate
Create a key in Dashboard → API keys, then export it where Cypress runs — locally via .env, in CI as a secret. The SDK reads MAILFIXTURE_API_KEY automatically; because it runs in the config file (Node), the key stays out of the browser.
MAILFIXTURE_API_KEY=mfx_••••••••••••••••••••
3 · Register two tasks
Cypress runs spec code in the browser sandbox; anything that needs Node — network SDKs, secrets — goes through cy.task. Two tasks cover the whole flow. Task return values must be serializable, so we hand back the inbox's id and address rather than the handle.
import { defineConfig } from 'cypress'; import { MailFixture } from 'mailfixture'; const mail = new MailFixture(); // reads MAILFIXTURE_API_KEY export default defineConfig({ e2e: { setupNodeEvents(on) { on('task', { async createInbox() { const inbox = await mail.createInbox({ ttlSeconds: 900 }); return { id: inbox.id, address: inbox.address }; }, waitForOtp(inboxId) { return mail.waitForOtp(inboxId, { timeout: 30_000 }); }, }); }, }, });
4 · Write the test
One inbox per test keeps runs independent and parallel-safe. Give cy.task a timeout at least as long as the SDK's — Cypress shouldn't give up before the long-poll does.
it('signup sends a one-time code', () => { cy.task('createInbox').then(({ id, address }) => { cy.visit('/signup'); cy.get('#email').type(address); cy.contains('Send code').click(); cy.task('waitForOtp', id, { timeout: 45_000 }).then((otp) => { cy.get('#otp').type(otp); cy.get('h1').should('have.text', 'Welcome'); }); }); });
waitForLink task the same way — mail.waitForLink(inboxId, { kind: 'verify' }) returns the link already classified. cy.visit(link.url) and you're in.
5 · Run it
Headless or interactive — no sleeps to tune, so it behaves the same everywhere.
$ npx cypress run --spec cypress/e2e/signup.cy.ts Running: signup.cy.ts ✓ signup sends a one-time code (1892ms) 1 passing (3s)
Parallel runs
Because every test creates its own inbox, there is no shared state to fight over — cypress run --parallel across CI containers just works. Inboxes expire on their TTL (15 minutes above), so aborted runs clean up after themselves.