feat: 实现对外邮件投递(外发队列 + MX 直投 + DKIM 签名 + 退信 + 管理后台) #1

Merged
kevin merged 2 commits from dsh/mailgo:outbound-delivery into main 2026-08-16 11:38:47 +08:00
Contributor

概述

实现 todo.md 中「外部邮件投递功能」的阶段 1 + 阶段 2:认证用户可通过 Web 邮箱 / Outlook(SMTP 提交)向外部邮箱(QQ / Gmail / Outlook 等)发送邮件。

主要改动

  • internal/outbound 新模块
    • mailer.go:MX 查询(无 MX 时按 RFC 5321 回退到 A 记录)、SMTP 出站客户端(EHLO / 机会性 STARTTLS / MAIL FROM / RCPT TO / DATA / QUIT)、4xx 临时失败与 5xx 永久失败分类、8BITMIME 协商
    • manager.go:外发队列 worker(15s 轮询 + 立即触发)、指数退避重试(5/10/20/40… 分钟,上限 24h)、永久失败/超限退信到发件人收件箱、每用户每分钟/每日限速(max_per_day=0 可禁用外部投递)
    • sign.go:外发邮件 DKIM 签名(go-msgauth,使用域名管理中生成的 RSA 私钥与 selector)
  • outbound_messages 队列表 + OutboundStore:保存发件人/收件人/RawData/状态/重试次数/下次重试时间/最后 SMTP 响应,AutoMigrate 自动建表
  • SMTP 提交集成internal/smtp_server/server.go
    • 认证用户可向外部收件人投递;本地收件人仍走 INBOX
    • MAIL FROM 必须与登录用户邮箱一致(防伪造)
    • 未认证外部投递明确拒绝 relay access denied(防开放中继)
    • 单封最大外部收件人数限制
  • Web 发信集成internal/web/handlers/mail.go
    • 外部收件人自动入队,入队失败(限速/无效地址/未启用)在页面显示错误
    • 附件重写为 multipart/mixed + base64(76 列折行),进入外发邮件正文
  • 管理后台:新增「外发队列」页面(/admin/outbound)——状态统计、投递状态、失败原因、手动重试、取消任务
  • 配置:新增 [outbound] 段(hostname / poll_interval / max_attempts / retry_base_min / max_recipients / max_per_min / max_per_day / connect_timeout),README 补充配置说明与 DNS 条件

测试

  • 单元测试:internal/outbound —— SMTP 线格式(点填充)、8BITMIME、永久失败分类、DKIM 签名与验证(go test ./...
  • 已在 lmve.net 生产服务器实测:
    • SMTP 587(STARTTLS+AUTH)→ 外部地址(mail.tm):队列状态 sent,收件方收到,Return-Path / DKIM-Signature 正确
    • Web 发信(含附件)→ 外部地址:正常收到,MIME multipart/mixed + base64 附件解析正确
    • DKIM 签名通过 DNS 公钥(default._domainkey.lmve.net)验证
    • 未认证中继被拒(451 relay access denied)、伪造 MAIL FROM 被拒
    • NXDOMAIN → 永久失败 → 自动退信到发件人收件箱
    • 本地投递不受影响

说明

  • 服务器 PTR 记录目前为运营商默认值(nz181l177.bb60246.ctm.net),非 mail.lmve.net,需向机房/ISP 申请修改以提升信誉(Cloudflare 无法设置 PTR)。
  • todo.md 已勾选已实现项;「管理员禁用单个用户外发」暂未实现,可后续补充。

Closes: 外部邮件投递功能 TODO

## 概述 实现 todo.md 中「外部邮件投递功能」的阶段 1 + 阶段 2:认证用户可通过 Web 邮箱 / Outlook(SMTP 提交)向外部邮箱(QQ / Gmail / Outlook 等)发送邮件。 ## 主要改动 - **`internal/outbound` 新模块** - `mailer.go`:MX 查询(无 MX 时按 RFC 5321 回退到 A 记录)、SMTP 出站客户端(EHLO / 机会性 STARTTLS / MAIL FROM / RCPT TO / DATA / QUIT)、4xx 临时失败与 5xx 永久失败分类、8BITMIME 协商 - `manager.go`:外发队列 worker(15s 轮询 + 立即触发)、指数退避重试(5/10/20/40… 分钟,上限 24h)、永久失败/超限退信到发件人收件箱、每用户每分钟/每日限速(`max_per_day=0` 可禁用外部投递) - `sign.go`:外发邮件 DKIM 签名(go-msgauth,使用域名管理中生成的 RSA 私钥与 selector) - **`outbound_messages` 队列表** + `OutboundStore`:保存发件人/收件人/RawData/状态/重试次数/下次重试时间/最后 SMTP 响应,AutoMigrate 自动建表 - **SMTP 提交集成**(`internal/smtp_server/server.go`) - 认证用户可向外部收件人投递;本地收件人仍走 INBOX - `MAIL FROM` 必须与登录用户邮箱一致(防伪造) - 未认证外部投递明确拒绝 `relay access denied`(防开放中继) - 单封最大外部收件人数限制 - **Web 发信集成**(`internal/web/handlers/mail.go`) - 外部收件人自动入队,入队失败(限速/无效地址/未启用)在页面显示错误 - 附件重写为 multipart/mixed + base64(76 列折行),进入外发邮件正文 - **管理后台**:新增「外发队列」页面(`/admin/outbound`)——状态统计、投递状态、失败原因、手动重试、取消任务 - **配置**:新增 `[outbound]` 段(hostname / poll_interval / max_attempts / retry_base_min / max_recipients / max_per_min / max_per_day / connect_timeout),README 补充配置说明与 DNS 条件 ## 测试 - 单元测试:`internal/outbound` —— SMTP 线格式(点填充)、8BITMIME、永久失败分类、DKIM 签名与验证(`go test ./...`) - 已在 lmve.net 生产服务器实测: - SMTP 587(STARTTLS+AUTH)→ 外部地址(mail.tm):队列状态 `sent`,收件方收到,`Return-Path` / `DKIM-Signature` 正确 - Web 发信(含附件)→ 外部地址:正常收到,MIME multipart/mixed + base64 附件解析正确 - DKIM 签名通过 DNS 公钥(`default._domainkey.lmve.net`)验证 - 未认证中继被拒(`451 relay access denied`)、伪造 MAIL FROM 被拒 - NXDOMAIN → 永久失败 → 自动退信到发件人收件箱 - 本地投递不受影响 ## 说明 - 服务器 PTR 记录目前为运营商默认值(`nz181l177.bb60246.ctm.net`),非 `mail.lmve.net`,需向机房/ISP 申请修改以提升信誉(Cloudflare 无法设置 PTR)。 - todo.md 已勾选已实现项;「管理员禁用单个用户外发」暂未实现,可后续补充。 Closes: 外部邮件投递功能 TODO
dsh added 2 commits 2026-08-16 05:31:20 +08:00
- 新增 internal/outbound 模块:MX 查询、SMTP 出站客户端(EHLO/STARTTLS/
  MAIL/RCPT/DATA/QUIT)、4xx 临时失败与 5xx 永久失败分类、8BITMIME 支持
- 新增 outbound_messages 队列表与 OutboundStore,后台 worker 指数退避重试
- 永久失败/超限退信到发件人收件箱,包含原因与目标收件人
- 外发邮件使用域名 DKIM 私钥签名(go-msgauth)
- SMTP 提交集成:认证用户可发外部收件人,MAIL FROM 必须等于登录用户邮箱,
  未认证外部投递明确拒绝(防开放中继)
- Web 发信集成:外部收件人自动入队,附件以 multipart/mixed + base64 编码
  加入邮件正文
- 每用户每分钟/每日发送限速(max_per_day=0 可禁用外部投递)
- 管理后台新增外发队列页面:状态统计、失败原因、手动重试/取消
- 新增 [outbound] 配置段并更新 README / todo.md
kevin merged commit 79220b816d into main 2026-08-16 11:38:47 +08:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: kevin/mailgo#1