discourse/app/jobs/regular/group_smtp_email.rb
Joffrey JAFFEUX e2db27a7ba
FIX: use next instead of return in sidekiq_retry_in blocks (#39529)
Using `return` inside a block attempts to return from the enclosing
method rather than from the block itself. In Sidekiq's `job_retry`
context this raises `LocalJumpError: unexpected return`, which shows up
in logs as:

```
Job exception: unexpected return
```

The correct Ruby idiom for exiting a block with a value is `next
<value>`.

Four jobs had the same pattern:
- `app/jobs/regular/user_email.rb`
- `app/jobs/regular/group_smtp_email.rb`
- `app/jobs/regular/notify_mailing_list_subscribers.rb`
- `plugins/automation/app/jobs/regular/discourse_automation/trigger.rb`
2026-04-24 16:59:49 +02:00

116 lines
3.8 KiB
Ruby
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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
next 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
# 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,
)
# Idempotency check if the EmailLog already exists, do not send again.
return if EmailLog.exists?(message_id: message.message_id)
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 occasionally,
# 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,
)
raise err # Re-raise the error so Sidekiq's retry mechanism kicks in.
end
# Create an incoming email record for tracking purposes.
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