2026-05-30 05:10:27 +00:00
2026-05-30 05:10:27 +00:00
2026-05-30 04:35:26 +00:00
2026-05-30 04:17:10 +00:00
2026-05-30 04:17:10 +00:00
2026-05-30 04:35:26 +00:00
2026-01-17 09:19:43 +00:00

Warmbly

Warmbly

The open-source cold email and mailbox warmup platform you can actually self-host.
No AWS lock-in. Distributed senders. Multi-IP workers. Admin UI included.

Quick start License Go Postgres Self-hostable No AWS required

Features · Quick start · Architecture · Self-hosting · Admin UI · No lock-in


Warmbly dashboard preview



Why Warmbly

Most cold email platforms force you into one of two corners. Hosted SaaS — fast to start, but your sender reputation lives in someone else's IP pool, and your data lives in someone else's database. Or roll-your-own — full control, six months of plumbing before you send the first email.

Warmbly is the third option: a real cold-outreach platform that runs on your infrastructure, your IPs, your database, without making you give up the features you'd expect from a hosted SaaS.

You can run it on a single $5 VPS with SQLite-free Postgres. You can run it across a Hetzner CX32 fleet with 16 IPs per box. The same code handles both.

Features

🛡️ Self-hostable

Single binary per service. No required calls to AWS, GCP, Stripe, or Cloudflare. Postgres + Redis + NATS is the whole infrastructure stack.

🌐 Distributed workers

One worker per IP, many workers per VPS. Multi-IP install in one command. Reputation per IP, not per VPS.

🔌 Pluggable everything

Swap KMS, blob store, event bus, codec at deploy time. AWS or local AES. Kafka or NATS JetStream. S3 or filesystem.

🎛️ Admin UI

Production-grade React + Vite admin app with workers, egresses, mailboxes, warmup, audit, and settings.

🔐 Envelope encryption

KMS-wrapped per-user DEKs. Workers fetch them over HTTPS, never touch Postgres directly. Constant-time bearer-token auth on internal endpoints.

✉️ Real warmup

Pool-based warmup with anti-abuse signals, spam-score tracking, and auto-blocking on token-forgery patterns. Free vs premium pool isolation.

📊 Multi-tier worker fleet

Shared free, shared premium, dedicated per-org. Tier migrations with mailbox rebalancing. Per-egress health bands.

🧪 Mailbox-first safety

Per-mailbox send caps (default 50/day), spacing (default 600s), warmup ramp from 10 to 40/day. Worker volume = sum of mailbox budgets, not a flat per-worker limit.

🛰️ Real-time tracking

Open/click pixel + redirect service (Rust), with deduplication at both the tracker and the consumer level. Replay-resistant.


Quick start

The fastest path to a running stack — one VPS, one command, everything local:

# Clone, install deps, boot everything
git clone https://github.com/warmbly/warmbly && cd warmbly
make infra   # Postgres, Redis, NATS, Kafka, Schema Registry, MailHog (~1 min)
make app     # backend, consumer, worker, tracking, realtime, dashboard (~30s)

# Open the dashboard
open http://localhost:5173

The admin app and marketing site live outside the compose stack and run on demand from their own terminals:

make admin   # Vite dev server  → http://localhost:5174
make site    # Astro dev server → http://localhost:4321

To open the admin app you need an account with admin_permissions > 0. There is no way to bootstrap the first admin from inside the UI, so sign up normally through the dashboard, then promote yourself from the host:

make grant-admin EMAIL=you@example.com               # super (all perms)
make grant-admin EMAIL=you@example.com ROLE=support  # support|ops|analyst
make revoke-admin EMAIL=you@example.com              # back to 0

That's it. Hot reload is wired for every language service (Go, Rust, Elixir, React).

For production: see Self-hosting below.

Admin UI

The admin app lives at admin/, separate from the user dashboard, with a clear amber accent so admins never confuse the two.

Warmbly admin app

What it covers:

  • Overview — fleet health, mailbox counts, send rates, bounce rates
  • Workers — physical worker processes, SSH lifecycle (test / install / restart / upgrade / logs)
  • Egresses — sending identities (worker × IP), health scores, quarantine
  • Mailboxes — every connected mailbox across the platform
  • Warmup — pools, participants, blocked accounts, appeals
  • Settings — KMS / blob store / encrypted-keys / event bus / cache / transports — all selectable backends
  • Audit log — every admin action with diffs

Architecture

                    ┌─────────────────────────────────┐
                    │           Admin UI              │
                    │       (React + Vite app)        │
                    └────────────────┬────────────────┘
                                     │
                                     ▼
┌────────────────┐   HTTPS   ┌─────────────────────────────────────┐
│   Dashboard    │ ◄───────► │            Backend API              │
│    (React)     │           │           (Go, Gin, REST)           │
└────────────────┘           │                                     │
                             │  ┌──────────┐ ┌────────────────┐    │
                             │  │ Settings │ │ /api/v1/       │    │
                             │  │ Registrar│ │  internal/dek  │    │
                             │  └──────────┘ └────────┬───────┘    │
                             └──┬────────────────┬────┴────────────┘
                                │                │       │
                                ▼                ▼       │
                      ┌─────────────────┐  ┌──────────┐  │
                      │   PostgreSQL    │  │  Redis   │  │
                      │ users, accounts │  │  cache   │  │
                      │ DEKs (via PG)   │  │ ratelim  │  │
                      └─────────────────┘  └──────────┘  │
                                                         │
                  ┌──────────────────────────────────────┘
                  │
                  ▼  Kafka / NATS JetStream events
┌─────────────────────────────────────────────────────────────────┐
│                  Distributed worker fleet                       │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────┐         │
│  │ Worker 1 │  │ Worker 2 │  │ Worker 3 │  │ Worker N │  ...    │
│  │  IP A    │  │  IP B    │  │  IP C    │  │  IP X    │         │
│  └────┬─────┘  └────┬─────┘  └────┬─────┘  └────┬─────┘         │
│       │              │              │              │             │
│       └──────────────┴──────────────┴──────────────┘             │
│                            │                                     │
│                            ▼                                     │
│              Outbound SMTP / IMAP / OAuth APIs                   │
│            (Gmail, Microsoft 365, Zoho, custom)                  │
└─────────────────────────────────────────────────────────────────┘

Control plane: backend API + consumer + Postgres + Redis + event bus. Decides what to send and where; owns all stateful data.

Execution plane: distributed worker fleet. One Go binary per VPS, one worker process per IP. Workers receive commands over the event bus, fetch DEKs over HTTPS, and emit telemetry back. Workers never connect to Postgres.

The split matters because workers are intended to scale horizontally across many cheap VPSes — each one is a sending identity, not a database client.

Self-hosting

Warmbly is designed so a self-hoster never has to pay AWS / GCP / Cloudflare / Stripe for anything except the boxes they want to rent.

Concern Self-host default Cloud option
Database PostgreSQL 16 RDS / Cloud SQL
Cache Redis (or Valkey) ElastiCache
Event bus NATS JetStream (1 binary) Kafka, MSK
Blob storage Filesystem S3, MinIO, R2, B2
KMS / root key Local AES master key AWS KMS, Vault, GCP
Encrypted DEKs PostgreSQL table DynamoDB / Scylla
Codec JSON Avro + Schema Registry
Captcha Bypass token (trusted) Cloudflare Turnstile
Payments Off Stripe

Every adapter is selected via an env var. See docs/VENDOR_LOCKIN.md for the honest audit of every external dependency and what to do about it.

Minimum-viable env

# Self-hostable defaults
KMS_PROVIDER=local
KMS_LOCAL_MASTER_KEY=$(openssl rand -base64 32)

ENCRYPTED_KEYS_PROVIDER=postgres   # on the backend
INTERNAL_API_TOKEN=$(openssl rand -base64 32)

BLOB_PROVIDER=filesystem
BLOB_FS_ROOT=/var/lib/warmbly/blobs

EVENTBUS_PROVIDER=nats
NATS_URL=nats://localhost:4222

CODEC_PROVIDER=json

Workers get one extra:

ENCRYPTED_KEYS_PROVIDER=http
ENCRYPTED_KEYS_BACKEND_URL=https://api.yourdomain.com
ENCRYPTED_KEYS_WORKER_TOKEN=<same as INTERNAL_API_TOKEN>

Multi-IP worker installation

One Hetzner CX32 with 16 attached Primary IPs becomes 16 sending identities with one command:

sudo ./scripts/install-worker.sh \
  --kafka kafka.yourdomain.com:9092 \
  --redis redis://cache.yourdomain.com:6379 \
  --ips 5.6.7.11,5.6.7.12,5.6.7.13,5.6.7.14,5.6.7.15,\
5.6.7.16,5.6.7.17,5.6.7.18,5.6.7.19,5.6.7.20,5.6.7.21,\
5.6.7.22,5.6.7.23,5.6.7.24,5.6.7.25,5.6.7.26

Each IP gets its own systemd unit (warmbly-worker@<dashed-ip>.service) and a deterministic UUIDv5 identity — reputation persists across reinstalls. Full runbook in docs/MULTI_IP_WORKERS.md.

Stack

LayerTechWhy
Backend API Go 1.25 + Gin Same binary on a Pi, an EC2 box, or a Hetzner dedicated
Consumer Go (event-bus driven) Same
Worker Go (Kafka/NATS subscriber) ~50MB RAM per process; runs anywhere with port 25 open
Tracking Rust + Axum Open/click pixel hot path; dedup in-memory + persistent
Realtime Elixir + Phoenix Channels WebSocket fanout, naturally concurrent
Dashboard React 19 + Vite + Tailwind v4 + shadcn Modern stack, no Next.js coupling
Admin UI React 19 + Vite + Tailwind v4 + shadcn Same stack as dashboard; distinct visual identity
Primary DB PostgreSQL 16 Boring, durable, well-known
Cache Redis 7 (or Valkey / KeyDB) Sliding-window rate limits + DEK cache
Event bus NATS JetStream (default) or Kafka NATS = single binary; Kafka = the historical hosted path

Project layout

warmbly/
├── cmd/
│   ├── backend/        # REST API + admin orchestration
│   ├── consumer/       # event-bus consumer → Postgres
│   ├── worker/         # distributed sender (one per IP)
│   └── seed/           # local-dev fixtures
├── internal/
│   ├── api/            # HTTP handlers, routes, middleware
│   ├── app/            # business services (auth, email, campaign, ...)
│   ├── client/
│   │   ├── netbind/    # per-egress bind-IP for SMTP/IMAP
│   │   └── smtpimap/   # SMTP + IMAP client
│   ├── events/         # publisher + event schemas
│   ├── infrastructure/
│   │   ├── codec/      # Avro + JSON (pluggable)
│   │   ├── encryptedkeys/  # Postgres + DynamoDB + HTTP (pluggable)
│   │   ├── eventbus/   # Kafka + NATS JetStream (pluggable)
│   │   ├── kms/        # local + AWS (pluggable)
│   │   └── storage/    # filesystem + S3-compatible (pluggable)
│   ├── models/         # domain types
│   └── repository/     # Postgres data access
├── tracking/           # Rust open/click service
├── realtime/           # Elixir WebSocket gateway
├── web/                # User dashboard (Vite + React)
├── admin/          # Admin UI (Vite + React)
├── scripts/
│   └── install-worker.sh   # single-IP + multi-IP installer
├── docs/               # operator docs (auth, lock-in, multi-IP)
└── resources/          # technical / architectural docs

Building

go build -o bin/backend  ./cmd/backend
go build -o bin/consumer ./cmd/consumer
go build -o bin/worker   ./cmd/worker

cd tracking    && cargo build --release
cd realtime    && mix deps.get && mix release
cd web         && pnpm install && pnpm build
cd admin   && pnpm install && pnpm build

Testing

go test ./...
cd web         && pnpm typecheck && pnpm lint
cd admin   && pnpm typecheck && pnpm lint
cd tracking    && cargo test
cd realtime    && mix test

Coverage highlights:

  • internal/infrastructure/codec — 11 tests (JSON round-trip + Avro interface conformance)
  • internal/infrastructure/encryptedkeys — 14 tests (HTTP round-trip, factory, conflict semantics)
  • internal/infrastructure/eventbus — 16 tests (Kafka + NATS round-trip + ack redelivery)
  • internal/infrastructure/kms — 11 tests (local AES round-trip + tamper detection + factory)
  • internal/infrastructure/storage — 14 tests (filesystem + traversal protection + factory)
  • internal/client/netbind — 4 tests (dialer + TLS dialer + env fallback)
  • internal/api/middleware — 5 internal-auth tests
  • internal/api/handler — 9 internal-DEK handler tests
  • internal/app/settings — 5 registrar tests
  • cmd/worker — 5 UUID-from-IP derivation tests

Full suite runs in under 5 seconds.

Documentation

Doc What it covers
docs/VENDOR_LOCKIN.md Every external dependency and how to replace it
docs/INTERNAL_API_AUTH.md How workers authenticate to the backend
docs/MULTI_IP_WORKERS.md Hetzner CX32 + 16 Primary IPs deployment recipe
resources/architecture.md Control-plane vs execution-plane split, encryption model
resources/local-development.md Docker Compose, profiles, seeding
resources/deployment-guide.md Production control plane + worker fleet
resources/Events.md Event bus event reference
resources/EMSG.md Encrypted-message blob format

Security

If you find a vulnerability please email security@warmbly.com rather than opening a public issue. We prefer responsible disclosure and will credit you in the release notes.

The encryption model is documented in resources/architecture.md. The internal-API auth model is in docs/INTERNAL_API_AUTH.md.

Contributing

Bug reports and PRs are welcome. The codebase follows Go community conventions (gofmt, go vet), TypeScript with the web/ config, and Rust with cargo fmt. Please run the full test suite before opening a PR.

For larger changes, open an issue first to discuss the approach. The maintainers respond fastest to PRs that:

  • Stay scoped to one logical change
  • Keep the worker free of new direct-data-service dependencies (no Postgres in workers; route through /api/v1/internal/* instead)
  • Add tests for new business logic
  • Don't break self-hostability (any new external dependency must have an open-source path documented in docs/VENDOR_LOCKIN.md)

License

Licensed under the Apache License 2.0. Copyright 2026 Mindroot Ltd. See LICENSE for the full text.


Built with the boring tools that actually work in production.

Languages
Go 41.3%
TypeScript 36.5%
Swift 9.9%
Astro 9.3%
Elixir 0.9%
Other 2%