Every app that sends a verification email presents the same challenge for developers, QA engineers, and automation teams: how do you automate the part where a human is supposed to read an email and type a code?
This is the complete guide to solving that problem in 2026. We cover every angle — scripted automation, test framework integration, AI agent workflows, CI/CD pipelines, and no-code options — using FreeCustom.Email, the only disposable email provider with an official CLI, official SDKs, WebSocket real-time delivery, and a dedicated OTP extraction endpoint.
What Signup Automation Actually Needs
Before picking a tool, understand exactly what a signup automation requires from a disposable email service:
Requirement | Why it matters |
|---|---|
Programmatic inbox creation | Can't rely on a fixed address; parallel tests need isolated inboxes |
Custom inbox address | Some apps validate the format or domain; random addresses must be controllable |
Real-time or fast delivery | Tight CI timeouts; no minutes of polling |
OTP extraction | No regex maintenance; works across different email formats |
Parallel isolation | Multiple tests must not share inboxes or receive each other's codes |
Cleanup | Long-running CI must not accumulate thousands of stale inboxes |
CI-compatible auth | No browser popup in a headless runner |
Rate limits that match scale | 1 req/sec won't cut it for 50 parallel jobs |
FreeCustom.Email is the only disposable email service that satisfies every item on this list.
The Core Three-Step Pattern
All signup automation — regardless of language, framework, or tool — follows one pattern:
1. Create fresh inbox → get an address
2. Submit address in signup → app sends verification email
3. Extract OTP / link → complete verificationThe difference between approaches is how smoothly each step executes.
Method 1: fce CLI (Fastest for Scripts and Local Dev)
The fce CLI is the simplest entry point. Install it once and every step is one command:
bash
# Install
curl -fsSL freecustom.email/install.sh | sh
# Authenticate (once, saves to keychain)
fce login
# Full signup automation in 4 lines:
INBOX=$(fce inbox add random)
curl -s -X POST https://myapp.com/signup -d "email=$INBOX&password=Test1234!"
OTP=$(fce otp "$INBOX" | grep "OTP ·" | awk '{print $NF}')
curl -s -X POST https://myapp.com/verify -d "email=$INBOX&otp=$OTP"OTP output from fce otp:
────────────────────────────────────────────────
OTP
────────────────────────────────────────────────
OTP · 212342
From · noreply@myapp.com
Subj · Your verification code
Time · 20:19:54No regex. No email parsing. No second browser tab. For more on the CLI: freecustom.email/api/cli — full install options including Homebrew, Scoop, Chocolatey, npm, and Go at github.com/DishIs/fce-cli.
fce dev — the one-command shortcut
For local development, fce dev creates an inbox and starts watching it simultaneously:
bash
fce dev
# → dev-fy8x@ditcloud.info created
# → WebSocket open, emails appear in real time
# → First OTP highlighted automaticallyThis is the fastest way to test a signup flow manually while building your app. See How to Receive OTP Without Using Your Real Email (2026) for more on this use case.
Method 2: JavaScript / TypeScript SDK
For Node.js applications and test suites, the official SDK gives clean async/await OTP extraction:
bash
npm install freecustom-emailAutomated signup flow
typescript
import { FreecustomEmailClient } from 'freecustom-email';
import fetch from 'node-fetch';
const fce = new FreecustomEmailClient({ apiKey: process.env.FCE_API_KEY! });
const APP = process.env.APP_URL!;
async function automateSignup(): Promise<{ email: string; otp: string }> {
// Step 1: Fresh inbox
const email = `auto-${Date.now()}@ditmail.info`;
await fce.inboxes.register(email);
console.log(`Created inbox: ${email}`);
// Step 2: Submit signup
const signupRes = await fetch(`${APP}/api/signup`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password: 'AutoTest123!' }),
});
if (!signupRes.ok) throw new Error(`Signup failed: ${signupRes.status}`);
console.log('Signup submitted');
// Step 3: OTP — no polling, no regex, built-in wait
const otp = await fce.otp.waitFor(email, { timeout: 30_000 });
console.log(`OTP: ${otp}`);
// Step 4: Complete verification
const verifyRes = await fetch(`${APP}/api/verify`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, otp }),
});
if (!verifyRes.ok) throw new Error(`Verify failed: ${verifyRes.status}`);
console.log('Verified!');
await fce.inboxes.unregister(email);
return { email, otp };
}
automateSignup().then(console.log);Parallel signup automation
typescript
async function runParallel(count: number) {
const tasks = Array.from({ length: count }, (_, i) =>
automateSignup().catch(err => ({ error: err.message, i }))
);
const results = await Promise.all(tasks);
const passed = results.filter(r => !('error' in r)).length;
console.log(`${passed}/${count} passed`);
return results;
}
runParallel(10); // 10 parallel signup flowsBecause each signup creates its own inbox, 10 parallel runs have zero interference.
Method 3: Python SDK
bash
pip install freecustom-email httpxpython
import asyncio
import os
import httpx
from freecustom_email import FreeCustomEmail
fce = FreeCustomEmail(api_key=os.environ["FCE_API_KEY"])
APP = os.environ["APP_URL"]
async def automate_signup() -> dict:
# Fresh inbox
email = f"auto-{int(asyncio.get_event_loop().time())}@ditmail.info"
await fce.inboxes.register(email)
print(f"Inbox: {email}")
async with httpx.AsyncClient() as client:
# Submit signup
r = await client.post(f"{APP}/api/signup",
json={"email": email, "password": "AutoTest123!"})
r.raise_for_status()
# Extract OTP — SDK handles waiting
otp = await fce.otp.wait_for(email, timeout=30)
print(f"OTP: {otp}")
# Complete verification
r2 = await client.post(f"{APP}/api/verify",
json={"email": email, "otp": otp})
r2.raise_for_status()
await fce.inboxes.unregister(email)
return {"email": email, "otp": otp}
# Run parallel
async def main():
results = await asyncio.gather(*[automate_signup() for _ in range(5)])
print(results)
asyncio.run(main())Method 4: Playwright Integration
Playwright is the best choice for browser-based signup automation because it interacts with the actual UI, not just the API. The FCE fixture pattern ensures each test worker gets an isolated inbox:
typescript
// playwright.config.ts — 4 parallel workers
export default defineConfig({ workers: 4, testDir: './tests' });
// tests/signup.spec.ts
import { test, expect } from '@playwright/test';
import { FreecustomEmailClient } from 'freecustom-email';
const fce = new FreecustomEmailClient({ apiKey: process.env.FCE_API_KEY! });
test.beforeEach(async ({}, testInfo) => {
testInfo['inbox'] = `pw-${testInfo.workerIndex}-${Date.now()}@ditmail.info`;
await fce.inboxes.register(testInfo['inbox']);
});
test.afterEach(async ({}, testInfo) => {
await fce.inboxes.unregister(testInfo['inbox']).catch(() => {});
});
test('signup flow', async ({ page }, testInfo) => {
const inbox = testInfo['inbox'];
await page.goto('/signup');
await page.fill('#email', inbox);
await page.fill('#password', 'Test123!');
await page.click('button[type="submit"]');
await page.waitForSelector('#otp-input');
const otp = await fce.otp.waitFor(inbox, { timeout: 30_000 });
await page.fill('#otp-input', otp);
await page.click('button[type="submit"]');
await expect(page).toHaveURL(/dashboard/);
});Full Playwright guide: OTP API for Playwright: Automate Email Verification Without Regex (2026)
Method 5: Selenium Integration
python
# Python Selenium
from selenium import webdriver
from selenium.webdriver.common.by import By
from freecustom_email import FreeCustomEmail
import asyncio, os
fce = FreeCustomEmail(api_key=os.environ["FCE_API_KEY"])
opts = webdriver.ChromeOptions()
opts.add_argument("--headless")
driver = webdriver.Chrome(options=opts)
inbox = f"sel-{int(asyncio.get_event_loop().time())}@ditmail.info"
asyncio.run(fce.inboxes.register(inbox))
driver.get("https://myapp.com/signup")
driver.find_element(By.ID, "email").send_keys(inbox)
driver.find_element(By.ID, "password").send_keys("Test123!")
driver.find_element(By.CSS_SELECTOR, "button[type='submit']").click()
otp = asyncio.run(fce.otp.wait_for(inbox, timeout=30))
driver.find_element(By.ID, "otp-input").send_keys(otp)
driver.find_element(By.CSS_SELECTOR, "button[type='submit']").click()
driver.quit()
asyncio.run(fce.inboxes.unregister(inbox))Full Selenium guide: Temp Mail API for Selenium: Automate Email Verification (2026)
Method 6: OpenClaw AI Agent (Zero Code)
With OpenClaw or any AI agent that can run shell commands, describe the task in plain English:
"Automate a complete signup flow for https://myapp.com: create a temp inbox, fill the signup form, extract the OTP, and complete verification. Use the fce CLI (already logged in)."
The agent runs:
fce inbox add random → dev-fy8x@ditcloud.info
[curl signup endpoint]
fce otp dev-fy8x@ditcloud.info → 212342
[curl verify endpoint]
fce inbox remove dev-fy8x@ditcloud.infoFull automation guide: AI Agent Email Automation with OpenClaw and n8n (2026)
Method 7: n8n Visual Workflow
For teams that prefer a visual automation canvas without code:
[Trigger]
→ [Execute Command: fce inbox add random]
→ [HTTP Request: POST /api/signup { email: {{stdout}} }]
→ [Execute Command: fce otp {{inbox}}]
→ [Code: parse OTP from stdout]
→ [HTTP Request: POST /api/verify { otp: {{otp}} }]
→ [Slack: "Signup automation passed"]See freecustom.email/api/automation/n8n for the full node configuration guide.
CI/CD: GitHub Actions (Complete)
yaml
# .github/workflows/signup-automation.yml
name: Signup Automation E2E
on:
push:
branches: [main]
schedule:
- cron: '0 */6 * * *' # Every 6 hours
jobs:
signup-test:
runs-on: ubuntu-latest
strategy:
matrix:
shard: [1, 2, 3, 4] # 4 parallel jobs, each with isolated inboxes
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20 }
- name: Install fce CLI
run: curl -fsSL freecustom.email/install.sh | sh
- name: Install deps
run: npm ci
- name: Run signup automation (shard ${{ matrix.shard }})
env:
FCE_API_KEY: ${{ secrets.FCE_API_KEY }}
APP_URL: ${{ vars.STAGING_URL }}
SHARD: ${{ matrix.shard }}
run: node scripts/signup-automation.js
- name: Cleanup on failure
if: failure()
env:
FCE_API_KEY: ${{ secrets.FCE_API_KEY }}
run: |
# List and remove any inboxes created by this shard
fce inbox list | grep "auto-" | while read addr; do
fce inbox remove "$addr" || true
doneHandling Different Email Types
Not all verification emails contain a numeric OTP. Here is how to handle each:
Numeric OTP (most common)
typescript
const otp = await fce.otp.waitFor(inbox);
// → "212342"
await page.fill('#otp-input', otp);Magic link (verification URL)
typescript
const messages = await fce.messages.list(inbox);
const msg = await fce.messages.get(inbox, messages[0].id);
const link = msg.verification_link;
await page.goto(link);Alphanumeric token
typescript
const messages = await fce.messages.list(inbox);
const msg = await fce.messages.get(inbox, messages[0].id);
// msg.body contains the full email text/html
const token = msg.body.match(/token=([A-Za-z0-9]+)/)?.[1];Avoiding Disposable Domain Blockers
Some apps validate the email domain at signup and reject known disposable domains. Solutions:
Option 1: Custom domain (Growth plan)
Register and verify your own domain in the FCE dashboard. Then create inboxes like test-001@automation.yourcompany.com.
Option 2: Use FCE's less-known domains
FCE provides multiple domains. Run fce domains to list all available on your plan — some are less likely to appear on blocklists.
Option 3: Use the Developer plan with custom domains for test teams
See Private or Custom Domains for the full setup guide.
Related: Getting Around 'Work Email' Requirements Ethically (2026)
Complete Approach Comparison
Approach | Setup | OTP extraction | Parallel safe | Browser needed | CI-ready |
|---|---|---|---|---|---|
fce CLI | 1 command | Auto | ✓ | ✗ | ✓ |
JS/TS SDK |
| Auto | ✓ | ✗ | ✓ |
Python SDK |
| Auto | ✓ | ✗ | ✓ |
Playwright + SDK | Fixture setup | Auto | ✓ | ✓ | ✓ |
Selenium + SDK | Fixture setup | Auto | ✓ | ✓ | ✓ |
OpenClaw AI agent |
| Auto | Partial | ✗ | ✓ |
n8n workflow | n8n + fce | Auto | ✓ | ✗ | ✓ |
REST API direct | HTTP client | Manual parse | ✓ | ✗ | ✓ |
Mailinator API | API key | Manual regex | ✓ | ✗ | Partial |
Gmail IMAP | OAuth setup | Manual parse | ✗ | ✗ | ✗ |
Frequently Asked Questions
How many inboxes can I create per minute? Depends on your plan. Free: 1/sec. Developer: 10/sec. Startup: 25/sec. Growth: 50/sec. Enterprise: 100/sec. For parallel test suites, Growth covers most needs.
What happens if my app is slow to send the email? The FCE SDK's waitFor() and the CLI's fce otp both poll with backoff until the email arrives, up to a configurable timeout. Set the timeout to match your app's SLA — usually 30–60 seconds is safe.
Can I use FCE in a Docker container without fce login? Yes — set FCE_API_KEY as an environment variable. The CLI and SDK both detect it automatically and skip interactive login. Get the key from the dashboard.
Does the same inbox work for multiple test steps (signup + profile update + password reset)? Yes. The inbox persists until you call fce inbox remove or fce.inboxes.unregister(). Each call to fce otp or fce.otp.waitFor() returns the most recent OTP, so multi-step flows work naturally.
Can I test with a custom subdomain like qa-bot@test.mycompany.com? Yes, on Growth plan and above with a custom domain registered in the dashboard.
Is there a webhook option instead of polling? Webhooks are on the API roadmap. Today, real-time delivery is via WebSocket (fce watch / client.realtime) on Startup plan+.
Is the free tier suitable for automation testing? For local development and small test suites, yes. For CI pipelines with many parallel jobs, Startup ($19/mo) or Growth ($49/mo) is more appropriate.
Related reading
How to Automate Email Verification Testing in CI/CD Pipelines (2026)
OTP API for Playwright: Automate Email Verification Without Regex (2026)
Temp Mail API for Selenium: Automate Email Verification (2026)
Why FreeCustom.Email Is the Only Disposable Email API With an Official CLI (2026)
FreeCustom.Email vs Mailinator vs Guerrilla Mail: API Comparison (2026)
Written by
Dishant Singh
A full stack developer with good knowledge of email server, SEO, proxies, and networking, have more than 3 years of experience in building webapps for the netizens. Developing open source, fast, and free SaaS for all.
Frequently Asked Questions
How many inboxes can I create per minute?+
Depends on your plan. Free: 1/sec. Developer: 10/sec. Startup: 25/sec. Growth: 50/sec. Enterprise: 100/sec. For parallel test suites, Growth covers most needs.
What happens if my app is slow to send the email?+
The FCE SDK's waitFor() and the CLI's fce otp both poll with backoff until the email arrives, up to a configurable timeout. Set the timeout to match your app's SLA — usually 30–60 seconds is safe.
Does the same inbox work for multiple test steps (signup + profile update + password reset)?+
Yes. The inbox persists until you call fce inbox remove or fce.inboxes.unregister(). Each call to fce otp or fce.otp.waitFor() returns the most recent OTP, so multi-step flows work naturally.
Is there a webhook option instead of polling?+
Webhooks are on the API roadmap. Today, real-time delivery is via WebSocket (fce watch / client.realtime) on Startup plan+.
Is the free tier suitable for automation testing?+
For local development and small test suites, yes. For CI pipelines with many parallel jobs, Startup ($19/mo) or Growth ($49/mo) is more appropriate.
No comments yet. Be the first to share your thoughts.