Skip to main content

Command Palette

Search for a command to run...

How to E2E Test SendPigeon Email Workflows in Playwright

Updated
5 min read
How to E2E Test SendPigeon Email Workflows in Playwright
Z
Building ZeroDrop — instant disposable email inboxes for testing auth flows in CI pipelines. No signup, no Docker, no SMTP config. Built on Cloudflare Workers + Upstash Redis. Writing about developer tooling, edge infrastructure, and building in public.

When you integrate SendPigeon into your app, you get three stages of email testing. Each stage solves a different problem. This guide walks through all three — and shows how to combine SendPigeon with ZeroDrop for full end-to-end coverage in CI.


The app we're testing

A Next.js signup flow that sends a verification email via SendPigeon:

// app/api/auth/signup/route.ts
import { SendPigeon } from 'sendpigeon';

const pigeon = new SendPigeon(process.env.SENDPIGEON_API_KEY!);

export async function POST(req: Request) {
  const { email } = await req.json();

  const token = crypto.randomUUID();
  const verifyUrl = `\({process.env.NEXT_PUBLIC_URL}/verify?token=\){token}`;

  await pigeon.send({
    from: 'hello@yourapp.com',
    to: email,
    subject: 'Verify your email',
    html: `
      <p>Welcome! Click below to verify your email address.</p>
      <a href="${verifyUrl}">Verify email</a>
    `,
  });

  return Response.json({ success: true });
}

Simple. Now how do you test it at each stage?


Stage 1 — Local development: SendPigeon test key

SendPigeon's sp_test_ API keys capture every email in their dashboard without sending anything to real inboxes. Zero config needed.

SENDPIGEON_API_KEY=sp_test_xxx npm run dev

Sign up in your browser, check the SendPigeon dashboard — the email is there. Subject, HTML, links, everything. This is perfect for visually inspecting your email templates during development.

What it solves: Am I calling SendPigeon correctly? Does my email template look right?

What it doesn't solve: Automated testing. You can't write a Playwright test that reads emails from SendPigeon's dashboard.


Stage 2 — Staging: SendPigeon live key to a real inbox

When you're testing against a staging environment, switch to a live key:

SENDPIGEON_API_KEY=sp_live_xxx

Now emails go to real inboxes. You can test the full flow manually — sign up with your own email, check Gmail, click the link. This catches real delivery issues like spam filtering, DKIM failures, or broken links.

What it solves: Does the email actually reach a real inbox? Does the verification link work end to end?

What it doesn't solve: Automation. You can't run this in CI because the test would need to access your personal Gmail — and parallel test runs would collide on the same inbox.


Stage 3 — CI: SendPigeon live key + ZeroDrop

For automated Playwright tests in GitHub Actions, you need:

  • SendPigeon live key — so real emails are sent through actual delivery infrastructure
  • ZeroDrop — to catch those emails in an isolated, disposable inbox your test can read
npm install zerodrop-client
import { test, expect } from '@playwright/test';
import { ZeroDrop } from 'zerodrop-client';

const mail = new ZeroDrop();

test('user can sign up and verify email', async ({ page }) => {
  // 1. Generate a disposable inbox
  const inbox = mail.generateInbox();
  // → "swift-x7k2m@zerodrop-sandbox.online"

  // 2. Sign up — SendPigeon sends a real verification email to this inbox
  await page.goto('/signup');
  await page.fill('[data-testid="email"]', inbox);
  await page.click('[data-testid="submit"]');

  await expect(page).toHaveURL('/check-email');

  // 3. ZeroDrop catches the email — magic link auto-extracted, no regex needed
  const email = await mail.waitForLatest(inbox, { timeout: 30000 });

  expect(email.subject).toContain('Verify your email');
  expect(email.magicLink).not.toBeNull();

  // 4. Click the verification link
  await page.goto(email.magicLink!);

  // 5. Assert verified
  await expect(page).toHaveURL('/dashboard');
  await expect(page.getByText('Email verified')).toBeVisible();
});

This tests the real path — SendPigeon's live infrastructure delivers the email, ZeroDrop catches it at Cloudflare's edge, the test reads it automatically.


OTP flows

If your app sends a numeric OTP instead of a magic link:

await pigeon.send({
  from: 'hello@yourapp.com',
  to: email,
  subject: 'Your verification code',
  html: `<p>Your code is: <strong>${otp}</strong></p>`,
});
const email = await mail.waitForLatest(inbox, { timeout: 30000 });

// OTP auto-extracted at the edge — no regex needed
expect(email.otp).not.toBeNull();
await page.fill('[data-testid="otp"]', email.otp!);
await page.click('[data-testid="verify"]');

ZeroDrop extracts 4-8 digit OTP codes automatically before the email reaches your test.


GitHub Actions workflow

name: E2E Tests

on: [push, pull_request]

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

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

      - run: npm ci

      - run: npx playwright install --with-deps chromium

      - name: Generate test inbox
        id: inbox
        uses: zerodrop-dev/create-inbox@8706a59 # v1.0.0

      - name: Run E2E tests
        run: npx playwright test
        env:
          TEST_INBOX: ${{ steps.inbox.outputs.inbox }}
          SENDPIGEON_API_KEY: ${{ secrets.SENDPIGEON_API_KEY }}
          NEXT_PUBLIC_URL: ${{ secrets.STAGING_URL }}
// Use CI inbox or generate locally
const inbox = process.env.TEST_INBOX ?? mail.generateInbox();

Each CI run gets a fresh isolated inbox. Parallel matrix builds get separate inboxes — no cross-test contamination.


The full picture

Test key (local) Live key (staging) Live key + ZeroDrop (CI)
Visual inspection ✅ dashboard ✅ real inbox ✅ automated
No real emails sent
Automated in CI
Parallel test runs
OTP auto-extraction
Tests real delivery

Each stage has a purpose. Use the test key during development, the live key for manual staging verification, and the live key + ZeroDrop for automated CI.


ZeroDrop — disposable email inboxes for CI pipelines. Free, no signup, no Docker. → zerodrop.dev · docs · npm