5 min read

搞定Ghost的邮箱问题

背景

最近刚开始经营ghost博客, 感觉ghost可真好用呀, ui又漂亮, 功能又实用, 性能也很不赖的样子. 而且还是基于我最熟悉的node构建的, 比wordpress啥的平添了几份亲切感. 强烈安利 https://ghost.org/

问题

但, 有一个邮箱的问题, 部署上之后就一直没搞定, 很头痛.

ysslang.com 用的是腾讯的邮箱服务. 毕竟是国内的大厂, 企业邮箱还是蛮香的. 可以设置几个服务账号用来统一提供服务, 我设置了一个 no-reply@ysslang.com 用来给服务器发发邮件, 挺不错的.

但按照 https://ghost.org/docs/config/#mail 文档配置了几个环境变量后, 能连通, 但一尝试发送邮件就会报错

[2022-09-26 16:34:00] ERROR Failed to send email. Reason: Mail command failed: 501 mail from address must be same as authorization user.
Failed to send email. Reason: Mail command failed: 501 mail from address must be same as authorization user.
"Please see https://ghost.org/docs/config/#mail for instructions on configuring email."
Error ID:
    068188f0-3db9-11ed-bcca-dbe6a2908088
Error Code: 
    EENVELOPE
----------------------------------------
Error: Mail command failed: 501 mail from address must be same as authorization user
    at createMailError (/var/lib/ghost/versions/5.14.2/core/server/services/mail/GhostMailer.js:71:12)
    at SMTPConnection._formatError (/var/lib/ghost/versions/5.14.2/node_modules/nodemailer/lib/smtp-connection/index.js:787:19)
    at SMTPConnection._actionMAIL (/var/lib/ghost/versions/5.14.2/node_modules/nodemailer/lib/smtp-connection/index.js:1569:34)
    at SMTPConnection.<anonymous> (/var/lib/ghost/versions/5.14.2/node_modules/nodemailer/lib/smtp-connection/index.js:1044:18)
    at SMTPConnection._processResponse (/var/lib/ghost/versions/5.14.2/node_modules/nodemailer/lib/smtp-connection/index.js:950:20)
    at SMTPConnection._onData (/var/lib/ghost/versions/5.14.2/node_modules/nodemailer/lib/smtp-connection/index.js:752:14)
    at TLSSocket.SMTPConnection._onSocketData (/var/lib/ghost/versions/5.14.2/node_modules/nodemailer/lib/smtp-connection/index.js:191:44)
    at TLSSocket.emit (node:events:513:28)
    at addChunk (node:internal/streams/readable:315:12)
    at readableAddChunk (node:internal/streams/readable:289:9)
    at TLSSocket.Readable.push (node:internal/streams/readable:228:10)
    at TLSWrap.onStreamRead (node:internal/stream_base_commons:190:23)
[2022-09-26 16:34:00] INFO "POST /members/api/send-magic-link/" 400 728ms

很奇怪

排查

NodeMailer

后来研究了一番文档和源码, 发现Ghost底层用的是NodeMailer的邮件发送服务, 于是又去翻了翻NodeMailer的配置文档和源码, 发现可以给options里传递debug和logger参数来启动debug日志输出, 于是又给ghost容器多加了两条参数 mail__options__debug: 'true'mail__options__logger: 'true' , 重新尝试发送邮件后看到了更详细的报错.

[2022-09-26 16:33:59] DEBUG Sending mail using SMTP/6.7.8[client:6.7.8]
[2022-09-26 16:33:59] DEBUG [HVxeyzRDfjw] Resolved smtp.exmail.qq.com as 183.2.143.59 [cache miss]
[2022-09-26 16:33:59] INFO  [HVxeyzRDfjw] Secure connection established to 183.2.143.59:465
[2022-09-26 16:34:00] DEBUG [HVxeyzRDfjw] S: 220 smtp.qq.com Esmtp QQ QMail Server
[2022-09-26 16:34:00] DEBUG [HVxeyzRDfjw] C: EHLO [127.0.0.1]
[2022-09-26 16:34:00] DEBUG [HVxeyzRDfjw] S: 250-smtp.qq.com
[2022-09-26 16:34:00] DEBUG [HVxeyzRDfjw] S: 250-PIPELINING
[2022-09-26 16:34:00] DEBUG [HVxeyzRDfjw] S: 250-SIZE 73400320
[2022-09-26 16:34:00] DEBUG [HVxeyzRDfjw] S: 250-AUTH LOGIN PLAIN
[2022-09-26 16:34:00] DEBUG [HVxeyzRDfjw] S: 250-AUTH=LOGIN
[2022-09-26 16:34:00] DEBUG [HVxeyzRDfjw] S: 250-MAILCOMPRESS
[2022-09-26 16:34:00] DEBUG [HVxeyzRDfjw] S: 250 8BITMIME
[2022-09-26 16:34:00] DEBUG [HVxeyzRDfjw] SMTP handshake finished
[2022-09-26 16:34:00] DEBUG [HVxeyzRDfjw] C: AUTH PLAIN ===================================
[2022-09-26 16:34:00] DEBUG [HVxeyzRDfjw] S: 235 Authentication successful
[2022-09-26 16:34:00] INFO  [HVxeyzRDfjw] User "no-reply@ysslang.com" authenticated
[2022-09-26 16:34:00] INFO  Sending message <ae522939-40de-48dd-e21d-4da0e813b14a@ysslang.com> to <yuansanshilang@gmail.com>
[2022-09-26 16:34:00] DEBUG [HVxeyzRDfjw] C: MAIL FROM:<noreply@ysslang.com>
[2022-09-26 16:34:00] DEBUG [HVxeyzRDfjw] S: 501 mail from address must be same as authorization user

原来是mail__from的地址变成了 noreply@ysslang.com , 跟我的 no-reply@ysslang.com 不一致.

然而, 根据NodeMailer的文档, mail.from的信息, 是作为邮件的内容 envelope 来提供的, 并不是配置项, 于是只能继续研究 Ghost.

Mail__from

后来研究了一番源码和配置, 始终没能搞定. 谷歌了一下报错, 没想到竟然找到了一样的问题issue . 根据提示, 我在Ghost的设置里尝试配置服务邮箱为 no-reply@ysslang.com, 没想到还是失败了, 看日志, 它好像还是会尝试发送邮件后才会配置成功, 而这个邮件发送, 还是会用原来的错误邮箱.

源码调试

没办法, 只能再重新调试源码尝试理解逻辑.

根据 源码安装的文档 准备了一个vscode下的开发环境, yarn setup 后到package.json下找到debug的命令, 运行debug, 然后抓包到发邮箱的请求

curl 'http://localhost:2368/members/api/send-magic-link/' \
  -H 'content-type: application/json' \
  --data-raw '{"name":"","email":"yuansanshilang@gmail.com","requestSrc":"portal"}' 

跟踪了两下后, 找到了原来地址是从这里生成的

    getMembersSupportAddress() {
        const supportAddress = this.settingsCache.get('members_support_address') || 'noreply';

        // Any fromAddress without domain uses site domain, like default setting `noreply`
        if (supportAddress.indexOf('@') < 0) {
            return `${supportAddress}@${this.getDefaultEmailDomain()}`;
        }
        return supportAddress;
    }

读取了一个 members_support_address 的配置项作为地址来发送邮件, 如果没读到, 就会生成 noreply@域名 来发送.

这才想起来, 我前段页面上改不成功, 直接改后台配置库不就好了嘛, 反正我是外迁的.

解决方案

于是连到外接的mysql里, 找到setting表, 根据key找到了 members_support_address 记录.

原值是 noreply, 修改为 no-reply@ysslang.com 后保存.

重启ghost, 访问页面, 可以发邮件了!