SMTP-CONFIG

Discourse 邮件配置文档

最后更新: 2025-12-29

环境信息

项目
服务器Ubuntu Linux
DiscourseDocker 容器部署
域名www.micu.wiki
邮件服务QQ 邮箱 (smtp.qq.com)
QQ 邮箱账号byeserver@qq.com
QQ 授权码qezavpcenetvdbij

问题描述

使用 QQ 邮箱直接作为 SMTP 服务器时,存在以下问题:

  1. QQ 邮箱要求 From 地址必须与授权用户一致
  2. QQ 邮箱在关闭 SMTP 连接时有非标准行为,导致 SSL_read: unexpected eof 错误
  3. 虽然邮件实际发送成功,但 Discourse 将此错误视为发送失败
  4. Sidekiq 自动重试导致用户收到多封重复邮件

最终解决方案

架构

Discourse 容器 → 宿主机 Postfix (172.17.0.1:25) → QQ 邮箱 (smtp.qq.com:465)

Discourse 发送邮件到本地 Postfix 会立即成功(无错误),Postfix 负责转发到 QQ 邮箱。


配置详情

1. 宿主机 Postfix 配置

安装 Postfix

DEBIAN_FRONTEND=noninteractive apt-get install -y postfix libsasl2-modules

SASL 密码文件

文件: /etc/postfix/sasl_passwd

[smtp.qq.com]:465 byeserver@qq.com:qezavpcenetvdbij

设置权限并生成数据库:

chmod 600 /etc/postfix/sasl_passwd
postmap /etc/postfix/sasl_passwd

主配置文件

文件: /etc/postfix/main.cf

关键配置项:

# 中继设置
relayhost = [smtp.qq.com]:465

# TLS 设置 (SSL wrapper mode for port 465)
smtp_tls_CApath=/etc/ssl/certs
smtp_tls_security_level=encrypt
smtp_tls_wrappermode=yes
smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache

# SASL 认证
smtp_sasl_auth_enable = yes
smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd
smtp_sasl_security_options = noanonymous

# 发件人地址重写 (QQ 要求 From = 授权用户)
sender_canonical_maps = static:byeserver@qq.com

# 网络设置 (允许 Docker 容器连接)
mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128 172.17.0.0/16
inet_interfaces = 127.0.0.1, 172.17.0.1

重启 Postfix

postfix check && systemctl restart postfix

验证 Postfix 监听

ss -tlnp | grep :25
# 应显示监听 127.0.0.1:25 和 172.17.0.1:25

2. Discourse 配置

文件: /var/discourse/containers/app.yml

env:
  ## SMTP 配置 - 使用本地 Postfix 中继
  DISCOURSE_SMTP_ADDRESS: 172.17.0.1
  DISCOURSE_SMTP_PORT: 25
  DISCOURSE_SMTP_ENABLE_START_TLS: false
  DISCOURSE_SMTP_FORCE_TLS: false
  DISCOURSE_SMTP_DOMAIN: micu.wiki
  DISCOURSE_SMTP_AUTHENTICATION: none
  DISCOURSE_NOTIFICATION_EMAIL: byeserver@qq.com

重建容器

cd /var/discourse && ./launcher rebuild app

验证方法

测试 Postfix (宿主机)

# 发送测试邮件
(
echo "From: byeserver@qq.com"
echo "To: 3427788907@qq.com"
echo "Subject: Postfix Test"
echo ""
echo "Test email from Postfix"
) | /usr/sbin/sendmail -t

# 检查队列 (应为空表示发送成功)
mailq

# 查看日志
tail -20 /var/log/mail.log

测试 Discourse

docker exec -i app rake emails:test[3427788907@qq.com]

成功输出:

Testing sending to 3427788907@qq.com using 172.17.0.1:25, username:(none) with no auth.
SMTP server connection successful.
Sending to 3427788907@qq.com…
Mail accepted by SMTP server.

相关文件清单

文件说明
/var/discourse/containers/app.ymlDiscourse 主配置
/etc/postfix/main.cfPostfix 主配置
/etc/postfix/sasl_passwdSMTP 认证凭据 (敏感)
/etc/postfix/sasl_passwd.dbSMTP 认证数据库
/var/log/mail.logPostfix 邮件日志

常用命令

Postfix 管理

# 检查配置
postfix check

# 重启服务
systemctl restart postfix

# 查看状态
systemctl status postfix

# 查看队列
mailq

# 清空队列
postsuper -d ALL

# 查看日志
tail -f /var/log/mail.log

Discourse 管理

# 重建容器
cd /var/discourse && ./launcher rebuild app

# 进入容器
docker exec -it app bash

# 测试邮件
docker exec -i app rake emails:test[email@example.com]

# 查看 Sidekiq 队列
docker exec -i app rails runner "puts Sidekiq::Queue.new.size"

故障排查

1. Postfix 无法启动

# 检查配置语法
postfix check

# 查看详细错误
journalctl -u postfix -n 50

2. 邮件发送失败

# 查看邮件日志
tail -50 /var/log/mail.log

# 检查队列中的邮件
mailq

# 查看特定邮件详情
postcat -q <queue_id>

3. Discourse 无法连接 Postfix

# 检查 Postfix 是否监听 Docker 网络
ss -tlnp | grep :25

# 从容器内测试连接
docker exec -i app bash -c "echo 'QUIT' | nc 172.17.0.1 25"

4. QQ 邮箱拒绝

常见原因:

  • 授权码过期或错误
  • 发件人地址与授权用户不一致
  • IP 被 QQ 邮箱封禁

解决方法:

  • 登录 QQ 邮箱重新生成授权码
  • 更新 /etc/postfix/sasl_passwd 并运行 postmap
  • 检查 sender_canonical_maps 配置

注意事项

  1. QQ 邮箱授权码: 使用授权码而非 QQ 密码,授权码在 QQ 邮箱设置中生成
  2. 发件人限制: QQ 邮箱要求发件人地址必须与授权用户一致
  3. 防火墙: 确保 Docker 容器可以访问宿主机的 25 端口
  4. Postfix 服务: 系统重启后 Postfix 会自动启动 (已启用 systemd 服务)
  5. 敏感信息: /etc/postfix/sasl_passwd 包含授权码,确保权限为 600

更换授权码步骤

如果需要更换 QQ 邮箱授权码:

# 1. 编辑密码文件
vi /etc/postfix/sasl_passwd
# 修改为: [smtp.qq.com]:465 byeserver@qq.com:新授权码

# 2. 重新生成数据库
postmap /etc/postfix/sasl_passwd

# 3. 重启 Postfix
systemctl restart postfix

# 4. 测试
echo "Test" | /usr/sbin/sendmail -t test@example.com