FEATURE: Log the SMTP response in EmailLog (#17056)

When sending emails with delivery_method_options -> return_response
set to true, the SMTP sending code inside Mail will return the SMTP
response when calling deliver! for mail within the app. This commit
ensures that Email::Sender captures this response if it is returned
and stores it against the EmailLog created for the sent email.

A follow up PR will make this visible within the admin email UI.
This commit is contained in:
Martin Brennan
2022-06-15 10:28:30 +10:00
committed by GitHub
parent 63df2b4550
commit 4d3c1ceb44
6 changed files with 61 additions and 32 deletions

View File

@@ -16,7 +16,8 @@ class GroupSmtpMailer < ActionMailer::Base
user_name: from_group.email_username, user_name: from_group.email_username,
password: from_group.email_password, password: from_group.email_password,
authentication: GlobalSetting.smtp_authentication, authentication: GlobalSetting.smtp_authentication,
enable_starttls_auto: from_group.smtp_ssl enable_starttls_auto: from_group.smtp_ssl,
return_response: true
} }
group_name = from_group.name_full_preferred group_name = from_group.name_full_preferred

View File

@@ -127,22 +127,23 @@ end
# #
# Table name: email_logs # Table name: email_logs
# #
# id :integer not null, primary key # id :integer not null, primary key
# to_address :string not null # to_address :string not null
# email_type :string not null # email_type :string not null
# user_id :integer # user_id :integer
# created_at :datetime not null # created_at :datetime not null
# updated_at :datetime not null # updated_at :datetime not null
# post_id :integer # post_id :integer
# bounce_key :uuid # bounce_key :uuid
# bounced :boolean default(FALSE), not null # bounced :boolean default(FALSE), not null
# message_id :string # message_id :string
# smtp_group_id :integer # smtp_group_id :integer
# cc_addresses :text # cc_addresses :text
# cc_user_ids :integer is an Array # cc_user_ids :integer is an Array
# raw :text # raw :text
# topic_id :integer # topic_id :integer
# bounce_error_code :string # bounce_error_code :string
# smtp_transaction_response :string(500)
# #
# Indexes # Indexes
# #

View File

@@ -0,0 +1,7 @@
# frozen_string_literal: true
class AddSmtpTransactionResponseToEmailLogs < ActiveRecord::Migration[7.0]
def change
add_column :email_logs, :smtp_transaction_response, :string, null: true, limit: 500
end
end

View File

@@ -145,6 +145,7 @@ module Email
} }
args[:delivery_method_options] = @opts[:delivery_method_options] if @opts[:delivery_method_options] args[:delivery_method_options] = @opts[:delivery_method_options] if @opts[:delivery_method_options]
args[:delivery_method_options] = (args[:delivery_method_options] || {}).merge(return_response: true)
args args
end end

View File

@@ -315,7 +315,13 @@ module Email
DiscourseEvent.trigger(:before_email_send, @message, @email_type) DiscourseEvent.trigger(:before_email_send, @message, @email_type)
begin begin
@message.deliver_now message_response = @message.deliver!
# TestMailer from the Mail gem does not return a real response, it
# returns an array containing @message, so we have to have this workaround.
if message_response.kind_of?(Net::SMTP::Response)
email_log.smtp_transaction_response = message_response.message&.chomp
end
rescue *SMTP_CLIENT_ERRORS => e rescue *SMTP_CLIENT_ERRORS => e
return skip(SkippedEmailLog.reason_types[:custom], custom_reason: e.message) return skip(SkippedEmailLog.reason_types[:custom], custom_reason: e.message)
end end

View File

@@ -7,6 +7,13 @@ describe Email::Sender do
SiteSetting.secure_media_allow_embed_images_in_emails = false SiteSetting.secure_media_allow_embed_images_in_emails = false
end end
fab!(:post) { Fabricate(:post) } fab!(:post) { Fabricate(:post) }
let(:mock_smtp_transaction_response) { "250 Ok: queued as 2l3Md07BObzB8kRyHZeoN0baSUAhzc7A-NviRioOr80=@mailhog.example" }
def stub_deliver_response(message)
message.stubs(:deliver!).returns(
Net::SMTP::Response.new("250", mock_smtp_transaction_response)
)
end
context "disable_emails is enabled" do context "disable_emails is enabled" do
fab!(:user) { Fabricate(:user) } fab!(:user) { Fabricate(:user) }
@@ -41,19 +48,20 @@ describe Email::Sender do
before { SiteSetting.disable_emails = "non-staff" } before { SiteSetting.disable_emails = "non-staff" }
it "doesn't deliver mail to normal user" do it "doesn't deliver mail to normal user" do
Mail::Message.any_instance.expects(:deliver_now).never Mail::Message.any_instance.expects(:deliver!).never
message = Mail::Message.new(to: user.email, body: "hello") message = Mail::Message.new(to: user.email, body: "hello")
stub_deliver_response(message)
expect(Email::Sender.new(message, :hello).send).to eq(nil) expect(Email::Sender.new(message, :hello).send).to eq(nil)
end end
it "delivers mail to staff user" do it "delivers mail to staff user" do
Mail::Message.any_instance.expects(:deliver_now).once Mail::Message.any_instance.expects(:deliver!).once
message = Mail::Message.new(to: moderator.email, body: "hello") message = Mail::Message.new(to: moderator.email, body: "hello")
Email::Sender.new(message, :hello).send Email::Sender.new(message, :hello).send
end end
it "delivers mail to staff user when confirming new email if user is provided" do it "delivers mail to staff user when confirming new email if user is provided" do
Mail::Message.any_instance.expects(:deliver_now).once Mail::Message.any_instance.expects(:deliver!).once
Fabricate(:email_change_request, { Fabricate(:email_change_request, {
user: moderator, user: moderator,
new_email: "newemail@testmoderator.com", new_email: "newemail@testmoderator.com",
@@ -69,32 +77,32 @@ describe Email::Sender do
end end
it "doesn't deliver mail when the message is of type NullMail" do it "doesn't deliver mail when the message is of type NullMail" do
Mail::Message.any_instance.expects(:deliver_now).never Mail::Message.any_instance.expects(:deliver!).never
message = ActionMailer::Base::NullMail.new message = ActionMailer::Base::NullMail.new
expect(Email::Sender.new(message, :hello).send).to eq(nil) expect(Email::Sender.new(message, :hello).send).to eq(nil)
end end
it "doesn't deliver mail when the message is nil" do it "doesn't deliver mail when the message is nil" do
Mail::Message.any_instance.expects(:deliver_now).never Mail::Message.any_instance.expects(:deliver!).never
Email::Sender.new(nil, :hello).send Email::Sender.new(nil, :hello).send
end end
it "doesn't deliver when the to address is nil" do it "doesn't deliver when the to address is nil" do
message = Mail::Message.new(body: 'hello') message = Mail::Message.new(body: 'hello')
message.expects(:deliver_now).never message.expects(:deliver!).never
Email::Sender.new(message, :hello).send Email::Sender.new(message, :hello).send
end end
it "doesn't deliver when the to address uses the .invalid tld" do it "doesn't deliver when the to address uses the .invalid tld" do
message = Mail::Message.new(body: 'hello', to: 'myemail@example.invalid') message = Mail::Message.new(body: 'hello', to: 'myemail@example.invalid')
message.expects(:deliver_now).never message.expects(:deliver!).never
expect { Email::Sender.new(message, :hello).send }. expect { Email::Sender.new(message, :hello).send }.
to change { SkippedEmailLog.where(reason_type: SkippedEmailLog.reason_types[:sender_message_to_invalid]).count }.by(1) to change { SkippedEmailLog.where(reason_type: SkippedEmailLog.reason_types[:sender_message_to_invalid]).count }.by(1)
end end
it "doesn't deliver when the body is nil" do it "doesn't deliver when the body is nil" do
message = Mail::Message.new(to: 'eviltrout@test.domain') message = Mail::Message.new(to: 'eviltrout@test.domain')
message.expects(:deliver_now).never message.expects(:deliver!).never
Email::Sender.new(message, :hello).send Email::Sender.new(message, :hello).send
end end
@@ -126,14 +134,14 @@ describe Email::Sender do
to: 'eviltrout@test.domain', to: 'eviltrout@test.domain',
body: '**hello**' body: '**hello**'
) )
message.stubs(:deliver_now) stub_deliver_response(message)
message message
end end
let(:email_sender) { Email::Sender.new(message, :valid_type) } let(:email_sender) { Email::Sender.new(message, :valid_type) }
it 'calls deliver' do it 'calls deliver' do
message.expects(:deliver_now).once message.expects(:deliver!).once
email_sender.send email_sender.send
end end
@@ -414,6 +422,7 @@ describe Email::Sender do
before do before do
SiteSetting.enable_smtp = true SiteSetting.enable_smtp = true
stub_deliver_response(message)
end end
it 'adds the group id and raw content to the email log' do it 'adds the group id and raw content to the email log' do
@@ -684,7 +693,7 @@ describe Email::Sender do
message = Mail::Message.new to: 'disc@ourse.org', body: 'some content' message = Mail::Message.new to: 'disc@ourse.org', body: 'some content'
message.header['X-Discourse-Post-Id'] = post.id message.header['X-Discourse-Post-Id'] = post.id
message.header['X-Discourse-Topic-Id'] = post.topic_id message.header['X-Discourse-Topic-Id'] = post.topic_id
message.expects(:deliver_now).never message.expects(:deliver!).never
email_sender = Email::Sender.new(message, :valid_type) email_sender = Email::Sender.new(message, :valid_type)
expect { email_sender.send }.to change { SkippedEmailLog.count } expect { email_sender.send }.to change { SkippedEmailLog.count }
@@ -703,7 +712,7 @@ describe Email::Sender do
message = Mail::Message.new to: 'disc@ourse.org', body: 'some content' message = Mail::Message.new to: 'disc@ourse.org', body: 'some content'
message.header['X-Discourse-Post-Id'] = post.id message.header['X-Discourse-Post-Id'] = post.id
message.header['X-Discourse-Topic-Id'] = post.topic_id message.header['X-Discourse-Topic-Id'] = post.topic_id
message.expects(:deliver_now).never message.expects(:deliver!).never
email_sender = Email::Sender.new(message, :valid_type) email_sender = Email::Sender.new(message, :valid_type)
expect { email_sender.send }.to change { SkippedEmailLog.count } expect { email_sender.send }.to change { SkippedEmailLog.count }
@@ -717,7 +726,7 @@ describe Email::Sender do
context 'with a user' do context 'with a user' do
let(:message) do let(:message) do
message = Mail::Message.new to: 'eviltrout@test.domain', body: 'test body' message = Mail::Message.new to: 'eviltrout@test.domain', body: 'test body'
message.stubs(:deliver_now) stub_deliver_response(message)
message message
end end
@@ -733,6 +742,10 @@ describe Email::Sender do
expect(@email_log.user_id).to eq(user.id) expect(@email_log.user_id).to eq(user.id)
end end
it 'should have the smtp_transaction_response message' do
expect(@email_log.smtp_transaction_response).to eq(mock_smtp_transaction_response)
end
describe "post reply keys" do describe "post reply keys" do
fab!(:post) { Fabricate(:post) } fab!(:post) { Fabricate(:post) }
@@ -782,7 +795,7 @@ describe Email::Sender do
context "with cc addresses" do context "with cc addresses" do
let(:message) do let(:message) do
message = Mail::Message.new to: 'eviltrout@test.domain', body: 'test body', cc: 'someguy@test.com;otherguy@xyz.com' message = Mail::Message.new to: 'eviltrout@test.domain', body: 'test body', cc: 'someguy@test.com;otherguy@xyz.com'
message.stubs(:deliver_now) stub_deliver_response(message)
message message
end end