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.