ZephZap - Disposible Email Service
ZephZap is a self-hosted disposable email service I built specifically for receiving OTP codes and verification emails in real time. It gives users unlimited throwaway inboxes on a custom domain, with automatic OTP extraction and one-click copy no signup required for public inboxes. I built this because every existing disposable email service is either ad-riddled, unreliable, or doesn't highlight the verification code prominently. I wanted something fast, clean, and fully under my control.

ZephZap - Disposable Email Service for OTP Verification
Overview
ZephZap is a self-hosted disposable email service I built specifically for receiving OTP codes and verification emails in real time. It gives users unlimited throwaway inboxes on a custom domain, with automatic OTP extraction and one-click copy no signup required for public inboxes.
I built this because every existing disposable email service is either ad-riddled, unreliable, or doesn't highlight the verification code prominently. I wanted something fast, clean, and fully under my control.
The Problem
Developers and QA engineers constantly need temporary inboxes to test registration flows, receive OTPs, and isolate sign-up traffic. Existing tools are slow, cluttered with ads, or shut down without warning. Running your own mail infrastructure traditionally means opening port 25, managing IP reputation, and dealing with spam filters a significant operational burden.
ZephZap solves this by routing inbound SMTP through Cloudflare Email Routing, completely bypassing the need to run a mail server. The app only receives a signed HTTPS webhook no port 25, no MX headaches.
What I Built
Real-Time Inbox
Emails appear in the browser within 2 seconds of receipt using Server-Sent Events over a Redis pub/sub channel. No polling, no page refresh. The SSE endpoint streams events directly from a dedicated Redis subscriber connection per client, with automatic reconnection and exponential backoff on the frontend.
OTP Extraction Engine
A custom regex pipeline scans incoming email bodies for verification codes. It priorities 6-digit numeric codes, falls back to 4 and 8-digit patterns, handles labelled formats like OTP: 482910 and verification code: 738291, and skips numbers embedded inside URLs to avoid false positives.
Two Inbox Modes
Public inboxes require no account any visitor who knows the address can read it. Private inboxes are owned by authenticated users and invisible to everyone else. Rate limiting (10 public inboxes per IP per hour, 50 private per user) is enforced via Redis counters. Custom addresses are restricted to authenticated users only.
Admin Dashboard
A separate admin panel with its own JWT-based session (independent of NextAuth) gives full visibility into platform activity. Admins can search users and inboxes by email or IP, view all received emails with full HTML preview in a sandboxed modal, block users, reset passwords, and monitor top IPs for abuse detection.
Cloudflare Email Worker
A Cloudflare Email Worker intercepts every inbound email on the domain, reads the raw MIME stream, base64-encodes it, signs the payload with HMAC-SHA256 using the Web Crypto API, and POSTs it to the app. The app verifies the signature with timingSafeEqual before processing anything.
Technical Highlights
Port 25 never opened Cloudflare handles raw SMTP, app only receives signed HTTPS webhooks
Redis dedup keys (TTL 60s) prevent double-processing of duplicate webhook deliveries
HTML email bodies sanitized server-side (script tag stripping) and client-side (DOMPurify) before rendering in a
sandbox="allow-same-origin"iframeDedicated Redis subscriber per SSE client — never reuses the global ioredis instance for pub/sub
SSE connection limit of 100 per inbox tracked via Redis INCR/DECR, returns 429 beyond that
Next.js 15 App Router with server components for admin pages — data fetched server-side, no client-side auth token exposure
Prisma standalone output with compiled seed script (esbuild) for zero-dependency Docker runtime
Admin auth uses
joseJWT entirely separate from NextAuth — different secret, different cookie, different session lifecycle
Key Decisions
I chose Server-Sent Events over WebSockets deliberately. SSE is unidirectional (server to client), which is exactly what an inbox needs the server pushes new emails, the client never needs to send data back over the same connection. SSE works over standard HTTP/2, requires no special server configuration, and reconnects automatically. WebSockets would have added complexity with no benefit for this use case.
Storing HTML email bodies directly in PostgreSQL as TEXT rather than in object storage (S3/Minio) was a conscious simplification. For a disposable email service with a 7-day retention policy, the volume of HTML bodies is bounded and predictable. Eliminating the object storage dependency removes an entire infrastructure component, simplifies the Docker setup to three services (app, postgres, redis), and makes the cleanup cron trivially simple a single batched DELETE with no external API calls.
The decision to use Cloudflare Email Routing instead of running Postfix or Haraka was the most impactful architectural choice. It completely eliminates IP reputation management, DKIM/SPF complexity, and the operational risk of running a mail server. The tradeoff is that the service is receive-only but for an OTP inbox tool, that is exactly the right constraint. It keeps the product focused and the infrastructure lean.