← Back to Blog

Free vs Pro OTPBox for CI: When 10 Creates/Hour Is Enough

Which tier for parallel OTP E2E? Free covers laptop and light CI; Pro unlocks matrix headroom. Decision framework, create quota, TTL, and wiring compared.

testingotpci-cdversuspricing
Free vs Pro OTPBox for CI: When 10 Creates/Hour Is Enough

Disposable inboxes for OTP and magic-link tests need enough create quota and TTL to survive the longest CI job without racing other workers. OTPBox offers a Free tier at zero cost and a Pro tier at $9/month. Both use the same API; the question is which one matches your create-rate and parallel-job pattern.

This versus post walks through when Free (laptop, single-job smoke, light CI) is enough and when Pro (matrix, parallel shards, shared NAT) becomes the deliberate answer. Facts below match the live OTPBox API and pricing. No invented competitor market share or benchmark data.

The shared problem: disposable inboxes in parallel CI

A reliable OTP test in CI needs:

  1. Isolation — a unique address per job so parallel workers do not steal codes
  2. Enough create quota — stay under the hourly ceiling on the shared egress IP
  3. TTL long enough — the inbox must outlive the longest test step that polls for a code
  4. Poll headroom — retry on 404 without hitting another rate limit

When create quota is too low or TTL expires mid-job, tests flake. See 7 Mistakes That Make Email OTP Tests Flake in CI and the CI Email OTP Checklist.

What Free gets you

From /pricing:

$0, no account

  • 10 inbox creates/hr per IP
  • 1h TTL
  • Poll /latest unrestricted
  • OTP + magic-link extraction

Only POST /api/inbox counts toward create quota. GET /api/inbox/:token and GET /api/inbox/:token/latest poll freely — no key, no limit. Create once, poll many times.

Live Free 429 body (from /docs):

{
  "error": "Free tier: 10 inbox creates/hr per IP. Need more for CI? See Pro → https://otpbox.app/pricing"
}

When Free works:

  • Laptop and local Playwright
  • Single-job GitHub Actions smoke test (one create per run)
  • Light CI where total creates across all workflows stay under 10/hr on the shared egress IP
  • Short suites that finish well under 1h from inbox create to last poll

When Free breaks:

  • Matrix strategies (OS × browser × shard) that fan out 12+ creates at once
  • Self-hosted runners behind one NAT with many developers pushing
  • Suites that need 90+ minutes from create to final verification
  • Any workflow pattern that exceeds 10 creates/hr from the same egress IP

GitHub-hosted runners use dynamic IPs from shared pools. Self-hosted runners often share one NAT IP. Treat that IP's 10/hr budget as shared across all workflows that create inboxes.

What Pro gets you

From /pricing:

$9/month

  • API key (Bearer or x-api-key)
  • 120 creates/hr
  • 6h TTL
  • Same API — more headroom for parallel CI

No seats, no teams, no SLA. You pay for create-rate and longer TTL. Polling remains unrestricted.

Head-to-head

Dimension Free Pro
Price $0, no account $9/mo
Inbox creates 10/hr per IP 120/hr with API key
Inbox TTL 1h 6h
Poll /latest Unrestricted Unrestricted
Auth on create None Authorization: Bearer KEY or x-api-key: KEY
Auth on poll None None (unauthenticated)
OTP extraction Built-in Built-in
Magic-link extraction Built-in Built-in
Best for Laptop, single smoke, light CI Matrix / parallel shards
Inbox domain *@box.otpbox.app *@box.otpbox.app

Only the create step requires Pro authentication. Poll calls stay simple:

# Free create (no key)
RESPONSE=$(curl -s -X POST https://otpbox.app/api/inbox)
TOKEN=$(echo "$RESPONSE" | jq -r '.token')

# Pro create (with key)
RESPONSE=$(curl -s -X POST https://otpbox.app/api/inbox \
  -H "Authorization: Bearer $OTPBOX_API_KEY")
TOKEN=$(echo "$RESPONSE" | jq -r '.token')

# Poll on both (no key)
OTP=$(curl -s "https://otpbox.app/api/inbox/$TOKEN/latest" | jq -r '.otp')

Choose Free when…

Create rate is low. One inbox per laptop test run, or a single GitHub Actions job that runs a few times per hour.

TTL is short. Your E2E completes in well under 60 minutes from inbox create to the last poll.

No parallel matrix. One job, one worker, sequential runs spread across hours.

Shared IP has budget. Self-hosted runners (or local dev) that do not burn 10 creates/hr from teammates.

Free pattern (from /docs)

#!/bin/bash
set -e

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

echo "Inbox: $EMAIL"

# 2. Trigger your app to send to $EMAIL
# Example: curl -X POST https://yourapp.com/signup -d "email=$EMAIL"

# 3. Poll /latest (unrestricted)
for i in {1..10}; do
  OTP=$(curl -s "https://otpbox.app/api/inbox/$TOKEN/latest" | jq -r '.otp // empty')
  if [ -n "$OTP" ] && [ "$OTP" != "null" ]; then
    echo "✓ Got OTP: $OTP"
    exit 0
  fi
  echo "  Attempt $i/10 - waiting..."
  sleep 3
done

echo "✗ Timeout"
exit 1

No secrets, no setup. Stays green as long as total creates from the egress IP stay under 10/hr.

Choose Pro when…

Matrix or parallel shards multiply creates. strategy.matrix that fans out 12 jobs at once, or Playwright with --workers=8 where each worker creates an inbox.

Shared NAT burns Free budget. Self-hosted runners or org-wide CI that shares one egress IP and routinely hits 10/hr.

Long suites need more TTL. Suites that take 90+ minutes from create to final poll. The 1h Free TTL expires mid-job; 6h Pro keeps the inbox alive.

Reliable headroom matters. Remove "will we 429?" from your CI planning when parallel OTP tests are part of every PR.

Pro wiring in GitHub Actions

Store the key as OTPBOX_API_KEY (repo or org secret). Send it only on create:

jobs:
  e2e-otp:
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      max-parallel: 4   # or remove cap with Pro
      matrix:
        shard: [1, 2, 3, 4, 5, 6, 7, 8]
    steps:
      - uses: actions/checkout@v4
      
      - name: Create OTPBox inbox
        id: inbox
        env:
          OTPBOX_API_KEY: ${{ secrets.OTPBOX_API_KEY }}
        run: |
          # Pro create with key
          RESPONSE=$(curl -s -X POST https://otpbox.app/api/inbox \
            -H "Authorization: Bearer $OTPBOX_API_KEY")
          echo "token=$(echo "$RESPONSE" | jq -r '.token')" >> "$GITHUB_OUTPUT"
          echo "email=$(echo "$RESPONSE" | jq -r '.email')" >> "$GITHUB_OUTPUT"
      
      - name: Run E2E with inbox email
        env:
          TEST_EMAIL: ${{ steps.inbox.outputs.email }}
          INBOX_TOKEN: ${{ steps.inbox.outputs.token }}
        run: npm test -- --shard=${{ matrix.shard }}/8
      
      # Poll /latest stays unauthenticated

From /docs → Using Pro in CI: send Authorization: Bearer $OTPBOX_API_KEY (or x-api-key: $OTPBOX_API_KEY) on POST /api/inbox only. Polls stay unauthenticated.

Full parallel-job patterns: GitHub Actions Matrix + OTPBox Create Quota.

The create-once-then-poll pattern (both tiers)

Rule 1: Never create inside the poll loop.

Create once per job, trigger the real send, then poll /latest until a code or link appears. Recreating the inbox on every retry is the fastest way to 429 yourself — even on Pro.

// Playwright example (Free or Pro)
import { test, expect } from '@playwright/test';

test('signup OTP flow', async ({ page }) => {
  // 1. Create inbox once
  const authHeader = process.env.OTPBOX_API_KEY
    ? { Authorization: `Bearer ${process.env.OTPBOX_API_KEY}` }
    : {};
  const inbox = await fetch('https://otpbox.app/api/inbox', {
    method: 'POST',
    headers: authHeader,
  }).then((r) => r.json());

  console.log(`Using inbox: ${inbox.email}`);

  // 2. Trigger signup
  await page.goto('https://yourapp.com/signup');
  await page.fill('#email', inbox.email);
  await page.click('button[type=submit]');

  // 3. Poll /latest (no auth)
  const otp = await expect
    .poll(
      async () => {
        const res = await fetch(
          `https://otpbox.app/api/inbox/${inbox.token}/latest`
        );
        if (!res.ok) return null;
        const data = await res.json();
        return data.otp || null;
      },
      { intervals: [3000], timeout: 30_000 }
    )
    .not.toBe(null);

  // 4. Use the code
  await page.fill('#code', otp);
  await page.click('button[type=submit]');
  await expect(page.locator('text=Welcome')).toBeVisible();
});

See How Playwright expect.poll Beats Fixed Sleeps for Email OTP for the full expect.poll pattern.

Decision framework

Your situation Tier
Laptop, single job, <1h suite Free
Matrix 2–4 jobs with max-parallel: 2 Free (stagger creates)
Matrix 8+ jobs that create at once Pro
Shared NAT runners burning 10/hr Pro
Suite needs 90+ minutes from create to poll Pro (6h TTL)
Parallel Playwright workers (each creates inbox) Pro if total workers × runs > 10/hr

Free is not a trial. It is a permanent zero-cost tier for laptop and light CI. Pro is for matrix headroom and longer TTL when parallel OTP E2E is part of every PR.

Do / don't checklist

Do

  • Create one inbox per job or worker, then poll freely
  • Log the disposable email on failure for provider debugging
  • Use max-parallel on Free to stagger creates when matrix jobs share an IP
  • Put OTPBOX_API_KEY in GitHub Actions secrets (or CI env) for Pro; omit it for Free
  • Keep polling simple — no auth required on /latest for either tier

Don't

  • Create inside retry/poll loops
  • Share one inbox across parallel workers (cross-talk)
  • Assume Free is a trial you must upgrade from — it is not
  • Add auth to poll calls — only the create needs the Pro key
  • Treat polling volume as the rate-limit risk — it is not; only creates count

Common mistakes

Burning create quota in a loop. The suite retries on 404 by creating a fresh inbox each time. Fix: create once, poll many.

Mixing Free and Pro without env flags. Half the team uses Pro keys, half uses Free, and nobody documents which workflows expect which. Fix: OTPBOX_API_KEY as optional; Free works when absent, Pro works when present.

Hitting 1h TTL on long suites. The E2E takes 75 minutes; Free inboxes expire at 60. Fix: Pro for 6h TTL.

Assuming Free is insufficient. Many teams run green on Free indefinitely because one create per job × a few runs per hour stays under 10/hr. You do not need Pro unless you hit the wall.

See 7 Mistakes That Make Email OTP Tests Flake in CI for the full list.

Related reading

Resources

  • OTPBox — create a disposable inbox in the browser
  • OTPBox API — POST /api/inbox, GET .../latest, Free vs Pro limits
  • Pricing — Free $0 (10/hr, 1h TTL) vs Pro $9/mo (120/hr, 6h TTL)

Free is not a trial. It is a permanent tier for laptop and light CI. Pro is for matrix headroom and longer TTL when parallel OTP E2E becomes part of every PR. Both use the same create-once-then-poll API — pick the tier that matches your create-rate and concurrency pattern.