Skip to content

Endpoints

An endpoint is a path Posthorn serves, together with the transport, recipients, templates, and protections that apply to submissions on that path.

You can configure as many endpoints as you want in a single config file. Each is fully independent — separate rate-limit counters, separate templates, separate honeypot field names, separate Postmark accounts if you want.

If you have one contact form, one endpoint is enough:

[[endpoints]]
path = "/api/contact"
to = ["you@example.com"]
from = "Contact Form <noreply@example.com>"
required = ["name", "email", "message"]
subject = "Contact: {{.name}}"
body = "From: {{.name}} <{{.email}}>\n\n{{.message}}"
[endpoints.transport]
type = "postmark"
[endpoints.transport.settings]
api_key = "${env.POSTMARK_API_KEY}"

Several forms relaying through the same Postmark server. They share a key via env var; the rate limit is per-endpoint so they don’t interfere with each other.

[[endpoints]]
path = "/api/contact"
to = ["alerts@example.com"]
from = "Contact <noreply@example.com>"
subject = "Contact: {{.name}}"
body = "..."
[endpoints.transport]
type = "postmark"
[endpoints.transport.settings]
api_key = "${env.POSTMARK_API_KEY}"
[endpoints.rate_limit]
count = 5
interval = "1m"
# -----
[[endpoints]]
path = "/api/feedback"
to = ["product@example.com"]
from = "Feedback <noreply@example.com>"
subject = "Feedback: {{.subject}}"
body = "..."
[endpoints.transport]
type = "postmark"
[endpoints.transport.settings]
api_key = "${env.POSTMARK_API_KEY}"
[endpoints.rate_limit]
count = 20
interval = "1m"

Routing transactional and marketing mail to separate Postmark servers — different DKIM signatures, different bounce policies, different per-server tokens.

[[endpoints]]
path = "/api/contact"
# ...
[endpoints.transport]
type = "postmark"
[endpoints.transport.settings]
api_key = "${env.POSTMARK_TRANSACTIONAL_KEY}"
[[endpoints]]
path = "/api/newsletter"
# ...
[endpoints.transport]
type = "postmark"
[endpoints.transport.settings]
api_key = "${env.POSTMARK_BROADCAST_KEY}"
ConcernScope
Rate limit countersPer-endpoint, per-IP
TemplatesPer-endpoint
RecipientsPer-endpoint
TransportPer-endpoint
Honeypot field namePer-endpoint
Allowed originsPer-endpoint
trusted_proxiesPer-endpoint
Logging level / formatGlobal (via [logging])
HTTP listener portGlobal (Posthorn process)

This means a malicious actor flooding /api/contact will hit your contact rate limit but won’t affect submissions to /api/feedback. They run as independent token buckets.

Two endpoints cannot share the same path. The config loader rejects this at validation time:

[[endpoints]]
path = "/api/contact"
[[endpoints]]
path = "/api/contact" # ← rejected: duplicate path

Path matching is exact, not prefix-based. /api/contact does not match /api/contact/.

Posthorn reads the config once at startup. Adding a new endpoint requires:

  1. Edit posthorn.toml
  2. posthorn validate --config posthorn.toml
  3. Restart the container or process

Hot reload is not currently supported. A future release may add SIGHUP-triggered reload.