Skip to content

Commit 573afac

Browse files
Merge pull request #1929 from alphagov/theseanything/revert-revert-make-confirmation-emails-async
Reintroduce making sending confirmation emails async
2 parents f0302f0 + 390a0b4 commit 573afac

File tree

7 files changed

+211
-59
lines changed

7 files changed

+211
-59
lines changed
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
class SendConfirmationEmailJob < ApplicationJob
2+
queue_as :confirmation_emails
3+
MailerOptions = Data.define(:title, :is_preview, :timestamp, :submission_reference, :payment_url)
4+
5+
def perform(submission:, notify_response_id:, confirmation_email_address:)
6+
set_submission_logging_attributes(submission:)
7+
8+
I18n.with_locale(submission.submission_locale || I18n.default_locale) do
9+
form = submission.form
10+
mail = FormSubmissionConfirmationMailer.send_confirmation_email(
11+
what_happens_next_markdown: form.what_happens_next_markdown,
12+
support_contact_details: form.support_details,
13+
notify_response_id:,
14+
confirmation_email_address:,
15+
mailer_options: mailer_options_for(submission:, form:),
16+
)
17+
18+
mail.deliver_now
19+
CurrentJobLoggingAttributes.confirmation_email_id = mail.govuk_notify_response.id
20+
end
21+
rescue StandardError
22+
CloudWatchService.record_job_failure_metric(self.class.name)
23+
raise
24+
end
25+
26+
private
27+
28+
def mailer_options_for(submission:, form:)
29+
MailerOptions.new(
30+
title: form.name,
31+
is_preview: submission.preview?,
32+
timestamp: submission.submission_time,
33+
submission_reference: submission.reference,
34+
payment_url: submission.payment_url,
35+
)
36+
end
37+
end

app/models/current_job_logging_attributes.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ class CurrentJobLoggingAttributes < ActiveSupport::CurrentAttributes
77
:preview,
88
:delivery_id,
99
:delivery_reference,
10+
:confirmation_email_id,
1011
:sqs_message_id,
1112
:sns_message_timestamp
1213

@@ -20,6 +21,7 @@ def as_hash
2021
preview: preview.to_s,
2122
delivery_id:,
2223
delivery_reference:,
24+
confirmation_email_id:,
2325
sqs_message_id:,
2426
sns_message_timestamp:,
2527
}.compact_blank

app/models/current_request_logging_attributes.rb

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ class CurrentRequestLoggingAttributes < ActiveSupport::CurrentAttributes
1212
:question_number,
1313
:submission_reference,
1414
:confirmation_email_reference,
15-
:confirmation_email_id,
1615
:rescued_exception,
1716
:rescued_exception_trace,
1817
:validation_errors,
@@ -33,7 +32,6 @@ def as_hash
3332
question_number:,
3433
submission_reference:,
3534
confirmation_email_reference:,
36-
confirmation_email_id:,
3735
rescued_exception:,
3836
rescued_exception_trace:,
3937
validation_errors:,

app/services/form_submission_service.rb

Lines changed: 36 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,12 @@ def initialize(current_context:, email_confirmation_input:, mode:)
2424

2525
def submit
2626
ensure_form_english
27+
2728
validate_submission
29+
validate_confirmation_email_address if requested_confirmation?
2830

29-
confirmation_mail = setup_confirmation_email if requested_confirmation?
30-
deliver_submission
31-
send_confirmation_email(confirmation_mail) if confirmation_mail.present?
31+
submission = deliver_submission
32+
enqueue_send_confirmation_email_job(submission:) if requested_confirmation?
3233

3334
submission_reference
3435
end
@@ -58,14 +59,15 @@ def validate_submission
5859
end
5960

6061
def deliver_submission
61-
case form.submission_type
62-
when "s3"
63-
enqueue_deliver_submission_job(SendS3SubmissionJob)
64-
when "email"
65-
enqueue_deliver_submission_job(SendSubmissionJob)
66-
else
67-
raise "unrecognized submission delivery method #{form.submission_type.inspect}"
68-
end
62+
submission =
63+
case form.submission_type
64+
when "s3"
65+
enqueue_deliver_submission_job(SendS3SubmissionJob)
66+
when "email"
67+
enqueue_deliver_submission_job(SendSubmissionJob)
68+
else
69+
raise "unrecognized submission delivery method #{form.submission_type.inspect}"
70+
end
6971

7072
LogEventService.log_submit(
7173
current_context,
@@ -74,6 +76,8 @@ def deliver_submission
7476
submission_type: form.submission_type,
7577
submission_format: form.submission_format,
7678
)
79+
80+
submission
7781
end
7882

7983
def create_submission_record
@@ -102,47 +106,38 @@ def enqueue_deliver_submission_job(job_class)
102106
message_suffix = ": #{job.enqueue_error&.message}" if job.enqueue_error
103107
raise StandardError, "Failed to enqueue submission for reference #{submission_reference}#{message_suffix}"
104108
end
109+
110+
submission
105111
end
106112

107113
def submission_timestamp
108114
time_zone = Rails.configuration.x.submission.time_zone || "UTC"
109115
Time.use_zone(time_zone) { Time.zone.now }
110116
end
111117

112-
def setup_confirmation_email
113-
mail = FormSubmissionConfirmationMailer.send_confirmation_email(
114-
what_happens_next_markdown: form.what_happens_next_markdown,
115-
support_contact_details: form.support_details,
116-
notify_response_id: email_confirmation_input.confirmation_email_reference,
117-
confirmation_email_address: email_confirmation_input.confirmation_email_address,
118-
mailer_options:,
119-
)
118+
def validate_confirmation_email_address
119+
mail = Mail.new(to: email_confirmation_input.confirmation_email_address)
120+
to_address_error = mail.errors.select { |error| error[0] == "To" }.first
121+
return unless to_address_error
120122

121-
if mail.message.errors.any?
122-
to_address_error = mail.message.errors.select { |error| error[0] == "To" }.first
123-
if to_address_error
124-
redacted_error = redact_emails_from_sentry_message(to_address_error[2].to_s)
125-
Sentry.capture_message("ActionMailer error for To email address in confirmation email", extra: {
126-
action_mailer_error: redacted_error,
127-
})
128-
raise ConfirmationEmailToAddressError
129-
end
130-
end
131-
132-
mail
123+
redacted_error = redact_emails_from_sentry_message(to_address_error[2].to_s)
124+
Sentry.capture_message("ActionMailer error for To email address in confirmation email", extra: {
125+
action_mailer_error: redacted_error,
126+
})
127+
raise ConfirmationEmailToAddressError
133128
end
134129

135-
def send_confirmation_email(mail)
136-
mail.deliver_now
137-
CurrentRequestLoggingAttributes.confirmation_email_id = mail.govuk_notify_response.id
138-
end
130+
def enqueue_send_confirmation_email_job(submission:)
131+
SendConfirmationEmailJob.perform_later(
132+
submission:,
133+
notify_response_id: email_confirmation_input.confirmation_email_reference,
134+
confirmation_email_address: email_confirmation_input.confirmation_email_address,
135+
) do |job|
136+
next if job.successfully_enqueued?
139137

140-
def mailer_options
141-
MailerOptions.new(title: form.name,
142-
is_preview: mode.preview?,
143-
timestamp: timestamp,
144-
submission_reference: submission_reference,
145-
payment_url: form.payment_url_with_reference(submission_reference))
138+
message_suffix = ": #{job.enqueue_error&.message}" if job.enqueue_error
139+
raise StandardError, "Failed to enqueue confirmation email for reference #{submission_reference}#{message_suffix}"
140+
end
146141
end
147142

148143
def requested_confirmation?
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
require "rails_helper"
2+
3+
RSpec.describe SendConfirmationEmailJob, type: :job do
4+
let(:submission_created_at) { Time.utc(2022, 12, 14, 13, 0o0, 0o0) }
5+
let(:form_document) do
6+
build(
7+
:v2_form_document,
8+
name: "Form 1",
9+
what_happens_next_markdown: "Please wait for a response",
10+
support_phone: "0203 222 2222",
11+
support_email: "help@example.gov.uk",
12+
support_url: "https://example.gov.uk/help",
13+
support_url_text: "Get help",
14+
payment_url: "https://www.gov.uk/payments/test-service/pay-for-licence",
15+
)
16+
end
17+
let(:submission) do
18+
create(
19+
:submission,
20+
form_document:,
21+
created_at: submission_created_at,
22+
reference: "ABC12345",
23+
submission_locale: "en",
24+
)
25+
end
26+
let(:notify_response_id) { "confirmation-ref" }
27+
let(:confirmation_email_address) { "testing@gov.uk" }
28+
29+
before do
30+
Settings.govuk_notify.form_filler_confirmation_email_template_id = "123456"
31+
Settings.govuk_notify.form_filler_confirmation_email_welsh_template_id = "7891011"
32+
end
33+
34+
it "sends the confirmation email" do
35+
expect {
36+
described_class.perform_now(
37+
submission:,
38+
notify_response_id:,
39+
confirmation_email_address:,
40+
)
41+
}.to change(ActionMailer::Base.deliveries, :count).by(1)
42+
43+
mail = ActionMailer::Base.deliveries.last
44+
expect(mail.to).to eq(["testing@gov.uk"])
45+
end
46+
47+
it "builds mailer arguments from the submission" do
48+
allow(FormSubmissionConfirmationMailer).to receive(:send_confirmation_email).and_call_original
49+
50+
described_class.perform_now(
51+
submission:,
52+
notify_response_id:,
53+
confirmation_email_address:,
54+
)
55+
56+
expect(FormSubmissionConfirmationMailer).to have_received(:send_confirmation_email).with(
57+
what_happens_next_markdown: "Please wait for a response",
58+
support_contact_details: have_attributes(
59+
phone: "0203 222 2222",
60+
call_charges_url: "https://www.gov.uk/call-charges",
61+
email: "help@example.gov.uk",
62+
url: "https://example.gov.uk/help",
63+
url_text: "Get help",
64+
),
65+
notify_response_id: "confirmation-ref",
66+
confirmation_email_address: "testing@gov.uk",
67+
mailer_options: an_instance_of(SendConfirmationEmailJob::MailerOptions),
68+
)
69+
end
70+
71+
context "when locale is Welsh" do
72+
it "uses the Welsh template" do
73+
submission.update!(submission_locale: "cy")
74+
described_class.perform_now(
75+
submission:,
76+
notify_response_id:,
77+
confirmation_email_address:,
78+
)
79+
80+
mail = ActionMailer::Base.deliveries.last
81+
expect(mail.govuk_notify_template).to eq("7891011")
82+
end
83+
end
84+
85+
context "when there is an error during processing" do
86+
before do
87+
allow(FormSubmissionConfirmationMailer).to receive(:send_confirmation_email).and_raise(StandardError, "Test error")
88+
allow(CloudWatchService).to receive(:record_job_failure_metric)
89+
end
90+
91+
it "raises an error" do
92+
expect {
93+
described_class.perform_now(
94+
submission:,
95+
notify_response_id:,
96+
confirmation_email_address:,
97+
)
98+
}.to raise_error(StandardError, "Test error")
99+
end
100+
101+
it "sends cloudwatch metric for failure" do
102+
described_class.perform_now(
103+
submission:,
104+
notify_response_id:,
105+
confirmation_email_address:,
106+
)
107+
expect(CloudWatchService).to have_received(:record_job_failure_metric).with("SendConfirmationEmailJob")
108+
rescue StandardError
109+
nil
110+
end
111+
end
112+
end

spec/models/current_request_logging_attributes_spec.rb

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
current.question_number = 3
2424
current.submission_reference = "a-submission-ref"
2525
current.confirmation_email_reference = "a-confirmation-email-ref"
26-
current.confirmation_email_id = "a-confirmation-email-id"
2726
current.rescued_exception = "StandardError"
2827
current.rescued_exception_trace = "a trace"
2928
current.validation_errors = ["text: blank"]
@@ -43,7 +42,6 @@
4342
question_number: 3,
4443
submission_reference: "a-submission-ref",
4544
confirmation_email_reference: "a-confirmation-email-ref",
46-
confirmation_email_id: "a-confirmation-email-id",
4745
rescued_exception: "StandardError",
4846
rescued_exception_trace: "a trace",
4947
validation_errors: ["text: blank"],
@@ -55,10 +53,6 @@
5553
expect(current.as_hash.key?(:confirmation_email_reference)).to be false
5654
end
5755

58-
it "does not include nil confirmation_email_id" do
59-
expect(current.as_hash.key?(:confirmation_email_id)).to be false
60-
end
61-
6256
it "does not include the validation errors array if empty" do
6357
current.validation_errors = []
6458
expect(current.as_hash.keys).not_to include :validation_errors

spec/services/form_submission_service_spec.rb

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -295,17 +295,31 @@
295295
end
296296

297297
describe "sending the confirmation email to the user" do
298-
it "calls FormSubmissionConfirmationMailer" do
299-
freeze_time do
300-
allow(FormSubmissionConfirmationMailer).to receive(:send_confirmation_email).and_call_original
298+
it "enqueues a job to send the confirmation email" do
299+
assert_enqueued_with(job: SendConfirmationEmailJob) do
301300
service.submit
302-
expect(FormSubmissionConfirmationMailer).to have_received(:send_confirmation_email).with(
303-
{ what_happens_next_markdown: form.what_happens_next_markdown,
304-
support_contact_details: form.support_details,
305-
notify_response_id: email_confirmation_input.confirmation_email_reference,
306-
confirmation_email_address: email_confirmation_input.confirmation_email_address,
307-
mailer_options: instance_of(FormSubmissionService::MailerOptions) },
308-
).once
301+
end
302+
end
303+
304+
context "when the confirmation email job fails to enqueue" do
305+
let(:enqueue_error) { nil }
306+
307+
before do
308+
allow(SendConfirmationEmailJob).to receive(:perform_later).and_yield(instance_double(SendConfirmationEmailJob, successfully_enqueued?: false, enqueue_error:))
309+
end
310+
311+
context "and there is no enqueue error" do
312+
it "raises an error" do
313+
expect { service.submit }.to change(Submission, :count).by(1).and raise_error(StandardError, "Failed to enqueue confirmation email for reference #{reference}")
314+
end
315+
end
316+
317+
context "and there is an enqueue error" do
318+
let(:enqueue_error) { ActiveJob::EnqueueError.new("An error occurred enqueueing job") }
319+
320+
it "raises an error" do
321+
expect { service.submit }.to change(Submission, :count).by(1).and raise_error(StandardError, "Failed to enqueue confirmation email for reference #{reference}: An error occurred enqueueing job")
322+
end
309323
end
310324
end
311325

0 commit comments

Comments
 (0)