Show HN: Posthorn,无需邮件服务器的自托管邮件系统

Hacker News Top 工具

摘要

Posthorn 是一个自托管的出站邮件网关,通过统一接口整合多个应用的邮件发送,支持 HTTP 和 SMTP 接收,并支持多种提供商传输。

Introducing Posthorn,一个自托管的电子邮件网关。在你的 VPS 上每个自托管应用与事务性邮件提供商之间,只需一个 Docker 容器(或 Go 二进制文件)。设置好 Posthorn 一次,将你的应用指向它,搞定。<p>我试图在 DigitalOcean 的 droplet 上部署 Ghost,发现 DO 和许多不同的 VPS 服务已经开始屏蔽默认的 SMTP 端口,以试图应对他们遭受的各种滥用行为。为了实际配置我的应用,我不得不拼凑出一个 Postfix 中继。<p>在另一个项目中,我有一个带有联系表单的静态网站,但我免费的 Formspree 账户偶尔会达到使用限制,我非常想要他们付费账户中才有的某些反垃圾邮件功能,于是我整理了一个 Caddy 模块来捕获 HTTP POST 请求并将它们转发给我的提供商。<p>我一直遇到这些相同的邮件问题。我想托管的许多服务(Gitea、Mastodon、Umami、Comentario)都遇到了同样的限制。这感觉是一个真正普遍的问题,却没有好的解决方案。<p>Posthorn 就是为了解决这个问题而构建的。它是一个小型 Go 二进制文件(或 10 MB 的 Docker 镜像),位于你的自托管应用和你选择的事务性邮件提供商之间(附带支持 Postmark、Resend、Mailgun、Amazon SES 或一个出站 SMTP 中继)。它还接受来自 HTML 表单的 POST 请求,以支持静态网站需求,同时增加安全层,如蜜罐字段、来源检查和 IP 速率限制。此外还有一个 JSON HTTP API,支持 Bearer 认证,适用于只需要一个 /send 端点的后端脚本或 cron 作业。<p>我现在在多种场景下亲自使用它,并且花了很多时间来打磨和测试,尽我所能进行验证。我很想知道这对你可能如何有用,有什么问题,以及你可能有的任何反馈。它是在 Apache 2.0 许可下的开源项目,我欢迎贡献。我计划长期支持和发展这个项目。<p>Code: <a href="https://github.com/craigmccaskill/posthorn" rel="nofollow">https://github.com/craigmccaskill/posthorn</a><p>Docs: <a href="https://posthorn.dev/" rel="nofollow">https://posthorn.dev/</a><p>Longer write up: <a href="https://craigmccaskill.com/introducing-posthorn/" rel="nofollow">https://craigmccaskill.com/introducing-posthorn/</a><p>Previous HN discussion on the exact issue I'm trying to solve: <a href="https://news.ycombinator.com/item?id=43620318">https://news.ycombinator.com/item?id=43620318</a>
查看原文
查看缓存全文

缓存时间: 2026/05/27 07:00

craigmccaskill/posthorn

来源:https://github.com/craigmccaskill/posthorn

Posthorn CI(https://github.com/craigmccaskill/posthorn/actions/workflows/ci.yml)

文档(https://posthorn.dev)

许可证:Apache-2.0

Go 版本(https://go.dev)

自托管项目的统一出站邮件层。

一个网关,连接你自托管的每个应用与你已选好的事务性邮件提供商。三种入口形态(HTTP 表单、HTTP API、SMTP),五种传输通道(Postmark、Resend、Mailgun、AWS SES、出站 SMTP 中继),单个 Go 二进制文件,单个 TOML 配置文件。

真实应用场景: Hugo + Comentario(https://posthorn.dev/recipes/hugo-comentario/)· Ghost(https://posthorn.dev/recipes/ghost/)· Gitea(https://posthorn.dev/recipes/gitea/)· Umami 摘要定时任务(https://posthorn.dev/recipes/umami/)· Cloudflare Worker(https://posthorn.dev/recipes/cloudflare-worker/)

为什么

没人想在 2026 年自己跑邮件服务器。自托管运营者使用 Postmark、Resend、Mailgun 或 AWS SES,因为它们便宜、能妥善处理投递问题,而且 SPF / DKIM / DMARC / 退信 / 发件人声誉等有别人操心。

但你自托管的每个应用都需要独立集成该服务。你的联系表单、Ghost 博客的管理员邮件、Gitea 的魔法链接、Mastodon 的通知、点击链接后触发密码重置邮件的 Cloudflare Worker——每个都需要自己的 API 密钥副本、自己的集成代码、自己在重试和退信处理上的特殊逻辑。

同样的出站关注点在你的整个技术栈中重复出现。

而且在那些阻止出站 SMTP 的云主机上——DigitalOcean、AWS Lightsail、Linode、Vultr——纯 SMTP 的应用若无变通办法根本无法工作。

Posthorn 就是这座桥梁。一个容器、一份配置、一套凭据。你的应用指向 Posthorn,Posthorn 与你的提供商通信。

应用连接方式Posthorn 所做的工作
HTTP 表单(联系表单、注册、告警 Webhook)蜜罐 + Origin/Referer + 速率限制 + 可选的 CSRF;模板化邮件;发送
HTTP API 模式(Worker、定时任务、支付处理器、内部服务)Authorization: Bearer 认证;JSON 请求体;幂等重试;每请求的 to_override 用于事务性发送
SMTP 监听(Ghost、Gitea、Mastodon、Matrix、NextCloud、Authentik 等任何发出 SMTP 的应用)AUTH PLAIN 或客户端证书;要求 STARTTLS;发件人 + 收件人白名单;解析 MIME;通过 HTTP API 传输转发

所有三种入口汇聚到同一个 transport.Message 和同一个出站提供商——可从 Postmark、Resend、Mailgun、AWS SES 或出站 SMTP 中继中选择。

Posthorn 不是什么

为避免走弯路:

它做什么建议改用
不是邮件服务器没有邮箱存储、没有 IMAP/JMAP、没有 DKIM 密钥管理、没有 MX 目标Stalwart(https://stalw.art)、Mailcow(https://mailcow.email)、iRedMail(https://www.iredmail.org)
不是自己的出站基础设施Posthorn 通过你选择的提供商进行中继;不运行自己的 SMTP 服务器群或管理 IP 声誉Postal(https://postalserver.io)、Hyvor Relay(https://hyvor.com/relay)
不是营销邮件平台没有列表管理、没有细分、没有活动仪表盘Listmonk(https://listmonk.app)
不是 Webmail / 邮箱界面没有阅读邮件的界面Roundcube、Snappymail(需配合邮件服务器)

它的核心定位是集成层,位于你自托管的应用与你已选好的事务性提供商之间。

文档

posthorn.dev(https://posthorn.dev)——入门指南、配置参考、部署指南、功能深入探讨、安全模型、HTTP API 参考、常见问题解答。包含十个教程,涵盖联系表单、新闻通讯注册、多表单站点、监控告警、Cloudflare Workers、内部 SMTP 中继(Docker Compose),以及 Hugo+Comentario、Ghost、Gitea 和自托管 Umami 摘要的完整案例研究。

项目历史及 v1.0 规范见 spec/

快速开始(Docker)

# docker-compose.yml
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" # 绑定到回环地址;通过前端反向代理
# posthorn.toml
[[endpoints]]
path = "/api/contact"
to = ["[email protected]"]
from = "Contact Form <[email protected]>"
honeypot = "_gotcha"
allowed_origins = ["https://example.com"]
required = ["name", "email", "message"]
subject = "Contact from {{.name}}"
body = """
From: {{.name}} <{{.email}}>

{{.message}}
"""
redirect_success = "/thank-you"

[endpoints.transport]
type = "postmark"
[endpoints.transport.settings]
api_key = "${env.POSTMARK_API_KEY}"

[endpoints.rate_limit]
count = 5
interval = "1m"

/api/contact 通过反向代理(Caddy、nginx、Traefik)从前端映射到 http://posthorn:8080。将你的表单的 action 指向 /api/contact。搞定。

完整教程:posthorn.dev/getting-started/quick-start(https://posthorn.dev/getting-started/quick-start/)。

API 模式(服务器到服务器)

适用于 Worker、定时任务、内部服务——任何使用 JSON 而非表单的应用:

[[endpoints]]
path = "/api/transactional"
to = ["[email protected]"]
from = "YourApp <[email protected]>"
auth = "api-key"
api_keys = ["${env.WORKER_KEY_PRIMARY}", "${env.WORKER_KEY_BACKUP}"]
required = ["subject_line", "message"]
subject = "{{.subject_line}}"
body = "{{.message}}"

[endpoints.transport]
type = "postmark"
[endpoints.transport.settings]
api_key = "${env.POSTMARK_API_KEY}"
curl -X POST https://posthorn.yourdomain.com/api/transactional \
  -H "Authorization: Bearer $WORKER_KEY_PRIMARY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: reset:user-123:$(date -u +%FT%H)" \
  --data '{
    "to_override": "[email protected]",
    "subject_line": "重置密码",
    "message": "点击此处:https://app.example.com/reset/abc"
  }'

完整教程:posthorn.dev/recipes/cloudflare-worker(https://posthorn.dev/recipes/cloudflare-worker/)。

SMTP 监听(Ghost / Gitea / Mastodon / Authentik)

适用于原生使用 SMTP 且无法重新配置为调用 HTTP API 的应用:

[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}"

[smtp_listener.transport]
type = "postmark"
[smtp_listener.transport.settings]
api_key = "${env.POSTMARK_API_KEY}"

将 Ghost(或任何应用的 SMTP 配置)指向 posthorn.yourdomain.com:2525,使用上述用户名/密码。Posthorn 解析 MIME,构建 transport.Message,通过 Postmark 转发。

完整文档:posthorn.dev/features/smtp-ingress(https://posthorn.dev/features/smtp-ingress/)。

选择传输通道

传输通道最适合认证方式请求体格式
Postmark事务性邮件,强投递默认设置X-Postmark-Server-TokenJSON
Resend现代 HTTP API,开发者友好的仪表盘Authorization: BearerJSON
Mailgun较高量级的事务性邮件,美国 + 欧洲区域HTTP Basicmultipart/form-data
AWS SESAWS 原生部署,大规模下最便宜AWS SigV4(定制)JSON
出站 SMTP任何支持 STARTTLS 的中继(Mailtrap、你的 Postfix smart host 等)AUTH PLAINSMTP DATA

切换提供商只需编辑 TOML——每种传输通道都实现相同的 Transport 接口。各提供商的配置见 posthorn.dev/configuration/transports(https://posthorn.dev/configuration/transports/)。

生产环境检查清单

在让真实流量进入 Posthorn 之前:

  1. DNS ——在发送域上设置 SPF、DKIM 和 DMARC 记录。没有这些,你的邮件会进垃圾箱。见 posthorn.dev/security/dns(https://posthorn.dev/security/dns/)。
  2. 反向代理 ——Posthorn 不终止 TLS。将其运行在 Caddy、nginx 或 Traefik 后面。见 posthorn.dev/deployment/reverse-proxy(https://posthorn.dev/deployment/reverse-proxy/)。
  3. allowed_origins(表单模式端点)——设置此项以将提交限制为你的域名。没有它,任何人都可以向你的端点发送 POST 请求。
  4. rate_limit ——为每个端点设置一个精简的速率限制(公共联系表单建议每秒 5 次;API 模式按匹配的密钥限制)。
  5. trusted_proxies ——如果在反向代理后面,列出其 CIDR(或使用 cloudflare 命名预设),这样速率限制器才能看到真实客户端 IP。
  6. /healthz/metrics ——自动注册在同一个监听器上。将 Docker 健康检查或 Prometheus 抓取连接到它们。

完整的运维检查清单在 posthorn.dev(https://posthorn.dev)。

v1.0 包含什么

模块详情
表单入口表单编码 + multipart 请求体;蜜罐、Origin/Referer 失败关闭、速率限制、可选的 CSRF 令牌
API 模式auth = "api-key" 配合 Bearer 令牌(恒时比较);JSON 内容类型;幂等性键(24 小时,内存 LRU);每请求的 to_override
传输通道Postmark、Resend、Mailgun、AWS SES(定制 SigV4)、出站 SMTP 中继
SMTP 监听TCP 监听器,支持 AUTH PLAIN / 客户端证书、要求 STARTTLS、发件人 + 收件人白名单、大小限制、MIME → transport.Message
运维/healthz/metrics(Prometheus 格式)、干运行模式、IP 剥离、命名的 trusted_proxies 预设(Cloudflare)
错误处理对瞬态错误/5xx 重试 1 次(1 秒)、429 重试 1 次(5 秒)、10 秒硬超时
日志结构化 JSON;UUIDv4 提交 ID 和 SMTP 会话 ID;submission_sent 事件中包含 transport_message_id
部署单个 Go 二进制文件,多架构 distroless Docker 镜像,位于 ghcr.io/craigmccaskill/posthorn

整个模块中只有三个外部 Go 依赖:TOML 解析器、UUID 库、LRU 缓存。每条传输通道都是自实现的——传输代码中没有供应商 SDK。

路线图

v2——平台成熟度。 SQLite 提交日志、跨重启的重试队列、抑制列表(硬退信自动处理)、持久化幂等性、通过 HMAC 签名 Webhook 的生命周期事件回调、RFC 8058 一键退订、文件附件、HTML 正文、每个端点多输出(邮件 + Webhook + 日志扇出)、多租户 SMTP 路由。

v3——探索性。 管理界面、工作量证明反垃圾挑战、PGP 加密。取决于社区支持程度。

完整路线图:posthorn.dev/roadmap(https://posthorn.dev/roadmap/)。

从源码构建

需要 Go 1.25 及以上版本。

git clone https://github.com/craigmccaskill/posthorn
cd posthorn/core
go build -o /tmp/posthorn ./cmd/posthorn
/tmp/posthorn version

贡献

v1.0 规范位于 spec/(简要、PRD、架构)。架构文档 spec/03-architecture.md 是设计问题的权威来源。

安全问题:见 SECURITY.md——不要公开提交安全披露问题。

许可证

Apache-2.0。见 LICENSE

相似文章

Show HN: E2a – 面向 AI 智能体的开源邮件网关

Hacker News Top

E2a 是一款开源邮件网关,支持 AI 智能体通过 webhook、WebSocket 或 HTTP API 安全地收发邮件。它具备 SPF/DKIM 验证功能,提供 TypeScript 和 Python SDK,并支持可选的人工介入审批。

knadh/listmonk

GitHub Trending (daily)

listmonk 是一个独立的、自托管的新闻简报和邮件列表管理器,速度快、功能丰富,并打包成单个二进制文件,使用 PostgreSQL 数据库。

Hookdeck Outpost

Product Hunt

Hookdeck Outpost 是一款面向平台的开源出站 Webhook 解决方案。