Every team I have talked to handles email verification the same way in their test suite: they skip it, mock it, or use a shared inbox that makes parallel tests unreliable. I did all three before I found an approach that actually works at scale.
The problem is that most disposable email services have no real developer tooling. You get a web UI and maybe a REST API that requires you to poll, parse, and regex your way to an OTP. Nothing is designed for automation.
FreeCustom.Email is different — it is the only disposable email service with an official CLI, official SDKs, a dedicated OTP extraction endpoint, and WebSocket real-time delivery. This post covers every way to use it for signup automation, from a three-line bash script to a full parallel Playwright test suite.
The Pattern
All signup automation comes down to three steps:
1. Create a fresh inbox → get an address nobody else is using
2. Submit it in the signup UI → your app sends the verification email
3. Extract the OTP → complete verification
The only question is which tool you use for each step. I will cover all of them.
Option 1: The CLI (Fastest for Local Dev and Scripts)
Install:
curl -fsSL freecustom.email/install.sh | sh # macOS / Linux
npm install -g fcemail@latest # All platforms
choco install fce # Windows
brew tap DishIs/homebrew-tap && brew install fce # Homebrew
Full install options and all releases at github.com/DishIs/fce-cli. Login once:
fce login
# Browser → sign in → key saved to OS keychain automatically
Automate a signup in four 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"
The fce otp output:
────────────────────────────────────────────────
OTP
────────────────────────────────────────────────
OTP · 212342
From · noreply@myapp.com
Subj · Your verification code
Time · 20:19:54
For local development, fce dev is even faster — it creates an inbox and starts watching it in one command:
fce dev
# → dev-fy8x@ditcloud.info created and watching
# → emails appear in real time as they arrive
Option 2: The TypeScript SDK
npm install freecustom-email
import { FreecustomEmailClient } from 'freecustom-email';
const fce = new FreecustomEmailClient({ apiKey: process.env.FCE_API_KEY! });
async function automateSignup() {
const email = `auto-${Date.now()}@ditmail.info`;
await fce.inboxes.register(email);
// Trigger signup
await fetch(`${process.env.APP_URL}/api/signup`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password: 'AutoTest123!' }),
});
// No polling loop. No regex. Just this:
const otp = await fce.otp.waitFor(email, { timeout: 30_000 });
console.log(`Got OTP: ${otp}`);
// Complete verification
await fetch(`${process.env.APP_URL}/api/verify`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, otp }),
});
await fce.inboxes.unregister(email);
}
Running 10 signups in parallel is trivial because each has its own inbox:
await Promise.all(Array.from({ length: 10 }, () => automateSignup()));
Option 3: The Python SDK
pip install freecustom-email httpx
import asyncio, os, httpx
from freecustom_email import FreeCustomEmail
fce = FreeCustomEmail(api_key=os.environ["FCE_API_KEY"])
APP = os.environ["APP_URL"]
async def automate_signup():
email = f"auto-{int(asyncio.get_event_loop().time())}@ditmail.info"
await fce.inboxes.register(email)
async with httpx.AsyncClient() as client:
await client.post(f"{APP}/api/signup",
json={"email": email, "password": "AutoTest123!"})
otp = await fce.otp.wait_for(email, timeout=30)
print(f"OTP: {otp}")
await client.post(f"{APP}/api/verify",
json={"email": email, "otp": otp})
await fce.inboxes.unregister(email)
# Five parallel signups
asyncio.run(asyncio.gather(*[automate_signup() for _ in range(5)]))
Option 4: Playwright (Browser-Based Flows)
When you need to interact with the actual UI rather than call APIs directly:
// tests/fixtures/fce.ts
import { test as base } from '@playwright/test';
import { FreecustomEmailClient } from 'freecustom-email';
const fce = new FreecustomEmailClient({ apiKey: process.env.FCE_API_KEY! });
export const test = base.extend({
inbox: async ({}, use) => {
const addr = `pw-${Date.now()}@ditmail.info`;
await fce.inboxes.register(addr);
await use(addr);
await fce.inboxes.unregister(addr).catch(() => {});
},
getOtp: async ({ inbox }: any, use: any) => {
await use((ms = 30_000) => fce.otp.waitFor(inbox, { timeout: ms }));
},
});
// tests/signup.spec.ts
import { test, expect } from './fixtures/fce';
test('signup flow', async ({ page, inbox, getOtp }: any) => {
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 getOtp();
await page.fill('#otp-input', otp);
await page.click('button[type="submit"]');
await expect(page).toHaveURL(/dashboard/);
});
Option 5: Selenium (Python)
from selenium import webdriver
from selenium.webdriver.common.by import By
from freecustom_email import FreeCustomEmail
import asyncio, os, time
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(time.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))
Option 6: OpenClaw AI Agent (Zero Code)
This one surprised me with how well it works. With any AI agent that can run shell commands, just describe the task:
"Automate a signup at https://myapp.com — create a temp inbox, fill the form, extract the OTP, and complete verification. The fce CLI is already logged in."
The agent figures out the right commands and runs them. Great for one-off tasks or when you want automation without writing a script.
GitHub Actions — Parallel Jobs with Matrix Strategy
jobs:
signup-automation:
runs-on: ubuntu-latest
strategy:
matrix:
shard: [1, 2, 3, 4]
steps:
- uses: actions/checkout@v4
- name: Install fce CLI
run: curl -fsSL freecustom.email/install.sh | sh
- name: Run signup automation (shard ${{ matrix.shard }})
env:
FCE_API_KEY: ${{ secrets.FCE_API_KEY }}
APP_URL: ${{ vars.STAGING_URL }}
run: node scripts/signup-automation.js
- name: Cleanup on failure
if: failure()
env:
FCE_API_KEY: ${{ secrets.FCE_API_KEY }}
run: fce inbox list | grep "auto-" | xargs -I{} fce inbox remove {} || true
Handling Apps That Reject Disposable Domains
Some signup forms check the email domain and reject known temp mail addresses. Two options:
Custom domain (Growth plan): Register your own domain in the FCE dashboard. Then test-001@automation.yourcompany.com is a valid FCE inbox. No blocklist problems.
Use less-known FCE domains: Run fce domains to see all domains available on your plan. Some are newer and not on common blocklists.
Approach Comparison
| Approach | OTP extraction | Parallel safe | CI-ready | Skill needed |
|---|---|---|---|---|
| fce CLI + bash | Auto | ✓ | ✓ | Bash |
| JS/TS SDK | Auto | ✓ | ✓ | TypeScript |
| Python SDK | Auto | ✓ | ✓ | Python |
| Playwright fixture | Auto | ✓ | ✓ | Playwright |
| Selenium fixture | Auto | ✓ | ✓ | Selenium |
| OpenClaw AI agent | Auto | Partial | ✓ | None |
| Shared inbox (old way) | Manual regex | ✗ | ✗ | Regex suffering |
Links
- 🖥️ CLI docs: freecustom.email/api/cli
- 🤖 Automation hub: freecustom.email/api/automation
- 📦 GitHub: github.com/DishIs/fce-cli
- 🚀 Releases: github.com/DishIs/fce-cli/releases
- 📖 API docs: freecustom.email/api/docs
- 💬 Discord: discord.com/invite/Ztp7kT2QBz
What is your current setup for testing email verification flows? I am especially curious whether anyone has built something with the AI agent approach — it still feels a bit like magic to me every time it works.
Top comments (0)