2013-06-10 15:33:37 -04:00
|
|
|
require_dependency 'email/sender'
|
2013-02-05 14:16:51 -05:00
|
|
|
|
|
|
|
module Jobs
|
|
|
|
|
|
|
|
# Asynchronously send an email to a user
|
|
|
|
class UserEmail < Jobs::Base
|
|
|
|
|
|
|
|
def execute(args)
|
|
|
|
raise Discourse::InvalidParameters.new(:user_id) unless args[:user_id].present?
|
2016-01-29 10:49:49 -05:00
|
|
|
raise Discourse::InvalidParameters.new(:type) unless args[:type].present?
|
2013-02-05 14:16:51 -05:00
|
|
|
|
2016-02-15 11:53:07 -05:00
|
|
|
post = nil
|
|
|
|
notification = nil
|
2016-01-26 20:19:49 -05:00
|
|
|
type = args[:type]
|
|
|
|
user = User.find_by(id: args[:user_id])
|
2016-02-15 11:53:07 -05:00
|
|
|
to_address = args[:to_address].presence || user.try(:email).presence || "no_email_found"
|
|
|
|
|
2016-02-16 10:35:57 -05:00
|
|
|
set_skip_context(type, args[:user_id], to_address, args[:post_id])
|
2015-11-06 13:19:13 -05:00
|
|
|
|
2016-01-29 10:49:49 -05:00
|
|
|
return skip(I18n.t("email_log.no_user", user_id: args[:user_id])) unless user
|
2013-02-25 11:42:20 -05:00
|
|
|
|
2016-02-15 11:53:07 -05:00
|
|
|
if args[:post_id].present?
|
2014-05-06 09:41:59 -04:00
|
|
|
post = Post.find_by(id: args[:post_id])
|
2016-01-29 10:49:49 -05:00
|
|
|
return skip(I18n.t('email_log.post_not_found', post_id: args[:post_id])) unless post.present?
|
2016-01-26 20:19:49 -05:00
|
|
|
end
|
2013-02-05 14:16:51 -05:00
|
|
|
|
2016-01-26 20:19:49 -05:00
|
|
|
if args[:notification_id].present?
|
|
|
|
notification = Notification.find_by(id: args[:notification_id])
|
|
|
|
end
|
|
|
|
|
2016-02-15 11:53:07 -05:00
|
|
|
message, skip_reason = message_for_email(user,
|
|
|
|
post,
|
|
|
|
type,
|
|
|
|
notification,
|
|
|
|
args[:notification_type],
|
|
|
|
args[:notification_data_hash],
|
|
|
|
args[:email_token],
|
|
|
|
args[:to_address])
|
2016-01-26 20:19:49 -05:00
|
|
|
|
|
|
|
if message
|
2016-02-15 11:53:07 -05:00
|
|
|
Email::Sender.new(message, type, user).send
|
2016-01-26 20:19:49 -05:00
|
|
|
else
|
|
|
|
skip_reason
|
2013-02-05 14:16:51 -05:00
|
|
|
end
|
2016-01-26 20:19:49 -05:00
|
|
|
end
|
|
|
|
|
2016-02-16 10:35:57 -05:00
|
|
|
def set_skip_context(type, user_id, to_address, post_id)
|
|
|
|
@skip_context = { type: type, user_id: user_id, to_address: to_address, post_id: post_id }
|
2016-01-26 20:19:49 -05:00
|
|
|
end
|
|
|
|
|
2016-05-06 13:34:33 -04:00
|
|
|
NOTIFICATIONS_SENT_BY_MAILING_LIST ||= Set.new %w{
|
|
|
|
posted
|
|
|
|
replied
|
|
|
|
mentioned
|
|
|
|
group_mentioned
|
|
|
|
quoted
|
|
|
|
}
|
|
|
|
|
2017-03-08 04:06:16 -05:00
|
|
|
CRITICAL_EMAIL_TYPES ||= Set.new %w{
|
2016-05-06 13:34:33 -04:00
|
|
|
account_created
|
|
|
|
admin_login
|
|
|
|
confirm_new_email
|
|
|
|
confirm_old_email
|
|
|
|
forgot_password
|
|
|
|
notify_old_email
|
|
|
|
signup
|
|
|
|
signup_after_approval
|
|
|
|
}
|
2016-01-26 20:19:49 -05:00
|
|
|
|
2017-03-08 13:19:11 -05:00
|
|
|
def message_for_email(user, post, type, notification, notification_type=nil, notification_data_hash=nil, email_token=nil, to_address=nil)
|
2016-02-16 10:35:57 -05:00
|
|
|
set_skip_context(type, user.id, to_address || user.email, post.try(:id))
|
2013-02-05 14:16:51 -05:00
|
|
|
|
2016-01-29 10:49:49 -05:00
|
|
|
return skip_message(I18n.t("email_log.anonymous_user")) if user.anonymous?
|
2017-01-05 03:15:55 -05:00
|
|
|
return skip_message(I18n.t("email_log.suspended_not_pm")) if user.suspended? && type.to_s != "user_private_message"
|
2013-02-05 14:16:51 -05:00
|
|
|
|
2017-03-08 13:19:11 -05:00
|
|
|
return if user.staged && type.to_s == "digest"
|
2013-02-25 11:42:20 -05:00
|
|
|
|
2016-01-26 20:19:49 -05:00
|
|
|
seen_recently = (user.last_seen_at.present? && user.last_seen_at > SiteSetting.email_time_window_mins.minutes.ago)
|
2016-02-16 23:46:19 -05:00
|
|
|
seen_recently = false if user.user_option.email_always || user.staged
|
2013-02-05 14:16:51 -05:00
|
|
|
|
2016-01-26 20:19:49 -05:00
|
|
|
email_args = {}
|
|
|
|
|
|
|
|
if post || notification || notification_type
|
|
|
|
return skip_message(I18n.t('email_log.seen_recently')) if seen_recently && !user.suspended?
|
|
|
|
end
|
|
|
|
|
2017-03-08 13:19:11 -05:00
|
|
|
email_args[:post] = post if post
|
2013-02-05 14:16:51 -05:00
|
|
|
|
2016-01-26 20:19:49 -05:00
|
|
|
if notification || notification_type
|
2016-02-15 11:53:07 -05:00
|
|
|
email_args[:notification_type] ||= notification_type || notification.try(:notification_type)
|
2016-01-26 20:19:49 -05:00
|
|
|
email_args[:notification_data_hash] ||= notification_data_hash || notification.try(:data_hash)
|
|
|
|
|
2016-02-05 14:07:30 -05:00
|
|
|
unless String === email_args[:notification_type]
|
|
|
|
if Numeric === email_args[:notification_type]
|
|
|
|
email_args[:notification_type] = Notification.types[email_args[:notification_type]]
|
|
|
|
end
|
|
|
|
email_args[:notification_type] = email_args[:notification_type].to_s
|
|
|
|
end
|
|
|
|
|
2016-02-16 23:46:19 -05:00
|
|
|
if user.user_option.mailing_list_mode? &&
|
2016-10-05 19:28:58 -04:00
|
|
|
user.user_option.mailing_list_mode_frequency > 0 && # don't catch notifications for users on daily mailing list mode
|
2016-02-24 01:30:28 -05:00
|
|
|
(!post.try(:topic).try(:private_message?)) &&
|
2016-02-03 13:27:58 -05:00
|
|
|
NOTIFICATIONS_SENT_BY_MAILING_LIST.include?(email_args[:notification_type])
|
|
|
|
# no need to log a reason when the mail was already sent via the mailing list job
|
|
|
|
return [nil, nil]
|
|
|
|
end
|
|
|
|
|
2016-02-16 23:46:19 -05:00
|
|
|
unless user.user_option.email_always?
|
2016-01-26 20:19:49 -05:00
|
|
|
if (notification && notification.read?) || (post && post.seen?(user))
|
|
|
|
return skip_message(I18n.t('email_log.notification_already_read'))
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
skip_reason = skip_email_for_post(post, user)
|
2017-03-08 13:19:11 -05:00
|
|
|
return skip_message(skip_reason) if skip_reason.present?
|
2013-06-10 12:02:04 -04:00
|
|
|
|
2013-02-05 14:16:51 -05:00
|
|
|
# Make sure that mailer exists
|
2016-01-26 20:19:49 -05:00
|
|
|
raise Discourse::InvalidParameters.new("type=#{type}") unless UserNotifications.respond_to?(type)
|
|
|
|
|
2017-03-08 13:19:11 -05:00
|
|
|
email_args[:email_token] = email_token if email_token.present?
|
|
|
|
email_args[:new_email] = user.email if type.to_s == "notify_old_email"
|
2016-03-07 14:40:11 -05:00
|
|
|
|
2016-03-23 00:08:34 -04:00
|
|
|
if EmailLog.reached_max_emails?(user)
|
2016-05-02 17:15:32 -04:00
|
|
|
return skip_message(I18n.t('email_log.exceeded_emails_limit'))
|
|
|
|
end
|
|
|
|
|
2017-03-08 04:06:16 -05:00
|
|
|
if !CRITICAL_EMAIL_TYPES.include?(type.to_s) && user.user_stat.bounce_score >= SiteSetting.bounce_score_threshold
|
2016-05-02 17:15:32 -04:00
|
|
|
return skip_message(I18n.t('email_log.exceeded_bounces_limit'))
|
2016-03-23 00:08:34 -04:00
|
|
|
end
|
|
|
|
|
2016-04-15 01:59:01 -04:00
|
|
|
message = EmailLog.unique_email_per_post(post, user) do
|
|
|
|
UserNotifications.send(type, user, email_args)
|
|
|
|
end
|
2015-11-30 20:12:55 -05:00
|
|
|
|
2013-02-05 14:16:51 -05:00
|
|
|
# Update the to address if we have a custom one
|
2017-03-08 13:19:11 -05:00
|
|
|
message.to = to_address if message && to_address.present?
|
2013-02-05 14:16:51 -05:00
|
|
|
|
2016-01-29 10:49:49 -05:00
|
|
|
[message, nil]
|
2013-06-10 16:46:08 -04:00
|
|
|
end
|
|
|
|
|
2016-05-09 14:37:33 -04:00
|
|
|
sidekiq_retry_in do |count, exception|
|
|
|
|
# retry in an hour when SMTP server is busy
|
|
|
|
# or use default sidekiq retry formula
|
|
|
|
case exception.wrapped
|
|
|
|
when Net::SMTPServerBusy
|
|
|
|
1.hour + (rand(30) * (count + 1))
|
|
|
|
else
|
|
|
|
Jobs::UserEmail.seconds_to_delay(count)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# extracted from sidekiq
|
|
|
|
def self.seconds_to_delay(count)
|
|
|
|
(count ** 4) + 15 + (rand(30) * (count + 1))
|
|
|
|
end
|
|
|
|
|
2013-06-10 16:46:08 -04:00
|
|
|
private
|
2013-02-05 14:16:51 -05:00
|
|
|
|
2016-01-26 20:19:49 -05:00
|
|
|
def skip_message(reason)
|
2016-01-29 10:49:49 -05:00
|
|
|
[nil, skip(reason)]
|
2016-01-26 20:19:49 -05:00
|
|
|
end
|
|
|
|
|
2013-06-10 16:46:08 -04:00
|
|
|
# If this email has a related post, don't send an email if it's been deleted or seen recently.
|
|
|
|
def skip_email_for_post(post, user)
|
2014-02-14 13:06:21 -05:00
|
|
|
if post
|
2016-06-03 09:48:54 -04:00
|
|
|
return I18n.t('email_log.topic_nil') if post.topic.blank?
|
|
|
|
return I18n.t('email_log.post_user_deleted') if post.user.blank?
|
|
|
|
return I18n.t('email_log.post_deleted') if post.user_deleted?
|
2017-03-08 13:19:11 -05:00
|
|
|
return I18n.t('email_log.user_suspended') if user.suspended? && !post.user&.staff?
|
2016-06-30 21:22:07 -04:00
|
|
|
|
2017-03-08 13:19:11 -05:00
|
|
|
already_read = !user.user_option.email_always? && PostTiming.exists?(topic_id: post.topic_id, post_number: post.post_number, user_id: user.id)
|
|
|
|
return I18n.t('email_log.already_read') if already_read
|
2014-02-14 13:06:21 -05:00
|
|
|
else
|
|
|
|
false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-01-29 10:49:49 -05:00
|
|
|
def skip(reason)
|
|
|
|
EmailLog.create!(
|
|
|
|
email_type: @skip_context[:type],
|
|
|
|
to_address: @skip_context[:to_address],
|
|
|
|
user_id: @skip_context[:user_id],
|
2016-02-16 10:35:57 -05:00
|
|
|
post_id: @skip_context[:post_id],
|
2016-01-29 10:49:49 -05:00
|
|
|
skipped: true,
|
2016-02-15 11:53:07 -05:00
|
|
|
skipped_reason: "[UserEmail] #{reason}",
|
2016-01-29 10:49:49 -05:00
|
|
|
)
|
2013-02-05 14:16:51 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
end
|