{"id":51865990,"url":"https://github.com/klppl/postern","last_synced_at":"2026-07-24T16:02:27.453Z","repository":{"id":370771305,"uuid":"1221915679","full_name":"klppl/postern","owner":"klppl","description":"Self-hosted email gateway: microservices POST to an HTTP API, messages queue in SQLite and deliver via SMTP or MXroute's HTTP API. Single Go binary, no CGO, retries, per-key rate limits, templates, admin UI.","archived":false,"fork":false,"pushed_at":"2026-07-10T20:36:59.000Z","size":112,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-07-10T22:13:41.476Z","etag":null,"topics":["email","email-api","email-gateway","golang","microservices","mxroute","self-hosted","smtp","sqlite","transactional-email"],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/klppl.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-26T20:56:50.000Z","updated_at":"2026-07-10T20:37:14.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/klppl/postern","commit_stats":null,"previous_names":["klppl/postern"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/klppl/postern","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/klppl%2Fpostern","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/klppl%2Fpostern/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/klppl%2Fpostern/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/klppl%2Fpostern/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/klppl","download_url":"https://codeload.github.com/klppl/postern/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/klppl%2Fpostern/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35847531,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-07-20T02:08:10.276Z","status":"online","status_checked_at":"2026-07-24T02:00:07.870Z","response_time":62,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["email","email-api","email-gateway","golang","microservices","mxroute","self-hosted","smtp","sqlite","transactional-email"],"created_at":"2026-07-24T16:02:26.678Z","updated_at":"2026-07-24T16:02:27.446Z","avatar_url":"https://github.com/klppl.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Postern\n\nSelf-hosted email gateway. Your microservices POST to `/api/v1/send`; Postern\nqueues the message in SQLite, renders any template, and delivers it through a\nprovider — a standard SMTP relay or MXroute's HTTP SMTP API.\n\nSingle Go binary, no CGO (`modernc.org/sqlite`), no external broker. Migrations,\nadmin templates, and static assets are embedded — only `.env` and the DB file\nare read at runtime.\n\n## Features\n\n- **HTTP send API** with `pn_`-prefixed API keys (stored hashed, shown once).\n- **Recipients bound to the key** by default; per-key opt-in for request-supplied\n  `to`/`cc`/`bcc` (all-or-nothing, capped at 50). `from` is never overridable.\n- **Queue + retry**: single worker drains the outbox, backoff 1m→5m→15m→1h→6h→24h,\n  dead after 6 attempts. 5xx SMTP errors dead-letter immediately.\n- **Per-key rate limits** (minute/hour/day), persisted in SQLite.\n- **Handlebars templates** (`{{var}}`) with subject/text/HTML variants; public or\n  per-key allow-listed.\n- **Server-rendered admin UI** with signed-cookie sessions for SMTP config, keys,\n  templates, and message inspection.\n- **Encryption at rest**: one master key AES-256-GCM-encrypts the SMTP password\n  and HMAC-signs cookies.\n\n## Quick start\n\n```bash\ngo build -o postern ./cmd/postern\ncp .env.example .env      # set POSTERN_MASTER_KEY (openssl rand -hex 32) + admin creds\n./postern                 # open http://localhost:8080/admin/\n```\n\nSign in, pick a delivery mode and fill in provider config, then create an API\nkey (the raw key is shown once).\n\n## API\n\n`POST /api/v1/send`, `Authorization: Bearer pn_...`, JSON body:\n\n```json\n{ \"subject\": \"Welcome\", \"body\": \"Hi.\", \"body_html\": \"\u003cp\u003eHi.\u003c/p\u003e\" }\n```\n\nor with a template:\n\n```json\n{ \"template_name\": \"welcome\", \"variables\": { \"name\": \"Alex\" } }\n```\n\nAdd `to`/`cc`/`bcc` when the key allows recipient override. Responses:\n`202` queued, `400` bad request, `401` bad key, `429` rate limited (honor\n`Retry-After`). Delivery is async; check status in `/admin/messages/`.\n`GET /healthz` → `{\"ok\":true}`.\n\n## Configuration\n\nBootstrap and secrets are env vars; provider config and retention live in SQLite\nand are edited in the admin UI (re-read on every send, so rotating credentials or\nswitching providers needs no restart).\n\n| Variable                  | Required | Default      | Notes                                      |\n| ------------------------- | -------- | ------------ | ------------------------------------------ |\n| `POSTERN_MASTER_KEY`      | yes      | —            | 32-byte hex/base64; encrypts secrets + signs cookies |\n| `POSTERN_DB_PATH`         | no       | `postern.db` | SQLite file path                           |\n| `POSTERN_LISTEN_ADDR`     | no       | `:8080`      | `host:port`                                |\n| `POSTERN_ADMIN_USERNAME`  | no       | —            | Bootstraps the first admin user            |\n| `POSTERN_ADMIN_PASSWORD`  | no       | —            | Same                                       |\n| `POSTERN_TLS_CERT` / `_KEY`| no      | —            | Built-in TLS (set both or neither)         |\n| `POSTERN_WORKER_INTERVAL` | no       | `1s`         | Outbox poll interval                       |\n| `POSTERN_SHUTDOWN_GRACE`  | no       | `30s`        | HTTP shutdown grace                        |\n\n**Back up the master key** — losing it makes the stored SMTP password\nunrecoverable.\n\n## Deploy\n\nRun behind a reverse proxy that terminates TLS (set\n`POSTERN_LISTEN_ADDR=127.0.0.1:8080`), or use built-in TLS via the cert/key vars.\n\n```bash\n# systemd\nsudo ./deploy/install.sh \u0026\u0026 sudo systemctl start postern\n\n# docker (distroless, ~25 MB)\ndocker run -d -p 8080:8080 -v postern-data:/data \\\n  -e POSTERN_MASTER_KEY=$(openssl rand -hex 32) \\\n  -e POSTERN_ADMIN_USERNAME=admin -e POSTERN_ADMIN_PASSWORD=changeme postern\n```\n\n## Build\n\n```bash\ngo test ./...\ngo build -trimpath -ldflags=\"-s -w\" -o postern ./cmd/postern\n```\n\nRequires Go 1.22+. No CGO.\n\n## License\n\n[The Lagom License](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fklppl%2Fpostern","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fklppl%2Fpostern","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fklppl%2Fpostern/lists"}