A local-first cockpit for SMTP, Kafka, Redis, and webhooks — so your next test email lands in a UI, not a customer inbox. One binary. One port. Zero accidental deliveries.
No terminal required. deadLetter ships a static Next.js UI that
lives at localhost:8025 —
inbox, Kafka workspace, Redis tools, webhooks, settings. Live updates
over SSE. Dark mode by default.
Capture, inspect, and debug SMTP · Kafka · Redis · Webhooks — one place, one shell, zero noise.
Hey there — your account is ready. Click the button below to get started.
If you didn't sign up, ignore this message.
Watch SMTP, tail Kafka, poke at Redis, fire webhooks — from one UI, one API, one binary. Built for the two places production doesn't want you: your laptop and CI.
:1025 · raw .eml + JSON
Point your app at localhost:1025. deadLetter accepts the
message, persists the raw envelope, extracts headers and MIME
parts, and rebuilds its index on boot so captures survive restarts.
Render sanitized HTML previews safely while keeping the original raw source untouched on disk.
Fan out captured events to HTTP endpoints with retries, HMAC signatures, and delivery logs.
/api/webhooks Brokers, topics, partitions, consumer groups, offsets. Tail a partition, reset a group, manage topics, and deserialize Confluent wire-format with Schema Registry integration.
Pub/Sub monitoring, live channel subscriptions over SSE, server info, key discovery, and DB usage — all from the same UI.
Re-send a captured message to a real SMTP target only when you explicitly enable it. Zero surprises.
Keep messages for a configurable window. Scheduled or manual cleanup. Disk never surprises you.
The UI streams backend events — email, replay, retention, Kafka, webhooks — so what you see is what just happened.
List, show, replay, and export captured emails from a bundled CLI. Friendly to automation, CI, and muscle memory.
$ deadletterctl replay 01HZX… --to qa@internal deadLetter flags likely repeats based on sender + subject within a short window, so misbehaving workers stop hiding.
Everyone else nails one protocol. deadletter collapses four into a single local binary — SMTP, Kafka, Redis, webhooks — with no SaaS, no signup, no telemetry.
| Tool | Kafka | Redis | Webhooks | Price | |
|---|---|---|---|---|---|
| deadletter | Free · OSS | ||||
| Mailpit | — | — | — | Free | |
| MailHog | — | — | — | Free · unmaintained | |
| MailCatcher | — | — | — | Free | |
| Mailtrap | — | — | — | Free tier · paid | |
| webhook.site | — | — | — | Free · paid | |
| Smee.io | — | — | — | Free | |
| Hookdeck | — | — | — | Free tier · paid | |
| ngrok inspect | — | — | — | Free · paid | |
| Kafka UI | — | — | — | Free | |
| Redpanda Console | — | — | — | Free · Enterprise | |
| AKHQ | — | — | — | Free | |
| Conduktor | — | — | — | Free tier · paid | |
| RedisInsight | — | — | — | Free | |
| Redis Commander | — | — | — | Free |
Scope: tools that replace at least one of deadletter's four surfaces. General-purpose tools (Postman, Wireshark, log aggregators) excluded.
Every view in the UI is backed by a documented HTTP endpoint and a live SSE topic. Nothing is hidden, nothing is proprietary.
/api/emails/:id 200 · application/json // Full metadata + parsed MIME for one capture { "id": "01HZX5A6QH7B0Q2F3TJ6PE8M7N", "from": "app@local", "to": ["alice@example.com"], "subject": "Welcome to deadLetter", "receivedAt": "2026-04-17T19:23:11Z", "size": 4123, "parts": ["text/plain", "text/html"], "duplicate": false, "replayed": false, "links": { "raw": "/api/emails/01HZX…/raw", "html": "/api/emails/01HZX…/html", "text": "/api/emails/01HZX…/text" } }
/events/stream text/event-stream
// Live backend events event: email.captured data: {"id":"01HZX…","from":"app@local","size":4123} event: email.duplicate data: {"id":"01HZX…","within":"30s"} event: webhook.delivered data: {"endpoint":"dev-bot","status":200,"ms":47} event: kafka.message data: {"topic":"orders","partition":2,"offset":1407} event: retention.swept data: {"removed":14,"keep":"24h"}
A Go backend with a static Next.js UI baked in. Ingest SMTP, tail Kafka, watch Redis — everything funnels through the same live SSE bus, so the UI never lies about what the backend just saw.
From a one-line Docker pull to a full Kafka + Redis stack — plus a Homebrew formula landing soon. Copy, paste, done.
1025 free port 8025 free Kafka / Redis (optional) $ docker pull peixotomdb/deadletter:latest $ docker run -d \ --name deadletter \ -p 1025:1025 -p 8025:8025 \ -v "$PWD/data:/data" \ peixotomdb/deadletter:latest # UI & API → http://localhost:8025 # SMTP sink → localhost:1025
$ curl -s localhost:8025/api/health {"status":"ok","version":"1.2.0","smtp":"1025","uptime":"3s"}
$ docker run -d \ --name deadletter \ -p 1025:1025 -p 8025:8025 \ -v "$PWD/data:/data" \ -e KAFKA_BROKERS=host.docker.internal:9092 \ -e SCHEMA_REGISTRY_URL=http://host.docker.internal:8081 \ -e REDIS_ADDR=host.docker.internal:6379 \ peixotomdb/deadletter:latest # Kafka topics, consumers & schemas show up in /kafka # Redis keys & pub/sub channels show up in /redis
KAFKA_BROKERS—comma-sep list · SASL supportedSCHEMA_REGISTRY_URL—optional · enables Avro decodingREDIS_ADDR—single node · user:pass@host okREPLAY_ENABLEDfalseopt-in SMTP re-sendRETENTION24hGo duration string# formula in review — expected in the next release $ brew install peixotomdb/tap/deadletter $ deadletter ▸ SMTP sink listening on :1025 ▸ HTTP API listening on :8025 ▸ data dir ~/Library/Application Support/deadletter $ deadletter --help # CLI mirrors the REST API
$ git clone https://github.com/peixotomdb/deadletter.git $ cd deadletter-ui && pnpm install && pnpm build $ cd .. && go build -o deadletter ./cmd/deadletter $ ./deadletter # or live UI + backend dev $ make dev
Error: bind: address already in use Another tool (MailHog? Mailpit?) holds 1025/8025. Map different host ports: -p 2525:1025 -p 18025:8025. connection refused · host.docker.internal Linux hosts need --add-host=host.docker.internal:host-gateway. Docker Desktop resolves it automatically. No events in UI Check curl localhost:8025/events/stream returns. If yes, your app is silent — verify SMTP host, not firewall. / /inbox /kafka /redis /webhooks /settings deadLetter is built for local and CI environments where an accidental delivery can mean a real user gets a test email. Every destructive action is opt-in, logged, and visible in the UI.
Until you flip REPLAY_ENABLED=true and point
it at a target, replay returns an error.
The .eml on disk is byte-exact. Previews
sanitize separately.
Rendered HTML is served in a sandboxed frame with no network egress by default.
Outbound webhooks carry an HMAC-SHA256 signature and retry with exponential backoff. Logs are keepable.
Not a production SMTP relay. Not a deliverability service. Not a hardened multi-tenant mailbox. It is a local-first observability cockpit — deliberately small, deliberately loud about what it sees.
One docker run —
and every SMTP, Kafka, Redis, and webhook event your dev env
emits lands in a UI you can actually reason about.