mirror of
https://github.com/discourse/discourse.git
synced 2025-02-25 18:55:32 -06:00
FEATURE: If PM email bounced for staged user then alert in whisper reply (#6648)
This commit is contained in:
parent
8bca2647ea
commit
cedd2118c4
@ -2692,6 +2692,15 @@ en:
|
|||||||
|
|
||||||
Can you make sure [your email address](%{base_url}/my/preferences/email) is valid and working? You may also wish to add our email address to your address book / contact list to improve deliverability.
|
Can you make sure [your email address](%{base_url}/my/preferences/email) is valid and working? You may also wish to add our email address to your address book / contact list to improve deliverability.
|
||||||
|
|
||||||
|
email_bounced: |
|
||||||
|
The message to %{email} bounced.
|
||||||
|
|
||||||
|
### Details
|
||||||
|
|
||||||
|
```text
|
||||||
|
%{raw}
|
||||||
|
```
|
||||||
|
|
||||||
too_many_spam_flags:
|
too_many_spam_flags:
|
||||||
title: "Too Many Spam Flags"
|
title: "Too Many Spam Flags"
|
||||||
subject_template: "New account on hold"
|
subject_template: "New account on hold"
|
||||||
|
@ -18,11 +18,9 @@ module Email
|
|||||||
@receiver.process!
|
@receiver.process!
|
||||||
rescue RateLimiter::LimitExceeded
|
rescue RateLimiter::LimitExceeded
|
||||||
@retry_on_rate_limit ? Jobs.enqueue(:process_email, mail: @mail) : raise
|
@retry_on_rate_limit ? Jobs.enqueue(:process_email, mail: @mail) : raise
|
||||||
rescue Email::Receiver::BouncedEmailError => e
|
|
||||||
# never reply to bounced emails
|
|
||||||
log_email_process_failure(@mail, e)
|
|
||||||
set_incoming_email_rejection_message(@receiver.incoming_email, I18n.t("emails.incoming.errors.bounced_email_error"))
|
|
||||||
rescue => e
|
rescue => e
|
||||||
|
return handle_bounce(e) if @receiver.is_bounce?
|
||||||
|
|
||||||
log_email_process_failure(@mail, e)
|
log_email_process_failure(@mail, e)
|
||||||
incoming_email = @receiver.try(:incoming_email)
|
incoming_email = @receiver.try(:incoming_email)
|
||||||
rejection_message = handle_failure(@mail, e)
|
rejection_message = handle_failure(@mail, e)
|
||||||
@ -34,6 +32,12 @@ module Email
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def handle_bounce(e)
|
||||||
|
# never reply to bounced emails
|
||||||
|
log_email_process_failure(@mail, e)
|
||||||
|
set_incoming_email_rejection_message(@receiver.incoming_email, I18n.t("emails.incoming.errors.bounced_email_error"))
|
||||||
|
end
|
||||||
|
|
||||||
def handle_failure(mail_string, e)
|
def handle_failure(mail_string, e)
|
||||||
message_template = case e
|
message_template = case e
|
||||||
when Email::Receiver::NoSenderDetectedError then return nil
|
when Email::Receiver::NoSenderDetectedError then return nil
|
||||||
|
@ -70,8 +70,10 @@ module Email
|
|||||||
return if IncomingEmail.exists?(message_id: @message_id)
|
return if IncomingEmail.exists?(message_id: @message_id)
|
||||||
ensure_valid_address_lists
|
ensure_valid_address_lists
|
||||||
@from_email, @from_display_name = parse_from_field(@mail)
|
@from_email, @from_display_name = parse_from_field(@mail)
|
||||||
|
@from_user = User.find_by_email(@from_email)
|
||||||
@incoming_email = create_incoming_email
|
@incoming_email = create_incoming_email
|
||||||
process_internal
|
process_internal
|
||||||
|
raise BouncedEmailError if is_bounce?
|
||||||
rescue => e
|
rescue => e
|
||||||
error = e.to_s
|
error = e.to_s
|
||||||
error = e.class.name if error.blank?
|
error = e.class.name if error.blank?
|
||||||
@ -109,14 +111,14 @@ module Email
|
|||||||
end
|
end
|
||||||
|
|
||||||
def process_internal
|
def process_internal
|
||||||
raise BouncedEmailError if is_bounce?
|
handle_bounce if is_bounce?
|
||||||
raise NoSenderDetectedError if @from_email.blank?
|
raise NoSenderDetectedError if @from_email.blank?
|
||||||
raise FromReplyByAddressError if is_from_reply_by_email_address?
|
raise FromReplyByAddressError if is_from_reply_by_email_address?
|
||||||
raise ScreenedEmailError if ScreenedEmail.should_block?(@from_email)
|
raise ScreenedEmailError if ScreenedEmail.should_block?(@from_email)
|
||||||
|
|
||||||
hidden_reason_id = is_spam? ? Post.hidden_reasons[:email_spam_header_found] : nil
|
hidden_reason_id = is_spam? ? Post.hidden_reasons[:email_spam_header_found] : nil
|
||||||
|
|
||||||
user = find_user(@from_email)
|
user = @from_user
|
||||||
|
|
||||||
if user.present?
|
if user.present?
|
||||||
log_and_validate_user(user)
|
log_and_validate_user(user)
|
||||||
@ -132,7 +134,7 @@ module Email
|
|||||||
if is_auto_generated? && !sent_to_mailinglist_mirror?
|
if is_auto_generated? && !sent_to_mailinglist_mirror?
|
||||||
@incoming_email.update_columns(is_auto_generated: true)
|
@incoming_email.update_columns(is_auto_generated: true)
|
||||||
|
|
||||||
if SiteSetting.block_auto_generated_emails?
|
if SiteSetting.block_auto_generated_emails? && !is_bounce?
|
||||||
raise AutoGeneratedEmailError
|
raise AutoGeneratedEmailError
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -157,17 +159,16 @@ module Email
|
|||||||
hidden_reason_id: hidden_reason_id,
|
hidden_reason_id: hidden_reason_id,
|
||||||
post: post,
|
post: post,
|
||||||
topic: post.topic,
|
topic: post.topic,
|
||||||
skip_validations: user.staged?)
|
skip_validations: user.staged?,
|
||||||
|
bounce: is_bounce?)
|
||||||
else
|
else
|
||||||
first_exception = nil
|
first_exception = nil
|
||||||
|
|
||||||
destinations.each do |destination|
|
destinations.each do |destination|
|
||||||
begin
|
begin
|
||||||
process_destination(destination, user, body, elided, hidden_reason_id)
|
return process_destination(destination, user, body, elided, hidden_reason_id)
|
||||||
rescue => e
|
rescue => e
|
||||||
first_exception ||= e
|
first_exception ||= e
|
||||||
else
|
|
||||||
return
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -195,16 +196,21 @@ module Email
|
|||||||
end
|
end
|
||||||
|
|
||||||
def is_bounce?
|
def is_bounce?
|
||||||
return false unless @mail.bounced? || verp
|
@mail.bounced? || verp
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_bounce
|
||||||
@incoming_email.update_columns(is_bounce: true)
|
@incoming_email.update_columns(is_bounce: true)
|
||||||
|
|
||||||
if verp && (bounce_key = verp[/\+verp-(\h{32})@/, 1]) && (email_log = EmailLog.find_by(bounce_key: bounce_key))
|
if verp && (bounce_key = verp[/\+verp-(\h{32})@/, 1]) && (email_log = EmailLog.find_by(bounce_key: bounce_key))
|
||||||
email_log.update_columns(bounced: true)
|
email_log.update_columns(bounced: true)
|
||||||
email = email_log.user.try(:email).presence
|
user = email_log.user
|
||||||
|
email = user&.email.presence
|
||||||
|
topic = email_log.topic
|
||||||
end
|
end
|
||||||
|
|
||||||
email ||= @from_email
|
email ||= @from_email
|
||||||
|
user ||= @from_user
|
||||||
|
|
||||||
if @mail.error_status.present? && Array.wrap(@mail.error_status).any? { |s| s.start_with?("4.") }
|
if @mail.error_status.present? && Array.wrap(@mail.error_status).any? { |s| s.start_with?("4.") }
|
||||||
Email::Receiver.update_bounce_score(email, SiteSetting.soft_bounce_score)
|
Email::Receiver.update_bounce_score(email, SiteSetting.soft_bounce_score)
|
||||||
@ -212,7 +218,11 @@ module Email
|
|||||||
Email::Receiver.update_bounce_score(email, SiteSetting.hard_bounce_score)
|
Email::Receiver.update_bounce_score(email, SiteSetting.hard_bounce_score)
|
||||||
end
|
end
|
||||||
|
|
||||||
true
|
return if SiteSetting.enable_whispers? &&
|
||||||
|
user&.staged? &&
|
||||||
|
(topic.blank? || topic.archetype == Archetype.private_message)
|
||||||
|
|
||||||
|
raise BouncedEmailError
|
||||||
end
|
end
|
||||||
|
|
||||||
def is_from_reply_by_email_address?
|
def is_from_reply_by_email_address?
|
||||||
@ -444,6 +454,12 @@ module Email
|
|||||||
end
|
end
|
||||||
|
|
||||||
def parse_from_field(mail)
|
def parse_from_field(mail)
|
||||||
|
if is_bounce?
|
||||||
|
Array.wrap(mail.final_recipient).each do |from|
|
||||||
|
return extract_from_address_and_name(from)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
return unless mail[:from]
|
return unless mail[:from]
|
||||||
|
|
||||||
if mail[:from].errors.blank?
|
if mail[:from].errors.blank?
|
||||||
@ -470,6 +486,11 @@ module Email
|
|||||||
end
|
end
|
||||||
|
|
||||||
def extract_from_address_and_name(value)
|
def extract_from_address_and_name(value)
|
||||||
|
if value[";"]
|
||||||
|
from_display_name, from_address = value.split(";")
|
||||||
|
return [from_address&.strip&.downcase, from_display_name&.strip]
|
||||||
|
end
|
||||||
|
|
||||||
if value[/<[^>]+>/]
|
if value[/<[^>]+>/]
|
||||||
from_address = value[/<([^>]+)>/, 1]
|
from_address = value[/<([^>]+)>/, 1]
|
||||||
from_display_name = value[/^([^<]+)/, 1]
|
from_display_name = value[/^([^<]+)/, 1]
|
||||||
@ -492,10 +513,6 @@ module Email
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def find_user(email)
|
|
||||||
User.find_by_email(email)
|
|
||||||
end
|
|
||||||
|
|
||||||
def find_or_create_user(email, display_name, raise_on_failed_create: false)
|
def find_or_create_user(email, display_name, raise_on_failed_create: false)
|
||||||
user = nil
|
user = nil
|
||||||
|
|
||||||
@ -582,6 +599,8 @@ module Email
|
|||||||
has_been_forwarded? &&
|
has_been_forwarded? &&
|
||||||
process_forwarded_email(destination, user)
|
process_forwarded_email(destination, user)
|
||||||
|
|
||||||
|
return if is_bounce? && destination[:type] != :reply
|
||||||
|
|
||||||
case destination[:type]
|
case destination[:type]
|
||||||
when :group
|
when :group
|
||||||
group = destination[:obj]
|
group = destination[:obj]
|
||||||
@ -616,7 +635,8 @@ module Email
|
|||||||
hidden_reason_id: hidden_reason_id,
|
hidden_reason_id: hidden_reason_id,
|
||||||
post: post,
|
post: post,
|
||||||
topic: post&.topic,
|
topic: post&.topic,
|
||||||
skip_validations: user.staged?)
|
skip_validations: user.staged?,
|
||||||
|
bounce: is_bounce?)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -852,6 +872,8 @@ module Email
|
|||||||
|
|
||||||
def create_reply(options = {})
|
def create_reply(options = {})
|
||||||
raise TopicNotFoundError if options[:topic].nil? || options[:topic].trashed?
|
raise TopicNotFoundError if options[:topic].nil? || options[:topic].trashed?
|
||||||
|
raise BouncedEmailError if options[:bounce] && options[:topic].archetype != Archetype.private_message
|
||||||
|
|
||||||
options[:post] = nil if options[:post]&.trashed?
|
options[:post] = nil if options[:post]&.trashed?
|
||||||
enable_email_pm_setting(options[:user]) if options[:topic].archetype == Archetype.private_message
|
enable_email_pm_setting(options[:user]) if options[:topic].archetype == Archetype.private_message
|
||||||
|
|
||||||
@ -985,6 +1007,13 @@ module Email
|
|||||||
end
|
end
|
||||||
|
|
||||||
user = options.delete(:user)
|
user = options.delete(:user)
|
||||||
|
|
||||||
|
if options[:bounce]
|
||||||
|
options[:raw] = I18n.t("system_messages.email_bounced", email: user.email, raw: options[:raw])
|
||||||
|
user = Discourse.system_user
|
||||||
|
options[:post_type] = Post.types[:whisper]
|
||||||
|
end
|
||||||
|
|
||||||
result = NewPostManager.new(user, options).perform
|
result = NewPostManager.new(user, options).perform
|
||||||
|
|
||||||
errors = result.errors.full_messages
|
errors = result.errors.full_messages
|
||||||
|
@ -103,7 +103,8 @@ describe Email::Receiver do
|
|||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "raises a BouncerEmailError when email is a bounced email" do
|
context "bounces" do
|
||||||
|
it "raises a BouncerEmailError" do
|
||||||
expect { process(:bounced_email) }.to raise_error(Email::Receiver::BouncedEmailError)
|
expect { process(:bounced_email) }.to raise_error(Email::Receiver::BouncedEmailError)
|
||||||
expect(IncomingEmail.last.is_bounce).to eq(true)
|
expect(IncomingEmail.last.is_bounce).to eq(true)
|
||||||
|
|
||||||
@ -111,6 +112,33 @@ describe Email::Receiver do
|
|||||||
expect(IncomingEmail.last.is_bounce).to eq(true)
|
expect(IncomingEmail.last.is_bounce).to eq(true)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "creates a whisper post in PM if user is staged" do
|
||||||
|
SiteSetting.enable_staged_users = true
|
||||||
|
SiteSetting.enable_whispers = true
|
||||||
|
|
||||||
|
email = "linux-admin@b-s-c.co.jp"
|
||||||
|
user = Fabricate(:staged, email: email)
|
||||||
|
private_message = Fabricate(:topic, archetype: 'private_message', category_id: nil, user: user)
|
||||||
|
private_message.allowed_users = [user]
|
||||||
|
private_message.save!
|
||||||
|
post = create_post(topic: private_message, user: user)
|
||||||
|
|
||||||
|
post_reply_key = begin
|
||||||
|
Fabricate(:post_reply_key,
|
||||||
|
reply_key: "4f97315cc828096c9cb34c6f1a0d6fe8",
|
||||||
|
user: user,
|
||||||
|
post: post
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
expect { process(:bounced_email) }.to raise_error(Email::Receiver::BouncedEmailError)
|
||||||
|
post = Post.last
|
||||||
|
expect(post.whisper?).to eq(true)
|
||||||
|
expect(post.raw).to eq(I18n.t("system_messages.email_bounced", email: email, raw: "Your email bounced").strip)
|
||||||
|
expect(IncomingEmail.last.is_bounce).to eq(true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
it "logs a blank error" do
|
it "logs a blank error" do
|
||||||
Email::Receiver.any_instance.stubs(:process_internal).raises(RuntimeError, "")
|
Email::Receiver.any_instance.stubs(:process_internal).raises(RuntimeError, "")
|
||||||
process(:existing_user) rescue RuntimeError
|
process(:existing_user) rescue RuntimeError
|
||||||
|
2
spec/fixtures/emails/bounced_email.eml
vendored
2
spec/fixtures/emails/bounced_email.eml
vendored
@ -2,7 +2,7 @@ Delivered-To: someguy@discourse.org
|
|||||||
Date: Thu, 7 Apr 2016 19:04:30 +0900 (JST)
|
Date: Thu, 7 Apr 2016 19:04:30 +0900 (JST)
|
||||||
From: MAILER-DAEMON@b-s-c.co.jp (Mail Delivery System)
|
From: MAILER-DAEMON@b-s-c.co.jp (Mail Delivery System)
|
||||||
Subject: Undelivered Mail Returned to Sender
|
Subject: Undelivered Mail Returned to Sender
|
||||||
To: someguy@discourse.org
|
To: reply+4f97315cc828096c9cb34c6f1a0d6fe8@bar.com
|
||||||
MIME-Version: 1.0
|
MIME-Version: 1.0
|
||||||
Content-Type: multipart/report; report-type=delivery-status;
|
Content-Type: multipart/report; report-type=delivery-status;
|
||||||
boundary="18F5D18A0075.1460023470/some@daemon.com"
|
boundary="18F5D18A0075.1460023470/some@daemon.com"
|
||||||
|
Loading…
Reference in New Issue
Block a user