Posthorn loads a single TOML file at startup. By default the standalone binary reads /etc/posthorn/config.toml, overridable via --config <path>.
The config file resolves ${env.VAR} placeholders against the process environment as a post-parse step. Missing env vars are config-validation errors, not runtime errors — posthorn validate will surface them before the listener starts.
This page is the canonical reference for every field. For an annotated example, see the Quick start.
One or more default recipient addresses. Plain addr@host or "Name <addr@host>". Each entry must parse as a valid email. API-mode endpoints can override per request via the to_override JSON field.
from
string
yes
—
Sender address. Format: "Name <addr@host>" or plain addr@host. Per-request override is not supported — a leaked API key cannot be used to spoof other senders.
reply_to_email_field
string
no
the value of email_field
Form field whose value to use as the email’s Reply-To header. Set to a name that doesn’t exist in the submission to disable Reply-To.
Maximum request body size. Format: "32KB", "1MB", "512KB". Exceeding → 413. The 1 MB default is safe-by-default; bump up for endpoints accepting large form uploads.
Classless Inter-Domain Routing (CIDR) ranges and/or preset names. When the request’s RemoteAddr is in one of these networks, the rate limiter reads the client IP from X-Forwarded-For (rightmost untrusted).
strip_client_ip
bool
no
false
When true, omit the resolved client IP from log lines for this endpoint. Rate-limit bucketing is unaffected — the IP is still computed; it just doesn’t reach logs.
Preset names accepted in trusted_proxies — cloudflare is populated; the rest are reserved as empty slots to avoid name churn when they’re populated later:
API mode swaps form-mode browser defenses for Authorization: Bearer auth and JSON body parsing. See API mode.
Field
Type
Required
Default
Description
auth
string
no
"form"
"form" (default) or "api-key". The two modes are mutually exclusive per endpoint.
api_keys
[]string
yes when auth = "api-key"
unset
Bearer tokens accepted on this endpoint. ${env.VAR} substitution honored. Empty list is rejected at parse time. Form-mode endpoints reject this field at parse time.
idempotency_cache_size
int
no
10000
Per-endpoint idempotency cache capacity. Least Recently Used (LRU) eviction, 24-hour TTL (time-to-live). API-mode only — form-mode endpoints reject at parse time.
Configuring honeypot, allowed_origins, redirect_success, redirect_error, or csrf_secret on an API-mode endpoint is a parse error — those defenses only make sense for browser-facing form endpoints.
HMAC key for token verification. Must be at least 16 bytes. When set, every form submission must carry a _csrf_token field issued at form-render time. API-mode endpoints reject at parse time.
csrf_token_ttl
duration
no
"1h"
Maximum age of a CSRF token. Tokens older than this are rejected with 403.
When true, the endpoint runs the full pipeline (validation, template render, recipient resolution) but skips transport.Send and returns the prepared message in the 200 response body.
When true, terminal failures log the full submission payload under the form field at ERROR level. When false, only field NAMES log (under form_fields).
When true (default), AUTH / MAIL / RCPT are rejected until the client has issued STARTTLS. Set explicitly to false only for local development on a loopback listener.
tls_cert
string
conditional
—
Path to PEM cert. Required when require_tls = true or when auth_required involves client certs.
tls_key
string
conditional
—
Path to PEM private key. Same conditions as tls_cert.
One of "smtp-auth", "client-cert", "either", or "none". See the internal-SMTP-relay recipe for when "none" is appropriate (private Docker network only — the sender allowlist becomes the only ingress gate).
smtp_users
[]table
yes for smtp-auth or either
—
List of AUTH PLAIN credential pairs. Each entry has username (string) and password (string; ${env.VAR} honored). Not used in "none" mode.
client_cert_ca
string
yes for client-cert or either
—
Path to a PEM-encoded CA bundle. Client certs signed by this CA are accepted.
Same shape as [endpoints.transport]. The listener forwards every accepted submission through this single transport — one listener has one outbound transport; per-recipient routing is a future feature.