自部署 Multica 配置邮件验证码:从“打印到日志”到 SMTP 真发邮件(以网易企业邮箱为例)

前言

Multica 是一个开源的 Managed Agents 平台(Go 后端 + Next.js 前端),支持 Docker 自部署。它用「邮箱 + 验证码」作为默认登录/注册方式。

但很多人 Docker 跑起来后会发现一个问题:输入邮箱点发送验证码,邮箱里根本收不到,验证码是被打印到后端日志里的,只能去 docker logs 里翻出来手动复制登录。

原因很简单:Multica 没有配置任何邮件后端时,会走兜底逻辑,把本该发出去的邮件直接写到 stdout。本文记录把它改成通过 SMTP 协议真正发邮件的完整过程,用网易企业邮箱作为示例,其它企业邮(阿里云、腾讯)思路完全一样,文末附了对照表。

环境:Multica 自部署版(docker-compose.selfhost.yml),Linux 服务器,Docker Compose 部署。


一、原理:Multica 的两种邮件后端

Multica 支持两种发信后端,按优先级决定走哪个:

后端 适用场景 触发条件
SMTP relay 自部署 / 内网 / 企业邮箱 设置了 SMTP_HOST
Resend API 公网云部署 设置了 RESEND_API_KEY 且未设 SMTP
DEV 兜底 本地测试 两者都没配,验证码打印到日志

关键规则:只要 SMTP_HOST 非空,就一定走 SMTP,优先级高于 Resend。这正是我们要的——验证码邮件全程不出内网/不依赖第三方 API。

启动时后端会打印一行它选用了哪个后端,例如:

EmailService: SMTP relay smtp.qiye.163.com:465 (implicit-tls) from=you@yourcompany.com

看到这行是 SMTP relay 而不是 DEV mode,就说明配置生效了。


二、前置条件:在网易企业邮箱拿到“客户端授权码”

这一步必须先做,否则 SMTP 认证会一直失败。SMTP_PASSWORD 填的不是你的邮箱登录密码,而是客户端授权码


  1. 用浏览器登录网易企业邮箱(灵犀办公)网页版;
  2. 进入 设置 → 客户端设置(或「客户端授权码」);
  3. 勾选 启用 SMTP 服务
  4. 生成 客户端授权码,复制保存好(后面填进 .env)。

网易企业邮的 SMTP 服务器信息:

  • 服务器:smtp.qiye.163.com
  • 加密端口:465(SSL/隐式 TLS,推荐),也支持 994587
  • 非加密端口:25

注意:部分账号分配的是带地区的专属地址(如 smtphz.qiye.163.com)。如果 smtp.qiye.163.com 连接超时,去网页版「客户端设置」页面看它显示的实际 SMTP 地址,以那个为准。


三、修改 .env

进入你的 Multica 部署目录(仓库根目录,和 docker-compose.selfhost.yml 同级),.env 里其实已经预留好了所有 SMTP 相关的键,只要改值,不要新增重复行

先看一下现有的邮件相关行:

grep -nE 'SMTP|RESEND|EMAIL' .env

你会看到类似这样(行号可能略有差异):

RESEND_FROM_EMAIL=noreply@multica.ai
SMTP_HOST=
SMTP_PORT=25
SMTP_USERNAME=
SMTP_PASSWORD=
SMTP_TLS_INSECURE=false
SMTP_TLS=
SMTP_EHLO_NAME=

把它们改成(用 nano .env 手动改,或用下面的 sed 一次性改好):

RESEND_FROM_EMAIL=you@yourcompany.com
SMTP_HOST=smtp.qiye.163.com
SMTP_PORT=465
SMTP_USERNAME=you@yourcompany.com
SMTP_PASSWORD=你的客户端授权码
SMTP_TLS=implicit

SMTP_TLS_INSECURE=falseSMTP_EHLO_NAME= 保持默认不动。

sed 批量改(先把授权码准备好,替换命令里的占位符):

sed -i \
  -e 's#^RESEND_FROM_EMAIL=.*#RESEND_FROM_EMAIL=you@yourcompany.com#' \
  -e 's#^SMTP_HOST=.*#SMTP_HOST=smtp.qiye.163.com#' \
  -e 's#^SMTP_PORT=.*#SMTP_PORT=465#' \
  -e 's#^SMTP_USERNAME=.*#SMTP_USERNAME=you@yourcompany.com#' \
  -e 's#^SMTP_PASSWORD=.*#SMTP_PASSWORD=你的授权码#' \
  -e 's#^SMTP_TLS=.*#SMTP_TLS=implicit#' \
  .env

改完核对一下:

grep -nE '^(SMTP_|RESEND_FROM)' .env

⚠️ 关键点:发件人必须和认证账号一致

RESEND_FROM_EMAIL 在 SMTP 模式下会被当作发件人 From: 地址。网易企业邮(以及几乎所有企业邮)要求发件人地址必须和 SMTP 认证账号一致。如果你用 you@yourcompany.com 的授权码登录,却以默认的 noreply@multica.ai 名义发信,服务器会直接拒收(常见 553 / 发件人与授权账户不匹配)。

所以 RESEND_FROM_EMAIL 一定要改成和 SMTP_USERNAME 相同的邮箱——这是最容易漏掉的一步。


四、重建容器(重点:用 up -d,不要用 restart

这是整个过程最大的坑

docker compose restart 只是把同一个容器停了再起,不会重新读取 .env.env 是在容器创建时注入的。改完 .env 必须用 up -d 让 Compose 重建容器,新变量才会生效。

docker compose -f docker-compose.selfhost.yml up -d backend

如果你用了 restart,日志会一直显示 DEV mode,让你以为没配对——其实只是变量没进容器。


五、验证配置生效

docker compose -f docker-compose.selfhost.yml logs --tail=50 backend | grep -i emailservice

期望输出:

EmailService: SMTP relay smtp.qiye.163.com:465 (implicit-tls) from=you@yourcompany.com

确认两点:

  • SMTP relay 而不是 DEV mode
  • from= 是你自己的邮箱,不是 noreply@multica.ai

如果想确认变量是否真的进了容器:

docker compose -f docker-compose.selfhost.yml exec backend env | grep SMTP

六、实测发信

打开 Multica 登录页 → 输入一个你能收信的邮箱 → 点发送验证码,然后立刻看日志:

docker compose -f docker-compose.selfhost.yml logs --tail=20 backend | grep -iE 'smtp|send|verif|email|55[0-9]'

没有报错 + 邮箱(含垃圾箱)收到验证码 = 全部完成。从此验证码走邮件,不再打日志。


七、常见问题排查

1. 日志一直是 DEV mode
变量没进容器。99% 是用了 restart 而不是 up -d;或者 .envSMTP_HOST 还是空的。

2. 报 535 认证失败
SMTP_PASSWORD 填成了登录密码而不是客户端授权码;或者网页版没勾「启用 SMTP 服务」。

3. 报 553 / 发件人不匹配
RESEND_FROM_EMAILSMTP_USERNAME 不是同一个邮箱。改成一致后 up -d 重建。

4. 连接 smtp.qiye.163.com 超时
你的账号用的是地区专属地址(如 smtphz.qiye.163.com),去网页版「客户端设置」查实际地址;或服务器出网 465 端口被防火墙挡了。

5. 日志里有 websocket: request origin not allowed
这和邮件无关,是前端访问地址不在后端 WebSocket origin 白名单里,只影响页面实时刷新,不影响登录和验证码。把你的访问地址(站点 URL)配进对应环境变量即可,可单独处理。


八、其它企业邮箱对照表

把第三步的 SMTP_HOST / SMTP_PORT 换成下面对应值即可,其余步骤一致(都要用授权码、发件人都要和账号一致):

服务商 SMTP 服务器 端口 TLS
网易企业邮 smtp.qiye.163.com 465 implicit
腾讯企业邮 smtp.exmail.qq.com 465 implicit
阿里云企业邮 smtp.qiye.aliyun.com 465 implicit
内网自建中继(Exchange/Postfix) 内网地址 25 留空,用户名密码也留空
需 STARTTLS 的常规 SMTP 对应地址 587 留空(自动 STARTTLS)

小结

整件事其实不用改一行代码,核心就三点:

  1. 在企业邮网页版开 SMTP 服务、拿客户端授权码
  2. .envSMTP_*,并让 RESEND_FROM_EMAIL 等于认证邮箱
  3. up -d 重建容器(不是 restart),看日志确认 SMTP relay 生效。

踩过 restart 不读 .env发件人不匹配 这两个坑之后,剩下就一路顺了。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐