Show HN: Posthorn,无需邮件服务器的自托管邮件系统
摘要
Posthorn 是一个自托管的出站邮件网关,通过统一接口整合多个应用的邮件发送,支持 HTTP 和 SMTP 接收,并支持多种提供商传输。
查看缓存全文
缓存时间: 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)
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-Token | JSON |
| Resend | 现代 HTTP API,开发者友好的仪表盘 | Authorization: Bearer | JSON |
| Mailgun | 较高量级的事务性邮件,美国 + 欧洲区域 | HTTP Basic | multipart/form-data |
| AWS SES | AWS 原生部署,大规模下最便宜 | AWS SigV4(定制) | JSON |
| 出站 SMTP | 任何支持 STARTTLS 的中继(Mailtrap、你的 Postfix smart host 等) | AUTH PLAIN | SMTP DATA |
切换提供商只需编辑 TOML——每种传输通道都实现相同的 Transport 接口。各提供商的配置见 posthorn.dev/configuration/transports(https://posthorn.dev/configuration/transports/)。
生产环境检查清单
在让真实流量进入 Posthorn 之前:
- DNS ——在发送域上设置 SPF、DKIM 和 DMARC 记录。没有这些,你的邮件会进垃圾箱。见 posthorn.dev/security/dns(https://posthorn.dev/security/dns/)。
- 反向代理 ——Posthorn 不终止 TLS。将其运行在 Caddy、nginx 或 Traefik 后面。见 posthorn.dev/deployment/reverse-proxy(https://posthorn.dev/deployment/reverse-proxy/)。
allowed_origins(表单模式端点)——设置此项以将提交限制为你的域名。没有它,任何人都可以向你的端点发送 POST 请求。rate_limit——为每个端点设置一个精简的速率限制(公共联系表单建议每秒 5 次;API 模式按匹配的密钥限制)。trusted_proxies——如果在反向代理后面,列出其 CIDR(或使用cloudflare命名预设),这样速率限制器才能看到真实客户端 IP。/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 智能体的开源邮件网关
E2a 是一款开源邮件网关,支持 AI 智能体通过 webhook、WebSocket 或 HTTP API 安全地收发邮件。它具备 SPF/DKIM 验证功能,提供 TypeScript 和 Python SDK,并支持可选的人工介入审批。
knadh/listmonk
listmonk 是一个独立的、自托管的新闻简报和邮件列表管理器,速度快、功能丰富,并打包成单个二进制文件,使用 PostgreSQL 数据库。
Show HN: Honker – 为 SQLite 带来 Postgres NOTIFY/LISTEN 语义
Honker 是一个 SQLite 扩展,为 SQLite 添加 PostgreSQL 风格的 NOTIFY/LISTEN 语义,无需外部代理或轮询即可实现持久化的发布/订阅与任务队列。
Hookdeck Outpost
Hookdeck Outpost 是一款面向平台的开源出站 Webhook 解决方案。
Show HN: Antenna – 内置 MCP 服务器的 RSS 阅读器
Antenna 是一款本地优先的 RSS 阅读器,使用 SQLite 存储订阅源,并通过邮件摘要和面向 AI 代理的 MCP 服务器提供内容,完全基于 MIT 许可证开源。