If you've tried to test Supabase authentication flows end-to-end in a CI pipeline, you've probably hit the same wall: the email.
Supabase's signup flow sends a verification email. Your Playwright test needs to click that verification link or enter that OTP. But the email lands in... nowhere your test can reach.
The standard advice is to use InBucket — Supabase's local email interceptor. But InBucket requires running Supabase locally via the CLI, which means Docker in CI. That's a Docker service block in your GitHub Actions YAML, slower builds, and a local Supabase instance that drifts from production.
There's a simpler way.
The Problem with Existing Approaches
InBucket / Local Supabase: InBucket is great for local development — it ships with supabase start and catches emails instantly. But it requires Docker, a full local Supabase stack, and doesn't test against your real Supabase project. In CI, that means a Docker service block, slower builds, and a local instance that can drift from production.
Mocking Supabase: You're not testing the real flow. Your tests pass while production auth could be broken.
Database triggers to set predictable OTPs: Clever, but adds complexity and requires direct database access in CI.
Shared Gmail inbox: Race conditions everywhere in parallel test runs.
The ZeroDrop Approach
ZeroDrop gives you real disposable email inboxes caught at Cloudflare's edge. No Docker. No local Supabase. No shared inbox collisions.
You send a real signup to your real Supabase project, using a ZeroDrop inbox as the email address. The verification email arrives in under a second. Your test reads email.otp or email.magicLink — auto-extracted, no regex needed.
Your app → Supabase → sends verification email → ZeroDrop catches it → your test reads email.otp
Setup
Install the SDK:
npm install zerodrop-client
No API key needed. No signup required.
Testing Supabase Email Verification (OTP Flow)
Supabase's default signup flow sends a 6-digit OTP for email verification. Here's how to test it end-to-end:
import { test, expect } from '@playwright/test';
import { ZeroDrop } from 'zerodrop-client';
const mail = new ZeroDrop();
test('Supabase signup with email verification', async ({ page }) => {
// Generate a unique inbox for this test run
const inbox = mail.generateInbox();
// Navigate to your signup page
await page.goto('/signup');
// Fill in the signup form with the ZeroDrop inbox
await page.fill('[name="email"]', inbox);
await page.fill('[name="password"]', 'TestPassword123!');
await page.click('[type="submit"]');
// Wait for Supabase to send the verification email
// ZeroDrop catches it at the edge — arrives in under 1 second
const email = await mail.waitForLatest(inbox, { timeout: 15000 });
// OTP is auto-extracted — no regex needed
expect(email.otp).toBeTruthy();
// Enter the OTP on the verification screen
await page.fill('[name="otp"]', email.otp!);
await page.click('[type="submit"]');
// Verify the user is now authenticated
await expect(page).toHaveURL('/dashboard');
});
Testing Supabase Magic Link Flow
If you're using Supabase's passwordless magic link auth:
import { test, expect } from '@playwright/test';
import { ZeroDrop } from 'zerodrop-client';
const mail = new ZeroDrop();
test('Supabase magic link login', async ({ page }) => {
const inbox = mail.generateInbox();
// Request a magic link
await page.goto('/login');
await page.fill('[name="email"]', inbox);
await page.click('button:has-text("Send magic link")');
// Catch the magic link email
const email = await mail.waitForLatest(inbox, { timeout: 15000 });
// magicLink is auto-extracted from the email body
expect(email.magicLink).toBeTruthy();
// Navigate directly to the magic link
await page.goto(email.magicLink!);
// User should now be authenticated
await expect(page).toHaveURL('/dashboard');
});
Testing Password Reset
test('Supabase password reset flow', async ({ page }) => {
const inbox = mail.generateInbox();
// First, create a user (or use an existing test account)
// Then trigger a password reset
await page.goto('/forgot-password');
await page.fill('[name="email"]', inbox);
await page.click('button:has-text("Send reset email")');
// Catch the reset email
const email = await mail.waitForLatest(inbox, { timeout: 15000 });
// Navigate to the reset link
await page.goto(email.magicLink!);
// Set a new password
await page.fill('[name="password"]', 'NewPassword123!');
await page.fill('[name="confirmPassword"]', 'NewPassword123!');
await page.click('[type="submit"]');
await expect(page).toHaveURL('/login');
});
Running in GitHub Actions CI
No Docker required. Add ZeroDrop's GitHub Action to generate isolated inboxes per test run:
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
- run: npx playwright test
env:
NEXT_PUBLIC_SUPABASE_URL: ${{ secrets.SUPABASE_URL }}
NEXT_PUBLIC_SUPABASE_ANON_KEY: ${{ secrets.SUPABASE_ANON_KEY }}
That's it. No supabase start. No Docker service block. ZeroDrop inboxes are generated client-side — no network request, no setup.
Parallel Test Runs — No Collisions
Because generateInbox() creates a unique inbox on every call with no network request, parallel CI runs are safe by default:
// Each worker gets its own inbox — no shared state, no collisions
test.describe.configure({ mode: 'parallel' });
test('user A signup', async ({ page }) => {
const inbox = mail.generateInbox(); // unique to this test
// ...
});
test('user B signup', async ({ page }) => {
const inbox = mail.generateInbox(); // different inbox, zero collision risk
// ...
});
50 parallel workers. 50 isolated inboxes. Zero race conditions.
InBucket vs ZeroDrop — When to Use Which
InBucket is the right choice for local development — it's built into Supabase's local stack and works great there. ZeroDrop is the right choice for CI pipelines against real Supabase projects.
| InBucket | ZeroDrop | |
|---|---|---|
| Best for | Local development | CI pipelines |
| Docker required | ✓ | ✗ |
| Tests real Supabase project | ✗ | ✓ |
| OTP auto-extraction | ✗ | ✓ |
| Magic link auto-extraction | ✗ | ✓ |
| Parallel-safe | ✗ | ✓ |
| CI setup complexity | High | None |
Conclusion
Testing Supabase auth flows end-to-end doesn't have to mean running a full local stack. ZeroDrop gives you real email delivery verification against your real Supabase project, with no Docker, no InBucket, and no shared inbox collisions.
email.otp and email.magicLink are auto-extracted at Cloudflare's edge before they reach your test suite. No regex. No HTML parsing. No race conditions.
Free to use. No signup required. Works in CI out of the box.
→ zerodrop.dev · npm · GitHub Action
Top comments (0)