Stamp0

Cypress

Complete guide to using Stamp0 with Cypress for E2E testing. Custom commands for email testing workflows.

This guide shows you how to integrate Stamp0 with Cypress for end-to-end testing of email workflows.

Installation

Install the Stamp0 SDK:

npm install @stamp0/sdk

Configuration

Environment Variables

Add your Stamp0 credentials to cypress.env.json:

cypress.env.json
{
  "STAMP0_API_KEY": "sk_live_your_api_key_here",
  "STAMP0_PROJECT_ID": "prj_abc123"
}

Add cypress.env.json to your .gitignore to keep your API key secure.

Custom Commands

Create custom Cypress commands for Stamp0:

cypress/support/commands.ts
import { Stamp0 } from '@stamp0/sdk';

const stamp0 = new Stamp0({
  apiKey: Cypress.env('STAMP0_API_KEY'),
});

declare global {
  namespace Cypress {
    interface Chainable {
      createInbox(): Chainable<{ id: string; address: string }>;
      waitForEmail(inboxId: string, timeout?: number): Chainable<any>;
      getEmails(inboxId: string): Chainable<any[]>;
    }
  }
}

Cypress.Commands.add('createInbox', () => {
  return cy.wrap(
    stamp0.createInbox({
      projectId: Cypress.env('STAMP0_PROJECT_ID'),
    })
  );
});

Cypress.Commands.add('waitForEmail', (inboxId: string, timeout = 30000) => {
  return cy.wrap(
    stamp0.waitForEmail({
      inboxId,
      timeout,
    })
  );
});

Cypress.Commands.add('getEmails', (inboxId: string) => {
  return cy.wrap(
    stamp0.getEmails({ inboxId })
  );
});

Import the commands in your support file:

cypress/support/e2e.ts
import './commands';

Example: User Signup Flow

cypress/e2e/signup.cy.ts
describe('User Signup', () => {
  it('should sign up and verify email', () => {
    // 1. Create a fresh inbox
    cy.createInbox().then((inbox) => {
      // 2. Fill out signup form
      cy.visit('/signup');
      cy.get('[name="name"]').type('Test User');
      cy.get('[name="email"]').type(inbox.address);
      cy.get('[name="password"]').type('SecurePassword123!');
      cy.get('button[type="submit"]').click();

      // 3. Verify success message
      cy.contains('Check your email to verify your account').should('be.visible');

      // 4. Wait for verification email
      cy.waitForEmail(inbox.id).then((email) => {
        // 5. Extract verification link
        const verifyLink = email.body.match(/https?:\/\/[^\s"]+verify[^\s"]*/)?.[0];
        expect(verifyLink).to.exist;

        // 6. Visit verification link
        cy.visit(verifyLink);

        // 7. Verify account is verified
        cy.contains('Email Verified').should('be.visible');
      });
    });
  });
});

Example: Password Reset Flow

cypress/e2e/password-reset.cy.ts
describe('Password Reset', () => {
  it('should reset password via email', () => {
    cy.createInbox().then((inbox) => {
      // Request password reset
      cy.visit('/forgot-password');
      cy.get('[name="email"]').type(inbox.address);
      cy.get('button[type="submit"]').click();

      // Wait for reset email
      cy.waitForEmail(inbox.id).then((email) => {
        // Extract reset link
        const resetLink = email.body.match(/https?:\/\/[^\s"]+reset[^\s"]*/)?.[0];
        expect(resetLink).to.exist;

        // Complete reset flow
        cy.visit(resetLink);
        cy.get('[name="password"]').type('NewPassword456!');
        cy.get('[name="confirmPassword"]').type('NewPassword456!');
        cy.get('button[type="submit"]').click();

        // Verify success
        cy.contains('Password updated successfully').should('be.visible');
      });
    });
  });
});

Example: OTP Verification

cypress/e2e/otp.cy.ts
describe('OTP Verification', () => {
  it('should verify with OTP from email', () => {
    cy.createInbox().then((inbox) => {
      // Trigger OTP flow
      cy.visit('/login');
      cy.get('[name="email"]').type(inbox.address);
      cy.get('[name="password"]').type('Password123!');
      cy.get('button[type="submit"]').click();

      // Wait for OTP email
      cy.waitForEmail(inbox.id).then((email) => {
        // Extract 6-digit OTP
        const otpMatch = email.body.match(/\b(\d{6})\b/);
        expect(otpMatch).to.exist;
        const otp = otpMatch[1];

        // Enter OTP
        cy.get('[name="otp"]').type(otp);
        cy.get('button[type="submit"]').click();

        // Verify successful login
        cy.url().should('include', '/dashboard');
      });
    });
  });
});

Using Fixtures

Store inbox data across test steps using fixtures:

cypress/e2e/multi-step.cy.ts
describe('Multi-step Flow', () => {
  let testInbox: { id: string; address: string };

  before(() => {
    // Create inbox once for all tests in this suite
    cy.createInbox().then((inbox) => {
      testInbox = inbox;
    });
  });

  it('step 1: sign up', () => {
    cy.visit('/signup');
    cy.get('[name="email"]').type(testInbox.address);
    cy.get('[name="password"]').type('Password123!');
    cy.get('button[type="submit"]').click();
  });

  it('step 2: verify email', () => {
    cy.waitForEmail(testInbox.id).then((email) => {
      const verifyLink = email.body.match(/https?:\/\/[^\s"]+verify[^\s"]*/)?.[0];
      cy.visit(verifyLink);
      cy.contains('Email Verified').should('be.visible');
    });
  });

  it('step 3: login', () => {
    cy.visit('/login');
    cy.get('[name="email"]').type(testInbox.address);
    cy.get('[name="password"]').type('Password123!');
    cy.get('button[type="submit"]').click();
    cy.url().should('include', '/dashboard');
  });
});

Handling Multiple Emails

When your flow sends multiple emails:

cypress/e2e/multiple-emails.cy.ts
describe('Multiple Emails', () => {
  it('should handle welcome and verification emails', () => {
    cy.createInbox().then((inbox) => {
      // Trigger signup (sends welcome + verification emails)
      cy.visit('/signup');
      cy.get('[name="email"]').type(inbox.address);
      cy.get('[name="password"]').type('Password123!');
      cy.get('button[type="submit"]').click();

      // Wait a bit for both emails
      cy.wait(5000);

      // Get all emails
      cy.getEmails(inbox.id).then((emails) => {
        // Find the verification email
        const verificationEmail = emails.find((e) =>
          e.subject.toLowerCase().includes('verify')
        );
        expect(verificationEmail).to.exist;

        // Find the welcome email
        const welcomeEmail = emails.find((e) =>
          e.subject.toLowerCase().includes('welcome')
        );
        expect(welcomeEmail).to.exist;
      });
    });
  });
});

Utility Functions

Create utility functions for common operations:

cypress/support/utils.ts
/**
 * Extract a URL from email body matching a pattern
 */
export function extractLink(body: string, pattern: RegExp): string | null {
  const match = body.match(pattern);
  return match ? match[0] : null;
}

/**
 * Extract verification link from email
 */
export function extractVerificationLink(body: string): string | null {
  return extractLink(body, /https?:\/\/[^\s"]+(?:verify|confirm)[^\s"]*/i);
}

/**
 * Extract password reset link from email
 */
export function extractResetLink(body: string): string | null {
  return extractLink(body, /https?:\/\/[^\s"]+(?:reset|password)[^\s"]*/i);
}

/**
 * Extract 6-digit OTP from email
 */
export function extractOTP(body: string): string | null {
  const match = body.match(/\b(\d{6})\b/);
  return match ? match[1] : null;
}

Use in tests:

import { extractVerificationLink, extractOTP } from '../support/utils';

cy.waitForEmail(inbox.id).then((email) => {
  const link = extractVerificationLink(email.body);
  const otp = extractOTP(email.body);
});

CI Integration

GitHub Actions

.github/workflows/e2e.yml
name: E2E Tests

on: [push, pull_request]

jobs:
  cypress:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: 20

      - name: Install dependencies
        run: npm ci

      - name: Run Cypress tests
        uses: cypress-io/github-action@v6
        with:
          start: npm run dev
          wait-on: 'http://localhost:3000'
        env:
          CYPRESS_STAMP0_API_KEY: ${{ secrets.STAMP0_API_KEY }}
          CYPRESS_STAMP0_PROJECT_ID: ${{ secrets.STAMP0_PROJECT_ID }}

Note the CYPRESS_ prefix for environment variables - Cypress automatically picks these up.

Best Practices

1. Fresh Inbox Per Test

Each test should create its own inbox:

it('test 1', () => {
  cy.createInbox().then((inbox) => {
    // This test has its own inbox
  });
});

it('test 2', () => {
  cy.createInbox().then((inbox) => {
    // This test has its own inbox
  });
});

2. Reasonable Timeouts

Use appropriate timeouts for email delivery:

// 30 seconds is usually enough
cy.waitForEmail(inbox.id, 30000);

Use case-insensitive patterns:

// Good - handles different URL formats
const link = email.body.match(/https?:\/\/[^\s"<>]+verify[^\s"<>]*/i)?.[0];

// Bad - too specific
const link = email.body.match(/https:\/\/example.com\/verify\?token=/)?.[0];

4. Handle Test Isolation

Cypress runs tests in isolation. Use before hooks for setup:

describe('Suite', () => {
  let inbox;

  before(() => {
    cy.createInbox().then((i) => {
      inbox = i;
    });
  });

  // Tests can use `inbox`
});

Troubleshooting

Commands not found?

Make sure you've imported the commands file in cypress/support/e2e.ts:

import './commands';

Timeouts?

If emails aren't arriving:

  • Check your application is actually sending emails
  • Verify the inbox address is correct
  • Increase the timeout if your email service is slow

Next Steps