DEV Community

Cover image for How to E2E Test SendPigeon Email Workflows in Playwright
zerodrop
zerodrop

Posted on

How to E2E Test SendPigeon Email Workflows in Playwright

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 });
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
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();
});
Enter fullscreen mode Exit fullscreen mode

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>`,
});
Enter fullscreen mode Exit fullscreen mode
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"]');
Enter fullscreen mode Exit fullscreen mode

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 }}
Enter fullscreen mode Exit fullscreen mode
// Use CI inbox or generate locally
const inbox = process.env.TEST_INBOX ?? mail.generateInbox();
Enter fullscreen mode Exit fullscreen mode

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

Top comments (0)