Skip to content
BurnerByte
Architecture

Two binaries, one set of stores.

BurnerByte is an HTTP API and an SMTP daemon that never call each other. They share a database, a cache and an object store, and they meet on a Redis channel. That separation is what lets mail ingest and request handling scale apart — and it is why a message can appear in an open browser tab in the same second the daemon accepts it.

BurnerByte request and mail pathsTwo independent paths reach one set of stores. On the HTTP path, a browser talks to the Next.js frontend on port 3000, which calls the Go API on port 8080 over REST and WebSocket; the API reads and writes PostgreSQL and S3-compatible object storage, and runs seven background workers. On the mail path, an external mail server delivers to the Go SMTP daemon on port 2525 or 25, which writes the message to PostgreSQL, attachments to object storage, and publishes an inbox event to Redis. The API and Redis are joined by a two-way publish and subscribe bridge, so the API receives that event and fans it out to connected WebSocket clients — which is how mail appears in a browser without polling, even though the two binaries never call each other.HTTP PATHMAIL PATHBrowserany deviceNext.js 16 UI:3000React 19 · standaloneREST + WScmd/api:8080Chi router · 162 opsWebSocket hubRBAC · limits · audit7 background workersPostgreSQL 1636 tables · 74 indexesMinIO / S3attachmentsRedis 7cache · pub/subpub/sub bridgeExternal MTAMX lookupcmd/smtpd:2525 (or :25)100 conns · 4 workerspublish bb:inboxstore messageattachmentsHTTP / WebSocketInbound mailRedis pub/sub
Two Go binaries, one set of stores. They never call each other — the SMTP daemon publishes on Redis and the API subscribes, which is what lets you scale mail ingest and request handling apart.

How to read it

  • The indigo path is a browser. Next.js on 3000 calls the Go API on 8080 over REST and WebSocket; the API reads and writes PostgreSQL and object storage, and runs all seven background workers.
  • The amber path is a stranger’s mail server. It resolves your MX and delivers to the SMTP daemon, which writes the message, the attachments, and an event.
  • They meet at Redis and nowhere else. The daemon has no idea which API instance holds the socket for the tab that is watching, and does not need to: it publishes, and whichever process has the connection delivers.

That last point is the whole design. Everything else — independent scaling, the ability to put the SMTP daemon on a different host from the API, the fact that Redis can be flushed without data loss — follows from the two binaries not knowing about each other.

One API replica, for now
In depth

Each part, in the reference.

Stack

Two Go binaries, a Next.js frontend, and three stores. Nothing exotic, and nothing you cannot run on one machine.

Backend

  • Go 1.25
  • Chi v5
  • pgx / pgxpool
  • go-redis
  • minio-go
  • Prometheus

Frontend

  • Next.js 16.1
  • React 19.2
  • Tailwind CSS 4
  • shadcn/ui
  • Zustand
  • TanStack Query
  • Recharts
  • next-intl

Data

  • PostgreSQL 16
  • Redis 7
  • MinIO or any S3-compatible store

Docs & tests

  • Fumadocs (MDX)
  • Go testing + rapid
  • Vitest
  • Testing Library
configuration resolves in this order
defaults  → config.yaml (optional)    → environment variables (BB_-prefixed, plus four unprefixed)      → values stored in the database by the admin UI

The last layer is the interesting one: mailer, storage, SSO and platform settings live in the database and hot-reload across every process via Redis pub/sub, so changing them takes effect without a restart. Database and Redis are the only settings that cannot work this way, for the obvious reason — the setup wizard’s own state lives in that database. Configuration is the full reference, and the authoritative per-endpoint description is the OpenAPI document the API serves at /api/v1/docs/openapi.json.