Skip to content

Roadmap

This page is the canonical public roadmap. The narrative lives here; the execution surface (open issues, in-flight work) lives in GitHub Milestones.

Posthorn ships in deliberately small, scoped releases. v1.0 absorbs what was originally sequenced as v1.1, v1.2, and v1.3 into a single release because the surface area is small enough that splitting them produced more carve-outs than coherent product moments. v2 is the next planned milestone — the stateful-platform shift.

Status: released. See the GitHub Releases page for the version history.

Scope: three ingress shapes, five outbound transports, full spam-protection stack, idempotency, dry-run, Prometheus metrics, hand-rolled SMTP listener for internal apps.

IngressDetail
HTTP form (default)application/x-www-form-urlencoded, multipart/form-data; honeypot, Origin/Referer fail-closed, optional CSRF tokens, rate limit
HTTP API modeBearer auth, JSON body, idempotency keys, per-request to_override, per-key rate limit
SMTP listenerAUTH PLAIN / client-cert, STARTTLS-required, sender allowlist + recipient allowlist or cap, size cap
TransportDetail
PostmarkHTTP API, server-token auth
ResendHTTP API, bearer auth
MailgunHTTP API, basic auth, multipart body, US/EU regions
AWS SESHTTP API, Signature Version 4 (SigV4) auth, region-scoped
Outbound SMTPAny STARTTLS-capable relay; stdlib net/smtp

Operational essentials: /healthz, /metrics (Prometheus exposition), dry-run mode, IP-stripping, named trusted_proxies presets (Cloudflare).

What you can do today:

  • Drop in a self-hosted contact form on any host that blocks outbound SMTP
  • Run Posthorn as a Docker sidecar next to your blog / static site / app stack
  • Hit it from a Cloudflare Worker for transactional sends with safe retries
  • Point Ghost / Gitea / Mastodon / Authentik at Posthorn’s SMTP listener and let them keep speaking SMTP
  • Reverse-proxy from whatever front door you already use (Caddy, nginx, Traefik, Cloudflare)

Get started →

Track on GitHub →

A focused minor release that stacks additive defenses on top of the v1.0 form-mode protections (honeypot, Origin/Referer fail-closed, rate limit, CSRF). Each item is independently shippable and backwards-compatible — existing configs keep working unchanged. No platform-shape changes, no storage layer required.

The order below reflects a concrete, validated workload: live contact-form spam against a production deployment with honeypot + Origin + rate-limit all correctly configured still lands, because form-blaster bots (Xrumer / GSA SER) render the page for a valid Referer, skip the empty honeypot, and trickle under the burst cap. The items that actually beat that pattern lead the release; the ones that the observed spam would have walked straight past are deprioritized.

Leads the release — beats the observed pattern:

  • Content-shape validation — bounded primitives only: max_links URL cap and min_message_length per-field minimum. The single highest-value item: most form-blaster spam exists to deliver a link, so a URL cap cuts the one element the spam can’t omit. (Single-link spam forces max_links = 0, which is a content policy, not a bot-blocker — hence the next two items.) Operator-managed regex blocklists are explicitly NOT in scope (slippery slope into spam-filter territory). (#34)
  • Reputation-based spam check — optional StopForumSpam lookup on submitter IP/email before send; fail-open so an outage never blocks legitimate mail. Targets the adversary directly (Xrumer/GSA SER is exactly StopForumSpam’s corpus) and, unlike content rules, stops the bot without restricting what humans may write. (#44)
  • Proof-of-browser token — a JS-gated submit token that blocks form-blasters which POST directly without executing JavaScript. No third-party dependency, no UX friction; the cheap rung below Turnstile. (#45)
  • Cloudflare Turnstile verification — optional per-endpoint captcha for high-spam targets; the durable backstop that stops even headless-browser bots. siteverify call before template rendering, byte-identical 200 response on failure. (#33)
  • CSRF minimum-age check — extends the existing CSRF TTL with a floor; bots that POST within milliseconds of token issuance get rejected. (#32)

Deprioritized — ships when a real deployment surfaces the matching pattern:

  • Multiple honeypot fieldshoneypot accepts an array; any field being filled trips rejection. Marginal against bots that skip hidden fields entirely. (#31)
  • Operator-provided email-domain blocklist — disposable-provider domains from a file. Deprioritized: the observed spam came from mainstream providers (gmail, gmx, zoho), so a disposable-domain list scored zero against it. (#35)
  • Per-day rate-limit cap — stacks on the existing token bucket for long-running low-rate abuse. Deprioritized: the observed spam is a distributed trickle from varied IPs, which a per-identity daily cap doesn’t touch. (#36)

Ordering reflects validated workload over upvotes — an item that a real deployment proves it needs (or proves it doesn’t) moves accordingly.

Track on GitHub →

The architectural shift that unlocks operating Posthorn as a real mail platform: durable state, lifecycle awareness, deliverability hygiene.

Storage layer:

  • SQLite submission log — every submission persisted, queryable
  • Retry queue across restarts — failed sends survive a container restart and retry later

Mail-platform features built on storage:

  • Suppression list — auto on hard bounces and spam complaints; admin API for inspection and manual entries. Refuses to send to suppressed addresses (returns 200 with {"suppressed": true} so callers treat it as final, not transient).
  • Durable idempotency — the v1.0 in-memory cache becomes restart-survivable
  • Lifecycle event callbacks — delivery / bounce / open / click events forwarded to the originating caller with a Hash-based Message Authentication Code (HMAC) signed webhook
  • Automatic unsubscribe link injection — per-recipient signed tokens, hosted unsubscribe endpoint, RFC 8058 one-click headers (which Gmail and Yahoo now require for senders >5000/day)
  • HTML body support
  • File attachments — multipart form uploads forwarded to the transport
  • Multiple outputs per endpoint — email + webhook + log fan-out for the same submission
  • Multi-tenant SMTP routing — RCPT-driven transport selection (one SMTP listener serves multiple tenants with separate sender identities)

The Transport.Send interface stays the synchronous primitive; the queue wraps it. v1.0 deployments keep working unchanged.

Smaller items on the queue (v1.0.x / v1.1)

Section titled “Smaller items on the queue (v1.0.x / v1.1)”

Operator feedback from the v1.0 launch surfaced a few quality-of-life items that don’t justify a v2 wait. These ship as patch releases or a 1.1 feature bump:

  • Auto-generate ephemeral self-signed TLS certs when require_tls = true and no tls_cert/tls_key is provided. For internal-network deployments where TLS-on-the-wire is wanted but certificate file management isn’t. Requires explicit startup logging that the cert is ephemeral and not trustworthy for external clients. Issue #27.
  • Friendlier validation errors with deployment-pattern hints. Errors that surface common-config-shape examples (“for internal Docker networks, use auth_required = none; for production, use smtp-auth with require_tls = true”). Polish, but the validation surface is where new operators first encounter Posthorn’s mental model. Issue #28.

No date. Depends entirely on community traction:

  • Admin UI — embedded web app for browsing submissions, viewing logs, retrying failed sends (requires v2’s SQLite storage)
  • Proof-of-work spam challenge — computational cost imposed on submitters; defeats botnet spam from many low-rate IPs that the v1.0 rate limiter can’t catch
  • PGP encryption — encrypt outbound mail to recipient public keys at the gateway

If you want to push something forward:

  1. Open a feature request with a concrete use case. Pitches like “wouldn’t it be cool if…” get less weight than “I want to use Posthorn for X, but Y is missing.”
  2. Comment on the relevant milestone or existing issue with your use case. Adoption signal beats pure upvotes.
  3. Start a discussion for half-formed ideas where the right framing isn’t clear yet.

These come up in conversation and are worth naming explicitly:

FeatureWhy not
Acting as a “real” MTA (MX records, inbound mail)Posthorn is an authenticated relay, not a mail server. The SMTP listener accepts mail from known internal clients only — see SMTP ingress.
Markdown body renderingTemplates are plain text. v2 may add markdown with explicit escaping.
GUI installer / first-run wizardThe TOML config is the source of truth; no UI in front of it.
Replacing transactional providers entirely (running our own outbound SMTP fleet)That’s “build a postfix replacement” territory; out of scope forever. The point of Posthorn is to use your provider, not become one.
Batch send APIDeferred without a target version. The api-mode audience surveyed didn’t have a real workload for it; we’ll reconsider when an operator surfaces with concrete batch volume that round-trip overhead actually constrains.