目标

new-api 的「邮箱验证」(Email Verification) 开启后,用户注册必须先收到一封验证码邮件。本篇记录它如何工作、如何配置发信、以及几个会让人误判的坑。

机制:new-api 自带发信,但邮箱要你自己给

new-api 自带完整的发信实现,不需要装插件、不需要额外脚本:

  • 发信代码:common/email.go 的 SendEmail(subject, receiver, content),用 Go 标准库 net/smtp 自己实现 SMTP 客户端(支持 SSL 直连 465 与 STARTTLS)。
  • 触发接口:GET /api/verification?email=... → controller/misc.go 的 SendEmailVerification。
  • 前端:注册页的「获取验证码」按钮(web/src/features/auth/hooks/use-email-verification.ts)。

但它不会替你提供邮箱。必须给它一个发信邮箱 + 授权码(authorization code)——授权码由邮箱服务商签发,与账号密码不同。所以「要不要自己授权」的答案是:要。

配置项(options 表)

Key说明QQ 邮箱取值
SMTPServerSMTP 服务器smtp.qq.com
SMTPPort端口465
SMTPSSLEnabled直连 SSLtrue(465 用)
SMTPStartTLSEnabledSTARTTLSfalse(587 才用)
SMTPAccount发信账号xxx@qq.com
SMTPFrom发件人地址(也用于生成 Message-ID 的域名)同 SMTPAccount
SMTPToken授权码(不是登录密码)16 位字母
EmailVerificationEnabled是否要求邮箱验证true
EmailDomainRestrictionEnabled是否限定邮箱域名true
EmailDomainWhitelist允许的域名列表,逗号分隔见下方坑
EmailAliasRestrictionEnabled是否禁止 + / . 别名默认关

端口分支逻辑(common/email.go 的 newSMTPClient):SMTPSSLEnabled 为真,或 SMTPPort == 465 且未开 STARTTLS 时,走 tls.Dial;否则走明文 smtp.Dial 再按需 StartTLS。

写入方式:直接改库最省事,改完不用重启——

INSERT INTO options(key,value) VALUES('SMTPServer','smtp.qq.com')
  ON CONFLICT(key) DO UPDATE SET value=excluded.value;

坑

1. ApiError 返回的是 HTTP 200,日志里的 200 不等于成功

common/gin.go:

func ApiError(c *gin.Context, err error) {
    c.JSON(http.StatusOK, gin.H{"success": false, "message": err.Error()})
}

所以 GIN 访问日志里 GET /api/verification ... 200 完全不能证明邮件发出去了。必须看响应体里的 success 字段,或者用 curl 实测。

判断依据:{"message":"invalid SMTP account","success":false} = SMTPFrom/SMTPAccount 是空的;{"message":"SMTP 服务器未配置","success":false} = 服务器和账号都空。

2. 邮箱域名白名单是精确匹配整个域名,不是后缀匹配

service/email_binding.go:

allowed := slices.Contains(common.EmailDomainWhitelist, parts[1])

parts[1] 是 @ 后面的完整域名。所以白名单里写 outlook 不会放行 outlook.com——只会放行一个字面量域名叫 outlook 的地址,等于永远不匹配。曾因此在线上实际挡掉真实用户。抄白名单时每个域名都要写全。

Go 默认白名单(common/constants.go)里有 gmail.com / qq.com / 163.com / 126.com / hotmail.com / foxmail.com 等;一旦 options 表写了 EmailDomainWhitelist,就以表里的为准,默认值被整体覆盖。

3. 改 options 表后要等同步周期(默认 60 秒)

common/init.go 的 SyncFrequency = GetEnvOrDefault("SYNC_FREQUENCY", 60),loadOptionsFromDatabase() 按这个周期把表里的值刷进内存变量。日志里能看到 [SYS] ... | syncing options from database。改完立刻测会失败,等一个周期再测。

4. EmailVerificationEnabled=true + SMTP 未配置 = 所有人注册不了

这是最容易踩的组合:验证开着、发信没配,前端「获取验证码」一直失败,但接口返回 HTTP 200,看日志看不出问题。排查时第一件事就是实测一次 /api/verification 看响应体。

5. QQ 个人邮箱有每日发信上限

约 100 封/天(QQ 会员约 500 封/天),且采用动态风控;短时间高频发信(每分钟上限约 20–40 封)会被临时阻断。用授权码不会提升额度。注册量一大就会顶到天花板,届时需换 Resend / Brevo 这类专业发信服务。

验证方法

# 1) 服务端直接发一封(不用浏览器)
curl -s "http://127.0.0.1:3000/api/verification?email=你的邮箱@qq.com"
# 期望:{"message":"","success":true}
# 响应耗时有几秒 = 真的连上了 SMTP 服务器并投递

# 2) 确认同步周期已加载配置
journalctl -u new-api --since "-3 min" | grep "syncing options"

# 3) 看服务端有无报错
journalctl -u new-api --since "-90 sec" | grep -iE "smtp|email"

适用边界

  • 本篇针对 new-api v1.0.0-rc.40(QuantumNous/new-api);字段名与端口分支逻辑可能随版本变化。
  • 邮箱验证开启时,controller/user.go 的 Register 要求 user.Email 和 user.VerificationCode 都非空;关闭后注册只校验用户名密码。
  • QQ 邮箱 SMTP 从境外服务器(如 LA 小鸡)连接正常,465 端口 TLS 握手可通,未见 IP 风控拦截。

本次实例:6767 API 配 QQ 邮箱(2026-09-24)

  • 站点 ai.6767.chat(LA 小鸡,/opt/relay/na/one-api.db)。
  • 原状态:EmailVerificationEnabled=true,但 options 表没有任何 SMTP 行 → 所有用户注册卡在拿不到验证码;当天已有真实用户在试(qq.com / gmail.com / outlook.com)全部失败。
  • 另发现 EmailDomainWhitelist 写成了 qq.com,163.com,126.com,gmail.com,outlook(漏 .com),outlook 用户被白名单挡掉。
  • 处理:备份库到 /root/one-api.db.bak-20260924_144336 → 写入 SMTPServer=smtp.qq.com / SMTPPort=465 / SMTPSSLEnabled=true / SMTPAccount/SMTPFrom / SMTPToken(授权码)→ 修白名单 → 等服务一个同步周期 → 实测 {"success":true},耗时 3.3s。

关联


本文归档于个人知识库 · 领域:CS & AI · 模块:04 Project_CS_AI/AI apps