重构邮件:测试 rake 任务输出

我最近检查了 emails:task 和相关代码,目的是测试所有失败的代码路径并润色错误文本。

我还发现,设置 DISCOURSE_SMTP_ENABLE_STARTTLS=false 的效果(大部分)是无效的。设置此项实际上并未禁用 STARTTLS,事实上,它可以在连接时与 TLS 共存(DISCOURSE_SMTP_FORCE_TLS=true)。

因此,我:

但在合并之前,我认为在仪表板中为设置 DISCOURSE_SMTP_ENABLE_STARTTLS=false 的情况添加管理员警告是合适的。我想象至少有一个自托管用户设置了此项,但他们并不需要它,并且实际上依赖于 STARTTLS。

4 个赞

这听起来是项不错的工作!我(认为我)注意到的一点是,rake 任务实际上并没有使用与实际发送(例如从 /admin/email 测试页面)相同的代码。我非常确定我遇到过一种情况,即它在 UX 中有效,但在 rake 任务中无效(或者也许是相反的?)

趁你还记得,如果你能确保它在实际发送时使用的是 Discourse 所使用的相同代码,那就太好了。

2 个赞

我们也在处理这个问题,以及改进队列邮件作业失败时的日志记录:+1:

4 个赞

您在我托管的论坛上需要做什么吗?

4 个赞

不,此警报不应显示在我们的托管环境中。我们会修复它,感谢您一如既往的报告。

5 个赞

@supermathie 这样做是否值得向拥有此变量的每个站点的每位管理员发送私人消息?我们当前的“问题检查”系统会执行此操作,而且我不确定在此处是否需要,因为在大多数情况下,这只会产生“nil”效果。理想情况下,我只想在仪表板中显示此信息,而不通知管理员用户,不确定我们当前的“问题检查”结构是否支持这种情况。

我认为是的——我发现,鉴于许多管理员对电子邮件设置感到困惑,有人设置了这个变量,而他们实际上依赖于 starttls。

可能没有人应该设置它。

我宁愿收到一个错误的警告,也不愿默默地破坏某人的电子邮件设置。

另一种选择是移除检查并禁用该变量,使其完全不起作用。

1 个赞

当外出 SMTP 服务器是 localhost(即与 Discourse 域名匹配)时,不显示警告会更好,因为 Docker 容器和主机之间不需要 TLS,它们是同一台机器。

1 个赞

在这种情况下,您可以从环境中删除该变量。

它仅在提供 STARTTLS 时使用。

1 个赞

在当前版本中,我遇到了一个情况:被注释掉的行(默认值为 true)与 force_tls 一起阻止了电子邮件发送。因此,我取消了它的注释并将其设置为 false。电子邮件发送现在可以工作了——但我在后端看到了这条消息……

这在一定程度上取决于您的邮件服务提供商运行在哪个端口……两者都行不通,并且肯定会产生影响。

我也注意到了这一点。在 11 月 28 日更新 Discourse 后,我开始看到邮件投递失败,错误信息为 Job exception: :enable_starttls and :tls are mutually exclusive. Set :tls if you're on an SMTPS connection. Set :enable_starttls if you're on an SMTP connection and using STARTTLS for secure TLS upgrade.(作业异常::enable_starttls 和 :tls 是互斥的。如果您使用的是 SMTPS 连接,请设置 :tls。如果您使用的是 SMTP 连接并使用 STARTTLS 进行安全的 TLS 升级,请设置 :enable_starttls。)

回溯
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/mail-2.9.0/lib/mail/network/delivery_methods/smtp.rb:159:in `build_smtp_session'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/mail-2.9.0/lib/mail/network/delivery_methods/smtp.rb:154:in `start_smtp_session'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/mail-2.9.0/lib/mail/network/delivery_methods/smtp.rb:108:in `deliver!'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/mail-2.9.0/lib/mail/message.rb:269:in `deliver!'
/usr/local/lib/ruby/3.3.0/delegate.rb:87:in `method_missing'
/var/www/discourse/lib/email/sender.rb:296:in `send'
/var/www/discourse/app/jobs/regular/user_email.rb:80:in `send_user_email'
/var/www/discourse/app/jobs/regular/user_email.rb:40:in `execute'
/var/www/discourse/app/jobs/base.rb:318:in `block (2 levels) in perform'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/rails_multisite-7.0.0/lib/rails_multisite/connection_management/null_instance.rb:49:in `with_connection'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/rails_multisite-7.0.0/lib/rails_multisite/connection_management.rb:17:in `with_connection'
/var/www/discourse/app/jobs/base.rb:305:in `block in perform'
/var/www/discourse/app/jobs/base.rb:301:in `each'
/var/www/discourse/app/jobs/base.rb:301:in `perform'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/processor.rb:220:in `execute_job'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/processor.rb:185:in `block (4 levels) in process'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/middleware/chain.rb:180:in `traverse'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/middleware/chain.rb:183:in `block in traverse'
/var/www/discourse/lib/sidekiq/suppress_user_email_errors.rb:6:in `call'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/middleware/chain.rb:182:in `traverse'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/middleware/chain.rb:183:in `block in traverse'
/var/www/discourse/lib/sidekiq/discourse_event.rb:6:in `call'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/middleware/chain.rb:182:in `traverse'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/middleware/chain.rb:183:in `block in traverse'
/var/www/discourse/lib/sidekiq/pausable.rb:131:in `call'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/middleware/chain.rb:182:in `traverse'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/middleware/chain.rb:183:in `block in traverse'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/job/interrupt_handler.rb:9:in `call'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/middleware/chain.rb:182:in `traverse'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/middleware/chain.rb:183:in `block in traverse'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/metrics/tracking.rb:26:in `track'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/metrics/tracking.rb:134:in `call'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/middleware/chain.rb:182:in `traverse'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/middleware/chain.rb:173:in `invoke'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/processor.rb:184:in `block (3 levels) in process'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/processor.rb:145:in `block (6 levels) in dispatch'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/job_retry.rb:118:in `local'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/processor.rb:144:in `block (5 levels) in dispatch'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/config.rb:39:in `block in <class:Config>'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/processor.rb:139:in `block (4 levels) in dispatch'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/processor.rb:281:in `stats'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/processor.rb:134:in `block (3 levels) in dispatch'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/job_logger.rb:15:in `call'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/processor.rb:133:in `block (2 levels) in dispatch'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/job_retry.rb:85:in `global'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/processor.rb:132:in `block in dispatch'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/job_logger.rb:40:in `prepare'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/processor.rb:131:in `dispatch'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/processor.rb:183:in `block (2 levels) in process'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/processor.rb:182:in `handle_interrupt'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/processor.rb:182:in `block in process'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/processor.rb:181:in `handle_interrupt'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/processor.rb:181:in `process'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/processor.rb:86:in `process_one'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/processor.rb:76:in `run'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/component.rb:10:in `watchdog'
/var/www/discourse/vendor/bundle/ruby/3.3.0/gems/sidekiq-7.3.9/lib/sidekiq/component.rb:19:in `block in safe_thread'

和 Johann Christoph 一样,我之前将 DISCOURSE_SMTP_ENABLE_START_TLS 注释掉,并将 DISCOURSE_SMTP_FORCE_TLS 设置为 true 以实现隐式 TLS,效果非常好。但现在我必须明确地将 DISCOURSE_SMTP_ENABLE_START_TLS 设置为 false 才能使邮件投递正常工作,这当然会在管理仪表板中触发上述消息。

1 个赞

force_tls 阻止了邮件发送——它在连接时强制使用 TLS,可能只适用于端口 465。

真是个独角兽!

您使用的是哪些 SMTP 参数(不包括用户名/密码)?

端口 465,设置 ENABLE_START_TLS=falseFORCE_TLS=trueOPEN_TIMEOUT=10。最后一个设置是因为大约三年前系统更新后我开始看到奇怪的超时错误,但如果这与上述问题有关,我愿意吃掉我的帽子。

在此期间我查看了提交历史,注意到 mail gem 在引入问题的那次更新前三天更新到了 2.9.0 版本(#36254)。该版本发布说明中有一条提到“SMTP: refactor and accept starttls :always and :auto by @eval in #1536”。我对 Ruby 不太了解,但引用 PR 中这项更改在我看来有点可疑:

考虑到 DISCOURSE_SMTP_ENABLE_START_TLS 默认文档说明为 true(即如果注释掉),这是否可能是问题的根源?

正如我所写,我设置了 force_TLS:

DISCOURSE_SMTP_FORCE_TLS = true

并将 ENABLE_START_TLS 明确设置为 false……然后邮件发送就可以工作了——前提是邮件服务器运行在 465 端口上。

对于运行在 567 端口上的邮件服务器,情况则正好相反。

从那时起,我在后端仪表板上有一个提示……但邮件发送没有问题。

要能将 DISCOURSE_SMTP_ENABLE_START_TLS 设置为 false,必须取消对其的注释——因为它默认设置为 true。正是这一点,为我造成了问题。不过,这仅在最新的构建(2025.12.0-latest)中出现。

哇,这是一个糟糕的更改。

enable_starttls要求使用 starttls,但 enable_starttls_auto 是机会主义的——它只会在提供 TLS 时协商 TLS。

如果邮件服务器是通过初始 TLS 连接的,它将不会提供 starttls:

○ → openssl s_client -connect localhost:5587 -starttls smtp
250 CHUNKING
EHLO localhost
250-testmailrelay
250-PIPELINING
250-SIZE 10240000
250-VRFY
250-ETRN
250-AUTH PLAIN
250-ENHANCEDSTATUSCODES
250-8BITMIME
250-DSN
250 CHUNKING

他们到底为什么要这么做?:facepalm:
这里的困难在于,我们一开始就不应该提供这种配置,它应该像这样:

DISCOURSE_SMTP_TLS_MODE = starttls_auto # [ none | starttls | starttls_auto (default) | tls ]

为了尽量减少人们的配置工作,而不是增加,我认为这种方法是最好的:

1 个赞

这现在已合并,不再需要显式禁用 STARTTLS,如果启用了 TLS,Discourse 会处理该问题。

是的,它又正常工作了。谢谢!