emonunix
(Emon Bhuiyan)
9월 10, 2025, 8:19오후
1
안녕하세요 여러분,
저는 자체 호스팅(self-hosted)된 Discourse를 사용 중이며, 나가는 이메일에 커스텀 헤더, 구체적으로 X-TAGS를 추가하는 방법을 찾고 있습니다. 목표는 이메일 서비스 제공업체(Netcore Email API)와 연동하여 더 나은 분석 및 추적을 가능하게 하는 것입니다. 주간 다이제스트와 같은 특정 유형의 이메일에 태그를 붙여 이메일 참여도에 대한 더 세분화된 인사이트를 얻고 싶습니다.
문제:
다이제스트로 발송되는 모든 이메일에 X-TAGS: Digest와 같은 헤더를 추가해야 합니다. 이 헤더는 내부 추적을 위한 것이며, 이메일 제공업체가 전달 전에 제거하므로 최종 사용자에게는 표시되지 않습니다.
이미 살펴본 내용:
사이트 설정: /admin/site_settings/category/email 페이지를 확인했으며 email custom headers 설정을 찾았습니다. 그러나 이는 모든 이메일에 정적(static) 헤더를 추가하는 것 같습니다. 이를 동적으로 만들거나 특정 유형의 이메일(예: 다이제스트)에만 적용할 수 있는지 확실하지 않습니다.
기존 포럼 주제: Discourse Meta 포럼을 검색하여 커스텀 헤더에 대한 몇 가지 토론을 찾았지만, 대부분 이메일 제목에 주제 태그를 추가하거나 사용자 측 필터링(예: Gmail)에 초점이 맞춰져 있으며 외부 이메일 분석에는 해당하지 않습니다. 이를 위한 공식적으로 문서화된 간단한 방법이 없어 보입니다.
질문:
다이제스트 이메일에 X-TAGS와 같은 커스텀 헤더를 특별히 추가할 수 있는 내장된 방법이나 제가 놓치고 있는 특정 사이트 설정이 있습니까?
없다면, 이는 커스텀 플러그인으로 구현할 수 있는 기능입니까?
코어 코드를 수정하는 것이 유일한 해결책입니까? 그렇다면 어디를 봐야 하는지(예: 어떤 파일을 봐야 하는지) 방향을 제시해 주실 수 있습니까?
가이드나 제안이 있다면 큰 도움이 될 것입니다. 미리 도움을 주셔서 감사합니다!
selase
(Selase Krakani)
9월 12, 2025, 2:31오후
2
아니요, 기본적으로는 지원되지 않습니다. email custom headers 사이트 설정은 모든 발신 이메일에 전역적으로 적용되지만, 다이제스트에만 적용하는 것은 불가능합니다.
네, 이 경우 사용자 정의 플러그인이 가장 좋은 접근 방식입니다.
현재 다이제스트 이메일의 흐름은 다음과 같습니다:
def execute(args)
return if SiteSetting.disable_emails == "yes"
return if SiteSetting.disable_digest_emails?
return if SiteSetting.private_email?
target_user_ids.each do |user_id|
::Jobs.enqueue(:user_email, type: "digest", user_id: user_id)
end
end
# frozen_string_literal: true
module Jobs
# Asynchronously send an email to a user
class UserEmail < ::Jobs::Base
include Skippable
sidekiq_options queue: "low"
sidekiq_retry_in do |count, exception|
# retry in an hour when SMTP server is busy
# or use default sidekiq retry formula. returning
# nil/0 will trigger the default sidekiq
# retry formula
#
# See https://github.com/mperham/sidekiq/blob/3330df0ee37cfd3e0cd3ef01e3e66b584b99d488/lib/sidekiq/job_retry.rb#L216-L234
case exception.wrapped
when Net::SMTPServerBusy
return 1.hour + (rand(30) * (count + 1))
end
This file has been truncated. show original
def digest(user, opts = {})
build_summary_for(user)
@unsubscribe_key = UnsubscribeKey.create_key_for(@user, UnsubscribeKey::DIGEST_TYPE)
@since = opts[:since].presence
@since ||= [user.last_seen_at, user.user_stat&.digest_attempted_at, 1.month.ago].compact.max
# Fetch some topics and posts to show
digest_opts = {
limit: SiteSetting.digest_topics + SiteSetting.digest_other_topics,
top_order: true,
}
topics_for_digest = Topic.for_digest(user, @since, digest_opts)
if topics_for_digest.empty? && !user.user_option.try(:include_tl0_in_digests)
# Find some topics from new users that are at least 24 hours old
topics_for_digest =
Topic.for_digest(user, @since, digest_opts.merge(include_tl0: true)).where(
"topics.created_at < ?",
24.hours.ago,
)
This file has been truncated. show original
# frozen_string_literal: true
# Builds a Mail::Message we can use for sending. Optionally supports using a template
# for the body and subject
module Email
class MessageBuilder
attr_reader :template_args, :reply_by_email_key
ALLOW_REPLY_BY_EMAIL_HEADER = "X-Discourse-Allow-Reply-By-Email"
INSTRUCTIONS_SEPARATOR = "---\n"
def initialize(to, opts = nil)
@to = to
@opts = opts || {}
@template_args = {
site_name: SiteSetting.title,
email_prefix: SiteSetting.email_prefix.presence || SiteSetting.title,
base_url: Discourse.base_url,
user_preferences_url: "#{Discourse.base_url}/my/preferences",
hostname: Discourse.current_hostname,
This file has been truncated. show original
플러그인은 메시지 빌드 과정(Email::MessageBuilder)에 후킹하여, 다이제스트에만 조건부로 사용자 정의 헤더를 삽입할 수 있습니다(수정자(modifier)를 통해 하는 것이 바람직합니다).