Skip to content

Self-hosting Postbag: one container, one Postgres

The hosted product is the same image with billing turned on. No feature may depend on a cloud-only service without a self-host path; that is a principle, not a roadmap item.

In short: Self-hosted Postbag runs as a single container with POSTBAG_ROLE set to api, worker or all, against Postgres 16. Migrations can run on boot (MIGRATE_ON_BOOT=true), health is exposed at /health, email goes through your own Resend domain, and signups can be disabled for a single-organization install.

What you need

  • Docker (the image is multi-arch: arm64 and amd64, Alpine-based).
  • Postgres 16 (the compose file starts one).
  • A Resend API key and a verified sending domain for email destinations. Telegram and webhooks need nothing else.
  • A public URL for APP_URL, so submit URLs, embed snippets and docs links are correct.

Environment

DATABASE_URL, APP_URL, BETTER_AUTH_SECRET, POSTBAG_ROLE (api | worker | all), PORT (3000), TZ, MIGRATE_ON_BOOT, RESEND_API_KEY, MAIL_FROM. Run api and worker as two containers from the same image if you want to scale them separately; several workers are safe because delivery claims use FOR UPDATE SKIP LOCKED.

Security posture

API keys are hashed (SHA-256) at rest, shown once, and scoped (manage, read, submit). Every tenant table carries organization_id and repositories require an organization scope. Row-level security policies and a postbag_app database role ship in the migrations as a second fence; owners of self-hosted databases are deliberately exempt so a non-superuser install still works. Health at /health reports database status, worker heartbeat and the oldest pending delivery age.

What is different from the hosted product

Nothing in features. Plan limits are effectively unlimited under the selfhost plan. You bring your own email domain. Billing does not exist.

Questions, answered

Is Postbag open source?
Yes. The server is AGPL-3.0 and the client packages (SDK, CLI, MCP server) are MIT. Self-hosting parity is designed in from the start (one image, one database, no cloud-only dependencies). The repository opens publicly with the first npm release; ask if you need access before that.
Can I run the API and the worker separately?
Yes. Start two containers from the same image with POSTBAG_ROLE=api and POSTBAG_ROLE=worker. Several workers are safe.
Do I need Redis or a queue?
No. Postgres is the only stateful dependency; the deliveries table is the queue.

Your first form is three minutes away.

Sign up, get a submit URL, point a form at it. The first submission lands in your inbox and your email. Everything else appears when you need it.