JavaScript / TypeScript SDK
Test, debug, and automate signup, OTP, and email-based authentication flows — with real-time observability.
Why this exists
Testing auth flows is painful:
- ❌ Flaky email delivery
- ❌ Polling delays
- ❌ OTP parsing issues
- ❌ No visibility into failures
FreeCustom.Email solves this by giving you: 👉 real-time auth flow debugging.
Installation
bash
npm install freecustom-email
# or
pnpm add freecustom-email
# or
yarn add freecustom-email🚀 Quick Start (Auth Flow Testing)
typescript
import { FreecustomEmailClient } from 'freecustom-email';
const client = new FreecustomEmailClient({
apiKey: 'fce_your_api_key_here',
});
// 1. Create inbox (pass `true` for zero-latency testing mode)
const email = 'test@ditube.info';
await client.inboxes.register(email, true);
// 2. Start test run (NEW - Groups events in your timeline)
await client.inboxes.startTest(email, 'signup-test-1');
// 3. Trigger your app
await fetch('https://yourapp.com/api/send-otp', {
method: 'POST',
body: JSON.stringify({ email }),
});
// 4. Wait for OTP
const otp = await client.otp.waitFor(email);
console.log('OTP:', otp);
// 5. Debug the full flow
const timeline = await client.inboxes.getTimeline(email, 'signup-test-1');
console.log(timeline);🔥 Debug Your Auth Flow
Timeline (see what actually happened)
typescript
const timeline = await client.inboxes.getTimeline(email);
console.log(timeline);
// [
// { "type": "smtp_rcpt_received", "time": 820 },
// { "type": "email_received", "time": 830 },
// { "type": "otp_extracted", "time": 835 },
// { "type": "websocket_sent", "time": 840 }
// ]Insights (why your test failed)
typescript
const insights = await client.inboxes.getInsights(email);
console.log(insights);
// [
// { "type": "slow_delivery", "message": "Email took >3s" },
// { "type": "multiple_detected", "message": "Multiple emails detected" }
// ]Test Runs (group your flows)
typescript
await client.inboxes.startTest(email, 'signup-test-1');
const timeline = await client.inboxes.getTimeline(email, 'signup-test-1');⚡ Real-time Debugging (WebSocket)
typescript
const ws = client.realtime({ mailbox: email });
ws.on('email', event => {
console.log('Flow update:', event);
});
await ws.connect();🧪 Full Playwright Example
typescript
import { test, expect } from '@playwright/test';
import { FreecustomEmailClient } from 'freecustom-email';
const client = new FreecustomEmailClient({ apiKey: process.env.FCE_API_KEY! });
test('signup flow', async ({ page }) => {
const email = 'test@ditube.info';
await client.inboxes.register(email, true);
await client.inboxes.startTest(email, 'e2e-signup');
await page.goto('/signup');
await page.fill('#email', email);
await page.click('button');
// Automatically waits for the email and extracts the code
const otp = await client.otp.waitFor(email);
await page.fill('#otp', otp);
await page.click('#verify');
// Debugging: View exactly how long delivery took
const timeline = await client.inboxes.getTimeline(email, 'e2e-signup');
console.log(timeline);
});🔁 Old vs New Mental Model
| Old (Temp Mail) | New (FreeCustom.Email) |
|---|---|
| receive emails | test auth flows |
| read inbox | debug flows |
| parse OTP manually | auto extract + analyze |
| polling | real-time events |
🔥 Most Important Methods (for devs)
client.inboxes.startTest(email, testId)client.otp.waitFor(email)client.inboxes.getTimeline(email, testId)client.inboxes.getInsights(email)
📚 Full API Reference
Client
typescript
const client = new FreecustomEmailClient({
apiKey: 'fce_...', // required
baseUrl: 'https://...', // optional, defaults to https://api2.freecustom.email/v1
timeout: 10_000, // optional, ms, defaults to 10s
retry: { // optional
attempts: 2,
initialDelayMs: 500,
},
});Inboxes
typescript
// Register
await client.inboxes.register('mytest@ditube.info');
// List all registered inboxes
const inboxes = await client.inboxes.list();
// [{ inbox: 'mytest@ditube.info', local: 'mytest', domain: 'ditube.info' }]
// Unregister
await client.inboxes.unregister('mytest@ditube.info');Messages
typescript
// List all messages
const messages = await client.messages.list('mytest@ditube.info');
// Get a specific message
const msg = await client.messages.get('mytest@ditube.info', 'D3vt8NnEQ');
console.log(msg.subject, msg.otp, msg.verificationLink);
// Delete a message
await client.messages.delete('mytest@ditube.info', 'D3vt8NnEQ');
// Wait for a message (polling helper)
const msg = await client.messages.waitFor('mytest@ditube.info', {
timeoutMs: 30_000,
pollIntervalMs: 2_000,
match: m => m.from.includes('github.com'),
});OTP
typescript
// Get latest OTP (Growth plan+)
const result = await client.otp.get('mytest@ditube.info');
if (result.otp) {
console.log('OTP:', result.otp);
console.log('Link:', result.verification_link);
}
// Wait for OTP — polls until it arrives (Growth plan+)
const otp = await client.otp.waitFor('mytest@ditube.info', {
timeoutMs: 30_000,
});
console.log('Got OTP:', otp);Full Verification Flow (convenience method)
typescript
// Register → trigger email → wait for OTP → unregister — all in one call
const otp = await client.getOtpForInbox(
'mytest@ditube.info',
async () => {
// This function should trigger your app to send the verification email
await fetch('https://yourapp.com/api/send-verification', {
method: 'POST',
body: JSON.stringify({ email: 'mytest@ditube.info' }),
});
},
{ timeoutMs: 30_000, autoUnregister: true },
);
console.log('Verified with OTP:', otp);Real-time WebSocket
typescript
// Connect to real-time email delivery (Startup plan+)
const ws = client.realtime({
mailbox: 'mytest@ditube.info', // undefined = subscribe to all your inboxes
autoReconnect: true,
reconnectDelayMs: 3_000,
maxReconnectAttempts: 10,
pingIntervalMs: 30_000,
});
// Event handlers
ws.on('connected', info => {
console.log('Connected — plan:', info.plan);
});
ws.on('email', email => {
console.log('New email!');
console.log('OTP:', email.otp);
});
ws.on('disconnected', ({ code, reason }) => {
console.log('Disconnected:', code, reason);
});
// Connect
await ws.connect();
// Disconnect cleanly
ws.disconnect();Domains
typescript
// List available domains on your plan
const domains = await client.domains.list();
// Custom domains (Growth plan+)
const custom = await client.domains.listCustom();
const result = await client.domains.addCustom('mail.yourdomain.com');
const verification = await client.domains.verifyCustom('mail.yourdomain.com');
await client.domains.removeCustom('mail.yourdomain.com');Webhooks
typescript
// Register a webhook (Startup plan+)
const hook = await client.webhooks.register(
'mytest@ditube.info',
'https://your-server.com/hooks/email',
);
console.log('Webhook ID:', hook.id);
// List active webhooks
const hooks = await client.webhooks.list();
// Unregister
await client.webhooks.unregister(hook.id);Account
typescript
// Account info
const info = await client.account.info();
// Usage stats
const usage = await client.account.usage();Error Handling
typescript
import {
FreecustomEmailClient,
AuthError,
PlanError,
RateLimitError,
NotFoundError,
TimeoutError,
FreecustomEmailError,
} from 'freecustom-email';
try {
const otp = await client.otp.get('mytest@ditube.info');
} catch (err) {
if (err instanceof AuthError) {
console.error('Invalid API key');
} else if (err instanceof PlanError) {
console.error('Plan too low:', err.message);
}
}TypeScript Types
typescript
import type {
Message,
OtpResult,
InboxObject,
DomainInfo,
AccountInfo,
UsageStats,
WsConnectedEvent,
WsNewEmailEvent,
} from 'freecustom-email';Links
- npmjs.com/package/freecustom-email — npm package page
- Interactive Playground — try API calls in the browser