DEV Community

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

Posted on

How to E2E Test SendGrid Email Workflows in Playwright

SendGrid powers transactional email for millions of applications. But how do you test that your SendGrid emails actually arrive, contain the right content, and work end-to-end in CI?

This guide covers the full testing progression — from local development to automated Playwright tests in GitHub Actions.


The app we're testing

A Next.js API route that sends a verification email via SendGrid:

// app/api/auth/signup/route.ts
import sgMail from '@sendgrid/mail';

sgMail.setApiKey(process.env.SENDGRID_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 sgMail.send({
    from: 'noreply@yourapp.com',
    to: email,
    subject: 'Verify your email',
    html: `<p>Click <a href="${verifyUrl}">here</a> to verify your email.</p>`,
  });

  return Response.json({ success: true });
}
Enter fullscreen mode Exit fullscreen mode

Stage 1 — Local development: SendGrid sandbox mode

SendGrid has a sandbox mode that intercepts emails without delivering them. Enable it by passing mail_settings in your API call:

await sgMail.send({
  from: 'noreply@yourapp.com',
  to: email,
  subject: 'Verify your email',
  html: `<p>Click <a href="${verifyUrl}">here</a> to verify.</p>`,
  mailSettings: {
    sandboxMode: {
      enable: process.env.NODE_ENV === 'development',
    },
  },
});
Enter fullscreen mode Exit fullscreen mode

In sandbox mode, SendGrid processes the request and validates it but doesn't deliver the email. You can verify delivery in the SendGrid Activity Feed dashboard.

What it solves: Does my app call SendGrid correctly? Is my email template valid?

What it doesn't solve: Automated testing. You can't read emails from SendGrid's Activity Feed programmatically in a test.


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

Switch sandbox mode off for staging:

mailSettings: {
  sandboxMode: {
    enable: false,
  },
},
Enter fullscreen mode Exit fullscreen mode

Emails now go through real delivery. You can manually verify they arrive, links work, and nothing lands in spam. Catches real delivery issues like domain authentication problems.

What it solves: Does the email actually reach a real inbox end-to-end?

What it doesn't solve: Automation. You can't run this in CI without access to a real inbox your test can read.


Stage 3 — CI: SendGrid live key + ZeroDrop

For automated Playwright tests in GitHub Actions:

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

OTP flows

If your app sends a numeric OTP via SendGrid:

await sgMail.send({
  from: 'noreply@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

Using SendGrid Dynamic Templates

If you use SendGrid's Dynamic Templates, the flow is the same — ZeroDrop catches the rendered email:

await sgMail.send({
  from: 'noreply@yourapp.com',
  to: email,
  templateId: 'd-your-template-id',
  dynamicTemplateData: {
    verify_url: verifyUrl,
    username: 'Test User',
  },
});
Enter fullscreen mode Exit fullscreen mode
// ZeroDrop catches the fully rendered template output
const email = await mail.waitForLatest(inbox, { timeout: 30000 });
expect(email.magicLink).not.toBeNull();
Enter fullscreen mode Exit fullscreen mode

This tests that your Dynamic Template renders correctly and the link is valid — something sandbox mode can't verify.


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 }}
          SENDGRID_API_KEY: ${{ secrets.SENDGRID_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

The full picture

Sandbox mode (local) Live key (staging) Live key + ZeroDrop (CI)
Validates API call
No real emails sent
Tests template rendering
Automated in CI
Parallel test runs
OTP auto-extraction
Tests real delivery

Use sandbox mode 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 (1)

Collapse
 
alexshev profile image
Alex Shev

Same pattern applies here: the provider API success is not the user outcome.

The useful Playwright test proves the email content can complete the workflow. Subject, destination, link shape, token validity, and expiry behavior matter more than "SendGrid returned 202." That distinction saves a lot of false confidence in CI.