Installation
Posthorn ships in two forms. Docker is the recommended path for most operators — it’s a single container running next to your apps, and it’s the deployment shape exercised most heavily in tests and documentation.
docker pull ghcr.io/craigmccaskill/posthorn:latestMulti-arch images are published for linux/amd64 and linux/arm64. The container expects a TOML config mounted at /etc/posthorn/config.toml and listens on 8080 by default.
A minimal docker-compose.yml:
services: posthorn: image: ghcr.io/craigmccaskill/posthorn:latest restart: unless-stopped volumes: - ./posthorn.toml:/etc/posthorn/config.toml:ro environment: POSTMARK_API_KEY: ${POSTMARK_API_KEY} ports: - "8080:8080"Continue to the Quick start for a working contact form.
go install github.com/craigmccaskill/posthorn/cmd/posthorn@latestRequires Go 1.25+. The binary is fully self-contained and runs anywhere.
posthorn validate --config posthorn.toml # check the config without servingposthorn serve --config posthorn.toml # start the HTTP listenerPre-built binaries for common platforms are attached to each GitHub release.
Prerequisites
Section titled “Prerequisites”Regardless of how you install Posthorn, you need:
| Prerequisite | Why |
|---|---|
| A transactional mail provider account with a verified sender domain | Posthorn relays through your chosen provider. Five transports ship: Postmark, Resend, Mailgun, AWS SES, and a generic outbound-SMTP relay — see Transports. |
| The provider’s API key (or SMTP credentials for the outbound-SMTP transport) | Used in [endpoints.transport.settings]. For Postmark: the server token, not the account token. |
| Sender Policy Framework (SPF), DomainKeys Identified Mail (DKIM), and Domain-based Message Authentication, Reporting & Conformance (DMARC) records on your sending domain | Required for deliverability regardless of transport — see DNS |
| A reverse proxy in front of Posthorn (Caddy, nginx, Traefik, Cloudflare) | TLS termination, HTTP/2, request logging |
Verifying the install
Section titled “Verifying the install”docker run --rm ghcr.io/craigmccaskill/posthorn:latest version# posthorn v1.0.0If you’re running the standalone binary:
posthorn version(--version and -v work too; version is the canonical subcommand. Local development builds print posthorn v0.0.1-dev — the released tag is injected at build time.)
For a deeper sanity check, run posthorn validate --config <your-config> — it parses the TOML, resolves ${env.VAR} placeholders, and reports schema errors without starting the listener.