Hotdry.

Article

Posthorn:无传统邮件服务器的自托管 SMTP 中继网关实践

用 Posthorn 在自托管应用与交易邮件服务商之间搭建统一网关,解决云主机 SMTP 阻断与多应用邮件配置碎片化问题。

2026-05-27systems

在 2026 年,已经很少有人愿意自己维护一套完整的邮件服务器。IMAP 存储、DKIM 密钥管理、MX 记录、退信处理、发件人声誉维护 —— 每一项都是长期负担。然而,自托管生态中的各类应用 ——Ghost 博客、Gitea 代码托管、Umami 分析、Mastodon 实例 —— 又都离不开邮件能力:密码重置、评论通知、管理员警报、定时报告。

传统方案是每个应用独立接入 Postmark、Resend、Mailgun 或 AWS SES。这导致同一套 API 密钥在多个配置文件中重复出现,每个应用都有自己的重试逻辑和退信处理 quirks。更棘手的是,DigitalOcean、AWS Lightsail、Linode、Vultr 等主流云主机默认阻断出站 25 端口,那些只支持 SMTP 的老旧应用直接无法工作。

Posthorn 的定位正是解决这一断层:它不做邮件存储,也不管理发件 IP 声誉,而是作为自托管应用与交易邮件服务商之间的统一集成层。

架构设计:三入口收敛到单一传输层

Posthorn 的核心抽象是将所有邮件请求统一转换为内部的 transport.Message,再路由到用户选定的服务商。它提供三种入口形态,覆盖不同场景:

HTTP 表单模式面向网站联系表单、新闻订阅等场景。配置中可启用蜜罐字段(honeypot)过滤爬虫、校验 Origin/Referer 防跨站伪造、设置速率限制(如每分钟 5 次提交),并支持自定义邮件模板。表单验证失败时直接返回错误,不触及下游服务商。

HTTP API 模式面向 Cloudflare Worker、定时任务、支付回调等服务器端场景。采用 Bearer Token 认证,支持幂等性键(Idempotency-Key)避免重复发送,并允许通过 to_override 字段在单次请求中动态指定收件人,适合密码重置这类事务性邮件。

SMTP 监听器则是解决云主机阻断问题的关键。Posthorn 在本地端口(如 2525)启动兼容的 SMTP 服务,支持 AUTH PLAIN 认证或客户端证书,强制 STARTTLS。Ghost、Gitea、NextCloud、Authentik 等应用只需将 SMTP 配置指向 Posthorn,后者解析 MIME 内容后通过 HTTPS API 转发给 Postmark 或 Resend,完全绕过被阻断的 25 端口。

三种入口最终都收敛到同一传输层,支持 Postmark、Resend、Mailgun、AWS SES 以及任意支持 STARTTLS 的出站 SMTP 中继。切换服务商只需修改 TOML 配置文件中的 type 字段。

部署实践:Docker Compose 与配置要点

Posthorn 以单二进制文件发布,官方推荐通过容器部署。最小化配置只需一个 docker-compose.yml 和一个 posthorn.toml

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:
      - "127.0.0.1:8080:8080"

配置文件中定义端点时,建议将 HTTP 服务绑定到回环地址,通过 Caddy、nginx 或 Traefik 反向代理暴露到公网,由后者处理 TLS 终止。Posthorn 本身不内置证书管理,这是有意为之 —— 自托管用户通常已有成熟的反向代理层。

对于 SMTP 监听器场景,需要额外挂载 TLS 证书:

[smtp_listener]
listen = ":2525"
require_tls = true
tls_cert = "/etc/posthorn/cert.pem"
tls_key = "/etc/posthorn/key.pem"
auth_required = "smtp-auth"
allowed_senders = ["*@yourdomain.com"]
max_recipients_per_session = 10
max_message_size = "1MB"

[[smtp_listener.smtp_users]]
username = "ghost"
password = "${env.GHOST_SMTP_PASSWORD}"

Ghost 的 SMTP 配置随后只需指向 posthorn.yourdomain.com:2525,使用上述用户名密码即可。Posthorn 会校验发件人域名白名单,解析 MIME 后通过 Postmark 的 HTTPS API 发出。

生产 checklist

在将真实流量接入前,需完成以下步骤:

DNS 记录:确保发送域名已配置 SPF、DKIM 和 DMARC 记录。没有这些,邮件几乎必然进入垃圾箱。Posthorn 文档提供了各服务商的 DNS 配置指引。

反向代理:Posthorn 不处理 TLS,必须运行在 Caddy、nginx 或 Traefik 之后。建议同时配置 rate limit 和 fail2ban,防止暴力破解 API 密钥。

密钥管理:API 密钥和 SMTP 密码应通过环境变量注入,避免硬编码在配置文件中。Docker secrets 或 systemd credentials 都是可行方案。

监控与日志:Posthorn 输出结构化日志,建议接入 Prometheus 或自建日志聚合。关注 5xx 错误率和下游服务商的退信反馈,及时调整速率限制阈值。

适用边界与替代方案

Posthorn 明确不是以下角色:

  • 不是邮件服务器:不提供 IMAP/JMAP、不存储邮件、不管理 DKIM 密钥。如需完整邮件托管,应考虑 Stalwart、Mailcow 或 iRedMail。
  • 不是独立出站基础设施:不维护自己的 SMTP 舰队或 IP 声誉池。如需自建发送能力,可参考 Postal 或 Hyvor Relay。
  • 不是营销邮件平台:不提供列表管理、分段或 campaign 仪表板。Listmonk 更适合此类需求。
  • 不是 Webmail:不提供邮件阅读界面。需要配合 Roundcube 或 Snappymail 与邮件服务器一起使用。

Posthorn 的 sweet spot 是:你已经选择了 Postmark/Resend 等服务商处理邮件送达,但希望统一管理自托管栈中的邮件出口,同时解决云主机 SMTP 阻断问题。对于只有一两个简单表单的个人站点,直接调用服务商 HTTP API 可能更简单;但对于运行 Ghost + Gitea + Umami + 多个 Cloudflare Worker 的复杂自托管环境,Posthorn 的集中式配置和统一认证机制能显著降低维护负担。

资料来源

systems

内容声明:本文无广告投放、无付费植入。

如有事实性问题,欢迎发送勘误至 i@hotdrydog.com