mirror of
https://github.com/discourse/discourse.git
synced 2024-12-25 11:33:54 +08:00
ed47b55026
A while ago we increased group SMTP read and open timeouts
to address issues we were seeing with Gmail sometimes giving
really long timeouts for these values. The commit was:
3e639e4aa7
Now, we want to increase all SMTP read timeouts to 30s,
since the 5s is too low sometimes, and the ruby Net::SMTP
stdlib also defaults to 30s.
Also, we want to slightly tweak the group smtp email job
not to fail if the IncomingEmail log fails to create, or if
a ReadTimeout is encountered, to avoid retrying the job in sidekiq
again and sending the same email out.
141 lines
4.8 KiB
Ruby
141 lines
4.8 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
module Jobs
|
|
class GroupSmtpEmail < ::Jobs::Base
|
|
include Skippable
|
|
|
|
sidekiq_options queue: "critical"
|
|
|
|
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
|
|
end
|
|
|
|
def execute(args)
|
|
return if quit_email_early?
|
|
email = args[:email]
|
|
recipient_user = User.find_by_email(email, primary: true)
|
|
|
|
post = Post.find_by(id: args[:post_id])
|
|
return skip(email, nil, recipient_user, :group_smtp_post_deleted) if post.blank?
|
|
|
|
group = Group.find_by(id: args[:group_id])
|
|
return if group.blank?
|
|
|
|
if !group.smtp_enabled
|
|
return skip(email, post, recipient_user, :group_smtp_disabled_for_group)
|
|
end
|
|
|
|
if !Topic.exists?(id: post.topic_id)
|
|
return skip(email, post, recipient_user, :group_smtp_topic_deleted)
|
|
end
|
|
|
|
cc_addresses =
|
|
args[:cc_emails].filter { |address| EmailAddressValidator.valid_value?(address) }
|
|
|
|
# Mask the email addresses of non-staged users so
|
|
# they are not revealed unnecessarily when we are sending
|
|
# the email notification out.
|
|
bcc_addresses = User.not_staged.with_email(cc_addresses).pluck(:email)
|
|
cc_addresses = cc_addresses - bcc_addresses
|
|
|
|
# There is a rare race condition causing the Imap::Sync class to create
|
|
# an incoming email and associated post/topic, which then kicks off
|
|
# the PostAlerter to notify others in the PM about a reply in the topic,
|
|
# but for the OP which is not necessary (because the person emailing the
|
|
# IMAP inbox already knows about the OP)
|
|
#
|
|
# Basically, we should never be sending this notification for the first
|
|
# post in a topic.
|
|
#
|
|
# If the group does not have IMAP enabled then this could be legitimate,
|
|
# for example in cases where we are creating a new topic to reply to another
|
|
# group PM and we need to send the participants the group OP email.
|
|
if post.is_first_post? && group.imap_enabled
|
|
ImapSyncLog.warn(
|
|
"Aborting SMTP email for post #{post.id} in topic #{post.topic_id} to #{email}, the post is the OP and should not send an email.",
|
|
group,
|
|
)
|
|
return
|
|
end
|
|
|
|
ImapSyncLog.debug(
|
|
"Sending SMTP email for post #{post.id} in topic #{post.topic_id} to #{email}.",
|
|
group,
|
|
)
|
|
|
|
# The EmailLog record created by the sender will have the raw email
|
|
# stored, the group smtp ID, and any cc addresses recorded for later
|
|
# cross referencing.
|
|
message =
|
|
GroupSmtpMailer.send_mail(
|
|
group,
|
|
email,
|
|
post,
|
|
cc_addresses: cc_addresses,
|
|
bcc_addresses: bcc_addresses,
|
|
)
|
|
|
|
begin
|
|
Email::Sender.new(message, :group_smtp, recipient_user).send
|
|
rescue Net::ReadTimeout => err
|
|
# We can't be sure if the send actually failed or if ENTER . ENTER (to end
|
|
# the SMTP data sequence) just timed out, as is the case with Gmail occassionaly,
|
|
# where they can do this if they suspect you are sending spam.
|
|
Discourse.warn_exception(
|
|
err,
|
|
message: "Got SMTP read timeout when sending group SMTP email",
|
|
env: args,
|
|
)
|
|
end
|
|
|
|
# Create an incoming email record to avoid importing again from IMAP
|
|
# server. While this may not be technically required if IMAP is not
|
|
# currently enabled for the group, it will help a lot with the initial
|
|
# sync if it is turned on at a later date.
|
|
begin
|
|
IncomingEmail.create!(
|
|
user_id: post.user_id,
|
|
topic_id: post.topic_id,
|
|
post_id: post.id,
|
|
raw: message.to_s,
|
|
subject: message.subject,
|
|
message_id: message.message_id,
|
|
to_addresses: message.to,
|
|
cc_addresses: message.cc,
|
|
from_address: message.from,
|
|
created_via: IncomingEmail.created_via_types[:group_smtp],
|
|
)
|
|
rescue => err
|
|
Discourse.warn_exception(
|
|
err,
|
|
message: "Failed to create IncomingEmail record when sending group SMTP email",
|
|
env: args,
|
|
)
|
|
end
|
|
end
|
|
|
|
def quit_email_early?
|
|
SiteSetting.disable_emails == "yes" || !SiteSetting.enable_smtp
|
|
end
|
|
|
|
def skip(email, post, recipient_user, reason)
|
|
create_skipped_email_log(
|
|
email_type: :group_smtp,
|
|
to_address: email,
|
|
user_id: recipient_user&.id,
|
|
post_id: post&.id,
|
|
reason_type: SkippedEmailLog.reason_types[reason],
|
|
)
|
|
end
|
|
end
|
|
end
|