← Back to Blog

Disposable Email for CI OTP Testing

Use disposable email to test OTP flows in CI/CD pipelines. Learn practical patterns for parallel testing, isolation, and reliable verification code extraction.

testingci-cdautomationchecklist
Disposable Email for CI OTP Testing

When your CI pipeline needs to test email verification flows, disposable email inboxes solve the problem of state pollution, parallel execution, and test reliability. Unlike shared Gmail accounts or local SMTP servers, disposable inboxes give each test run a unique address that expires automatically.

This guide shows practical patterns for using disposable email in CI, what to avoid, and when alternatives make more sense.

Why CI Needs Disposable Email

Testing OTP verification in continuous integration creates challenges that don't exist in local development:

  1. Parallel test runs — Multiple builds can't share the same inbox
  2. No manual intervention — You can't open Gmail and copy-paste codes
  3. State isolation — Old test emails shouldn't interfere with new ones
  4. Real delivery testing — Mocking skips SMTP, DNS, and deliverability issues

Traditional approaches fall short:

  • Shared team inbox — Tests race for the same messages; cleanup is error-prone
  • IMAP polling — Requires credentials, complex parsing, slow execution
  • Local SMTP catchers — Great for dev, but add Docker/setup overhead in CI
  • Mocking email service — Fast tests, but production email breaks go undetected

Disposable email services designed for testing provide instant inbox creation, REST API access, and built-in OTP extraction—no parsing required.

Do's and Don'ts: CI OTP Testing Checklist

✓ Do This

  • Create a unique inbox per test run — Prevents state pollution and race conditions
  • Poll for OTP with timeouts — Email typically arrives in 1-5 seconds; fail after 30 seconds
  • Use the /latest endpoint — Returns extracted OTP and link in JSON, no regex needed
  • Test failure paths too — Wrong codes, expired tokens, rate limits, invalid email formats
  • Log the inbox email address — Makes debugging failed CI runs much easier
  • Run tests in parallel safely — Each test gets its own inbox; no conflicts
  • Extract both OTP and magic links — Some flows send one or both

✗ Don't Do This

  • Reuse inboxes across tests — State leakage causes flaky failures
  • Hardcode test email addresses — Always create inboxes dynamically via API
  • Skip timeout handling — Infinite polling wastes CI minutes and hides real issues
  • Ignore deliverability — Test with real email to catch SPF/DKIM problems early
  • Parse raw email bodies — Use services with built-in extraction; maintain less code
  • Commit inbox tokens — They're temporary and expire; no need to store them

Pattern: Create, Trigger, Poll, Verify

The standard workflow for testing OTP flows in CI:

#!/bin/bash
set -e

# 1. Create disposable inbox
echo "Creating test inbox..."
RESPONSE=$(curl -s -X POST https://otpbox.app/api/inbox)
TOKEN=$(echo "$RESPONSE" | jq -r '.token')
EMAIL=$(echo "$RESPONSE" | jq -r '.email')

echo "Test email: $EMAIL"

# 2. Trigger your app's verification flow
echo "Triggering signup..."
curl -X POST https://yourapp.com/api/signup \
  -H "Content-Type: application/json" \
  -d "{\"email\": \"${EMAIL}\", \"password\": \"Test1234!\"}"

# 3. Poll for OTP with timeout
echo "Waiting for OTP..."
MAX_ATTEMPTS=10
SLEEP_SECONDS=3

for i in $(seq 1 $MAX_ATTEMPTS); do
  RESPONSE=$(curl -s https://otpbox.app/api/inbox/${TOKEN}/latest)
  OTP=$(echo "$RESPONSE" | jq -r '.otp // empty')
  
  if [ -n "$OTP" ] && [ "$OTP" != "null" ]; then
    echo "✓ Received OTP: $OTP"
    
    # 4. Verify the code
    echo "Verifying OTP..."
    curl -X POST https://yourapp.com/api/verify \
      -H "Content-Type: application/json" \
      -d "{\"email\": \"${EMAIL}\", \"code\": \"${OTP}\"}"
    
    echo "✓ Test passed"
    exit 0
  fi
  
  echo "  Attempt $i/$MAX_ATTEMPTS - no message yet"
  sleep $SLEEP_SECONDS
done

echo "✗ Timeout: No OTP received after 30 seconds"
exit 1

Key points:

  • POST /api/inbox returns { email, token } as separate fields, not token@domain
  • Poll /api/inbox/{token}/latest — returns { otp, link, ... } when message arrives
  • Empty inbox returns 404 or error; check for null in your polling loop
  • 30-second timeout (10 attempts × 3 seconds) handles normal latency

JavaScript Example for Playwright/Cypress

Testing OTP flows in end-to-end test frameworks:

// Helper functions for OTPBox
async function createInbox() {
  const response = await fetch('https://otpbox.app/api/inbox', {
    method: 'POST'
  });
  const data = await response.json();
  return { email: data.email, token: data.token };
}

async function pollForOtp(token, maxAttempts = 10) {
  for (let i = 0; i < maxAttempts; i++) {
    const response = await fetch(
      `https://otpbox.app/api/inbox/${token}/latest`
    );
    
    if (response.ok) {
      const data = await response.json();
      if (data.otp) {
        return data.otp;
      }
    }
    
    // Wait 3 seconds before retrying
    await new Promise(resolve => setTimeout(resolve, 3000));
  }
  
  throw new Error('OTP not received after 30 seconds');
}

// Playwright test
test('user can verify email with OTP', async ({ page }) => {
  // 1. Create test inbox
  const { email, token } = await createInbox();
  console.log(`Test inbox: ${email}`);
  
  // 2. Sign up with disposable email
  await page.goto('https://yourapp.com/signup');
  await page.fill('input[name="email"]', email);
  await page.fill('input[name="password"]', 'Test1234!');
  await page.click('button[type="submit"]');
  
  // 3. Wait for verification page
  await page.waitForSelector('text=Enter verification code');
  
  // 4. Poll for OTP
  const otp = await pollForOtp(token);
  console.log(`Received OTP: ${otp}`);
  
  // 5. Enter code and verify
  await page.fill('input[name="code"]', otp);
  await page.click('button:has-text("Verify")');
  
  // 6. Confirm success
  await page.waitForSelector('text=Email verified');
});

This pattern works with Playwright, Puppeteer, Cypress, or any framework that can make HTTP requests alongside browser automation.

When to Use Disposable Email vs Alternatives

Use Disposable Email When:

  • Testing in CI/CD — You need automated, parallel-safe email testing
  • E2E tests require real delivery — Validates SMTP, DNS, email templates
  • No infrastructure setup allowed — SaaS tools work without Docker or credentials
  • Multiple concurrent test runs — Each gets its own inbox with no collisions

Use Alternatives When:

  • Local development onlyMailpit or MailHog run locally, capture all mail, require no external services
  • Offline testingMocking the email service works for unit tests without network calls
  • Advanced debuggingMailtrap or Mailosaur offer search, webhooks, and team features (paid)
  • Public temp mail neededGuerrilla Mail or Mailinator work for casual use, but lack APIs and privacy

For CI automation, disposable email with an API offers the best trade-off: real delivery, simple integration, and no cost.

Category Comparison: Disposable Email Services

Different types of email testing tools solve different problems:

Category Examples Best For API Access CI-Friendly
OTP-First Services OTPBox CI automation, verification testing Yes Yes
Local SMTP Catchers Mailpit, MailHog Dev environments, offline testing Limited Requires setup
Public Temp Mail Guerrilla Mail, Mailinator Casual signups, one-time use Unofficial No
Paid Email APIs Mailtrap, Mailosaur Teams, advanced features Yes Yes

Choose based on your constraints: free vs paid, local vs cloud, simple vs feature-rich.

Common Pitfalls and Fixes

Pitfall: Tests fail with "No OTP received"

Problem: Email delivery took longer than your timeout, or the service is down.

Fix:

  • Check inbox creation succeeded: log the email address
  • Verify your app actually sent email: check send logs
  • Increase timeout to 45 seconds if latency is consistently high
  • Add retry logic if the service occasionally has downtime

Pitfall: Parallel tests interfere with each other

Problem: Multiple test runs try to use the same inbox.

Fix:

  • Always create a fresh inbox in each test's setup/before block
  • Never reuse inbox tokens across tests or test files
  • Use unique inbox per test, not per test suite

Pitfall: OTP extraction returns null

Problem: The email arrived, but the OTP wasn't detected.

Fix:

  • Verify your email contains a numeric code (typically 4-8 digits)
  • Check the /latest response—does link have a value instead?
  • Some emails only send magic links, not OTPs; extract the link

Pitfall: Rate limits hit during test runs

Problem: Too many requests in a short time.

Fix:

  • OTPBox allows 10 requests per hour per IP (as per docs)
  • Don't poll faster than every 3 seconds
  • Reuse the same inbox within a test; don't recreate unnecessarily

Related Articles

Resources

Testing OTP flows in CI doesn't require complex IMAP libraries or shared Gmail accounts. With disposable email, each test gets a unique inbox, polls for the code via REST API, and cleans up automatically—keeping your test suite fast, reliable, and maintainable.