HTTP form ingress
Accepts application/x-www-form-urlencoded and multipart/form-data POSTs
on configured paths. Templates the body, validates the fields, sends the
mail.
DigitalOcean, AWS Lightsail, Linode, and most cloud hosts block outbound SMTP on ports 25, 465, and 587. The block is policy, not configurable — providers explicitly recommend an HTTP API service like Postmark instead.
That breaks two patterns at once:
Posthorn is the bridge. Drop it in as a container, point your forms at it (v1.0) or your apps’ SMTP config at it (v1.2), and it relays through Postmark over HTTP.
HTTP form ingress
Accepts application/x-www-form-urlencoded and multipart/form-data POSTs
on configured paths. Templates the body, validates the fields, sends the
mail.
Postmark transport
Bespoke ~80-line HTTP client. No third-party SDK. API keys never appear in logs. Headers pass through Postmark’s structured JSON API — no string concatenation, no injection vector.
Defense in depth
Honeypot, Origin/Referer fail-closed, token-bucket rate limit with LRU eviction at 10K IPs, max body size, required-field validation, email syntax check.
Two deployment shapes
Standalone Docker container (primary) or a Caddy v2 module for operators already running Caddy. Both run the same pipeline — identical behavior by construction.
Structured logging
Every event emits JSON with a UUIDv4 submission ID, endpoint, transport, and latency. Failed submissions log the full payload so you can recover from terminal errors.
Retry that respects deadlines
One retry on transient/5xx (1s backoff), one retry on 429 (5s backoff), no retry on 4xx config errors, hard 10-second timeout including retries.
services: posthorn: image: ghcr.io/craigmccaskill/posthorn:latest volumes: - ./posthorn.toml:/etc/posthorn/config.toml:ro environment: POSTMARK_API_KEY: ${POSTMARK_API_KEY} ports: - "8080:8080"[[endpoints]]path = "/api/contact"to = ["you@example.com"]from = "Contact Form <noreply@example.com>"honeypot = "_gotcha"allowed_origins = ["https://example.com"]required = ["name", "email", "message"]subject = "Contact from {{.name}}"body = """From: {{.name}} <{{.email}}>
{{.message}}"""
[endpoints.transport]type = "postmark"
[endpoints.transport.settings]api_key = "${env.POSTMARK_API_KEY}"
[endpoints.rate_limit]count = 5interval = "1m"Reverse-proxy /api/contact from your front door (Caddy, nginx, Traefik) at https://posthorn:8080. Point your form’s action at /api/contact. Done.
PostHog is product analytics. Posthorn is an email gateway. Similar names, different categories, zero functional overlap.
Apache-2.0. Currently pre-v1.0 — spec is locked, implementation is in flight. Track progress on the GitHub project board.