selase
(Selase Krakani)
September 12, 2025, 2:31pm
2
No, this isn’t supported out of the box, aside from the email custom headers
site setting which applies globally to all outgoing emails.
Yes, a custom plugin would the best approach here.
emonunix:
Would modifying the core code be the only solution, and if so, could you point me in the right direction (e.g., which files to look at)?
The current flow for digest emails, is as follows:
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
A plugin could hook into the message building process (Email::MessageBuilder
) and inject(preferably via a modifier) your custom header conditionally, only for digests.
2 Likes