How to Automate Email Verification & OTP Workflows in Playwright and Pytest
End-to-end testing for authentication flows often fails at a single choke point: email delivery.
Whether verifying user signups, testing password resets, or checking multi-factor one-time passwords (OTP), relying on real email accounts (like a shared Gmail or Outlook inbox) introduces serious bottlenecks:
- Rate Limits & IP Blocks: Major email providers throttle incoming connections and trigger CAPTCHAs during concurrent CI runs.
- State Pollution: Shared mailboxes accumulate thousands of messages, making subject-line matching slow and error-prone.
- Flaky Race Conditions: Transactional mail delivery varies from 2 seconds to 3 minutes depending on outbound queues and greylisting.
- Cleanup Overhead: Purging test accounts after test execution adds maintenance burden.
Using ephemeral inboxes with programmatic lifespans solves this problem. Instead of persistent inboxes, tests generate throwaway addresses that receive messages in volatile memory and automatically self-destruct.
Below is an end-to-end guide showing how to integrate disposable inboxes into automated test suites across Playwright (Node.js) and Pytest (Python).
Selecting the Right Inbox Lifespan for Your Test Suite
Not all test runs have the same execution profile. Choosing the wrong mailbox duration can cause premature expiration or unnecessary resource waste:
| Test Profile | Recommended Lifespan | Typical Use Case | Live Tool Reference |
|---|---|---|---|
| Fast CI Unit / API | 5 Minutes | Single OTP extraction, rapid smoke tests | 5-Minute Disposable Mail |
| Standard Auth E2E | 10 Minutes | Basic signup, activation link clicking | 10-Minute Ephemeral Mail |
| Queued Webhooks | 15 Minutes | Third-party SMTP queues (SES/SendGrid delay) | 15-Minute Mail Buffer |
| Multi-Stage Onboarding | 20 Minutes | Multi-tenant setup, team invites, KYC | 20-Minute Mailbox |
| Staging & Integration | 25 Minutes | End-to-end purchasing, checkout validation | 25-Minute Disposable Mail |
| Manual QA & Bug Triage | 30 Minutes | Cross-browser debugging, exploratory testing | 30-Minute Temporary Mail |
Implementation 1: Playwright (Node.js) with Dynamic OTP Extraction
Here is a complete Playwright test suite using the zero-dependency client tempmail10min-client and regex extractor tempmail-otp-extractor.
1. Install Dependencies
npm install -D @playwright/test
npm install tempmail10min-client tempmail-otp-extractor
2. Playwright Test Script (auth-signup.spec.js)
const { test, expect } = require('@playwright/test');
const TempMail10min = require('tempmail10min-client');
const { extractOtp, extractMagicLink } = require('tempmail-otp-extractor');
test.describe('Automated User Registration & OTP Verification', () => {
let mailClient;
let testEmail;
test.beforeEach(async () => {
// 1. Initialize a 5-minute inbox for fast smoke testing
// For slower third-party delivery pipelines, bump duration to 15:
// https://temp-mail10min.online/15-minute-mail
mailClient = new TempMail10min({ duration: 5 });
const mailbox = await mailClient.createMailbox();
testEmail = mailbox.email;
});
test('signs up and verifies 6-digit OTP code', async ({ page }) => {
// Navigate to registration page
await page.goto('https://app.example.com/register');
// Fill registration credentials
await page.fill('input[name="name"]', 'QA Test Runner');
await page.fill('input[name="email"]', testEmail);
await page.fill('input[name="password"]', 'SecureP@ssw0rd2026!');
await page.click('button[type="submit"]');
// Expect OTP input prompt
await expect(page.locator('input[name="otp"]')).toBeVisible();
// 2. Poll the ephemeral inbox for incoming verification email
const message = await mailClient.waitForMessage({
timeoutMs: 45000,
pollIntervalMs: 3000
});
expect(message).toBeDefined();
expect(message.subject).toContain('Verification Code');
// 3. Extract 6-digit OTP using regex heuristic parser
const otpCode = extractOtp(message.body_text || message.body_html);
expect(otpCode).toMatch(/^\d{6}$/);
// 4. Submit OTP into application
await page.fill('input[name="otp"]', otpCode);
await page.click('button#verify-btn');
// Verify successful onboarding redirect
await expect(page).toHaveURL(/.*dashboard/);
await expect(page.locator('h1')).toContainText('Welcome to Dashboard');
});
test('verifies email via activation magic link', async ({ page }) => {
// Use 15-minute lifespan for multi-stage link checks
// Interactive tool: https://temp-mail10min.online/15-minute-mail
await page.goto('https://app.example.com/signup');
await page.fill('input[name="email"]', testEmail);
await page.click('button#request-link');
const message = await mailClient.waitForMessage({ timeoutMs: 30000 });
const activationLink = extractMagicLink(message.body_html);
expect(activationLink).toBeTruthy();
// Navigate directly to the tokenized activation URL
await page.goto(activationLink);
await expect(page.locator('.status-badge')).toContainText('Account Confirmed');
});
});
Implementation 2: Pytest & Python Automation
If your test suite uses Python, you can use the official tempmail10min library:
1. Install Package
pip install pytest pytest-playwright tempmail10min
2. Pytest Test Script (test_auth_flow.py)
import re
import pytest
from playwright.sync_api import Page, expect
from tempmail10min import TempMail10min
def test_registration_with_temporary_email(page: Page):
# Initialize a 10-minute temporary inbox
# Webmail equivalent: https://temp-mail10min.online/
client = TempMail10min(duration=10)
mailbox = client.create_mailbox()
temp_email = mailbox["email"]
# Open target registration form
page.goto("https://staging.example.com/signup")
page.fill("#email-field", temp_email)
page.fill("#password-field", "TestSecure123!")
page.click("button#register")
# Wait for incoming OTP via client polling helper
email_data = client.wait_for_message(timeout=60, poll_interval=3)
assert email_data is not None, "Timed out waiting for verification email"
# Regex parse 6-digit code
body = email_data.get("body_text", "")
match = re.search(r"\b\d{6}\b", body)
assert match is not None, f"No 6-digit OTP found in body: {body}"
otp_code = match.group(0)
# Enter OTP into verification UI
page.fill("#otp-input", otp_code)
page.click("button#submit-otp")
expect(page.locator(".welcome-header")).to_contain_text("Dashboard")
Handling Delivery Delays & Greylisting in CI/CD
When testing against staging environments, transactional mail deliverability behaves differently than production. Two primary issues frequently appear:
1. Greylisting Delays (SMTP Error 451)
Mail transfer agents (MTAs) often reject initial delivery attempts from unrecognized sending IPs with a temporary 4xx error. Legitimate mail servers retry within 1 to 5 minutes.
- If your staging mail server encounters greylisting, a 5-minute inbox may expire before the retry arrives.
- Fix: Use an extended 15-Minute Mail Buffer or 20-Minute Mail Session in your test configuration to accommodate MTA retry intervals.
- Read our deep dive on Why Websites Block Temporary Email & How Detection Works.
2. Complex Multi-Step Onboarding
Enterprise applications frequently require team confirmation: User A registers, invites User B, and User B must accept before the test completes.
- Running both signups in a single test run takes 8–12 minutes.
- Fix: Provision two isolated instances with a 25-Minute Disposable Mailbox or 30-Minute Temporary Inbox. This guarantees neither session drops while handling complex workflows.
Defensive Coding: Filtering Disposable Inboxes in Production
While disposable inboxes are essential for automated QA, SaaS applications often need to protect promotional credits and free trials from abuse.
If you are building authentication defenses, pair your verification flow with a normalization and detection engine:
const { isDisposableEmail, normalizeEmail } = require('tempmail10min-validator');
function validateRegistration(emailInput) {
// 1. Strip subaddressing (+tag) and Gmail dot tricks
const cleanEmail = normalizeEmail(emailInput);
// 2. Check domain against disposable provider lists
if (isDisposableEmail(cleanEmail)) {
return {
allowed: false,
reason: 'Disposable email addresses are not permitted for trial accounts.'
};
}
return { allowed: true, email: cleanEmail };
}
For performance benchmarks comparing delivery speeds across disposable mail providers, refer to our 8 Best Disposable Temp Mail Services Tested.
Summary & Best Practices
- Keep durations minimal for smoke tests: Use 5-Minute Mail to maintain fast CI job completion.
- Buffer for asynchronous queues: Scale to 15-Minute Mail when testing SendGrid or SES queues.
- Use regex parsers for OTPs: Never hardcode message body indexes; parse using keyword proximity.
- Clean up memory automatically: Rely on volatile RAM-based deletion to keep test databases sanitized.
Need an interactive disposable inbox right now? Launch a zero-signup mailbox at TempMail10min.
Top comments (0)