Skip to content

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.

Terminal window
docker pull ghcr.io/craigmccaskill/posthorn:latest

Multi-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.

Regardless of how you install Posthorn, you need:

PrerequisiteWhy
A transactional mail provider account with a verified sender domainPosthorn 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 domainRequired for deliverability regardless of transport — see DNS
A reverse proxy in front of Posthorn (Caddy, nginx, Traefik, Cloudflare)TLS termination, HTTP/2, request logging
Terminal window
docker run --rm ghcr.io/craigmccaskill/posthorn:latest version
# posthorn v1.0.0

If you’re running the standalone binary:

Terminal window
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.