최근 이메일 작업(emails:task) 및 관련 코드를 검토하여 각 실패 코드 경로(failure codepath)를 테스트하고 오류 메시지를 다듬는 작업을 진행했습니다.
또한 DISCOURSE_SMTP_ENABLE_START_TLS=false를 설정했을 때의 효과가 (거의) 없음을 발견했습니다. 이 설정을 해도 STARTTLS가 실제로 비활성화되지 않았으며, 실제로는 연결 시 TLS(DISCOURSE_SMTP_FORCE_TLS=true)와 함께 문제없이 공존할 수 있었습니다.
이 PR을 병합하기 전에, DISCOURSE_SMTP_ENABLE_STARTTLS=false가 설정된 경우 대시보드에 관리자 경고를 표시하는 것이 적절하다고 생각합니다. 이 설정을 적용했지만 실제로는 불필요했으며, 여전히 STARTTLS에 의존하고 있는 셀프호스터(self-hoster)가 적어도 한 명은 있을 것으로 추정됩니다.
좋은 작업이네요! 제가 (아마도) 알아챈 점이 하나 있는데, rake 태스크가 실제 전송 시 사용되는 코드(예: /admin/email 테스트 페이지에서)와 동일한 코드를 사용하지 않는다는 것입니다. UX에서는 동작했지만 rake 태스크에서는 동작하지 않던 경우가 있었던 것 같습니다(아니면 그 반대였을까요?).
아직 머릿속에 선명하실 때, 실제 전송 시에는 Discourse가 사용하는 것과 동일한 코드를 사용하는지 확인해 주시면 좋겠습니다.
@supermathie 이 변수가 설정된 모든 사이트의 모든 관리자에게 개인 메시지를 보내는 것이 가치가 있을까요? 현재 문제 검사 시스템은 그렇게 하지만, 대부분의 경우 이것이 nil 효과를 가지므로 여기서는 그렇게 할 필요가 없다고 생각합니다. 이상적으로는 관리자 사용자에게 알림을 보내지 않고 대시보드에만 표시되길 원하는데, 현재 문제 검사 구조가 이 사용 사례를 지원하는지 확실하지 않습니다.
저도 이 문제를 발견했습니다. 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.라는 오류 메시지와 함께 이메일 전송 실패가 발생하기 시작했습니다.
백트레이스
/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를 주석 처리하고 암시적 TLS(implicit TLS)를 위해 DISCOURSE_SMTP_FORCE_TLS를 true로 설정해 두었으며, 그때는 문제없이 잘 작동했습니다. 하지만 지금은 이메일 전송이 정상적으로 작동하려면 DISCOURSE_SMTP_ENABLE_START_TLS를 명시적으로 false로 설정해야 하며, 당연히 이는 관리자 대시보드에서 aforementioned 메시지가 트리거되게 만듭니다.
포트 465, ENABLE_START_TLS=false, FORCE_TLS=true, OPEN_TIMEOUT=10. 마지막 설정은 약 3년 전 시스템 업데이트 이후부터 이상한 타임아웃 오류가 발생하기 시작해서 그랬는데, 위에서 설명한 문제와 관련이 있다면 모자를 먹고 살겠습니다.
그동안 커밋 히스토리를 살펴보다가, 제 환경에서 문제가 발생한 업데이트 바로 3일 전에 mail 게미가 2.9.0 버전으로 업데이트되었음을 확인했습니다. 해당 릴리스 노트에는 "SMTP: refactor and accept starttls :always and :auto by @eval in #1536"라는 항목이 있습니다. 저는 Ruby에 대해 많이 알지 못하지만, 참조된 PR의 이 변경 사항은 다소 의심스럽습니다:
DISCOURSE_SMTP_ENABLE_START_TLS를 false로 설정하려면 주석 처리해야 합니다. 그렇지 않으면 기본적으로 true로 설정되어 있기 때문입니다. 바로 이것이 제 문제를 일으킨 원인이었습니다. 다만, 최신 빌드(2025.12.0-latest)에서야 비로소 해당 문제가 발생했습니다.
좋아요. STARTTLS를 비활성화할 필요가 없다는 경고가 나와서 짜증이 났는데, 사실 저희 SMTP 서버는 STARTTLS를 아예 지원하지 않거든요(암시적 TLS만 지원). 그래서 DISCOURSE_SMTP_FORCE_TLS=true를 설정하면 STARTTLS가 암시적으로 비활성화되는 건가요?