From 759bc1823fb10070c2b3696153158cd0f9137527 Mon Sep 17 00:00:00 2001 From: Mae Beale Date: Sun, 30 Aug 2026 09:07:59 -0400 Subject: [PATCH] Email scholarship recipients a confirmation when they accept MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Main already sent the trainings team an FYI on every agreement response, but the recipient had only the on-page notice — no record their acceptance went through. Send them a confirmation with the award amount and a ticket link, reusing ScholarshipMailer's existing conventions. Co-Authored-By: Claude Opus 4.8 (1M context) --- app/controllers/events/callouts_controller.rb | 5 +++- app/mailers/scholarship_mailer.rb | 9 +++++++ .../accepted_confirmation.html.erb | 24 +++++++++++++++++ .../accepted_confirmation.text.erb | 10 +++++++ config/features.yml | 12 +++++++++ spec/mailers/scholarship_mailer_spec.rb | 27 +++++++++++++++++++ spec/requests/events/callouts_spec.rb | 5 ++-- .../previews/scholarship_mailer_preview.rb | 7 +++++ 8 files changed, 96 insertions(+), 3 deletions(-) create mode 100644 app/views/scholarship_mailer/accepted_confirmation.html.erb create mode 100644 app/views/scholarship_mailer/accepted_confirmation.text.erb diff --git a/app/controllers/events/callouts_controller.rb b/app/controllers/events/callouts_controller.rb index 2490afa9d5..a72c6b3058 100644 --- a/app/controllers/events/callouts_controller.rb +++ b/app/controllers/events/callouts_controller.rb @@ -67,7 +67,10 @@ def sign_agreement if params[:agreement] == "yes" newly_accepted = !scholarship.agreement_signed? scholarship.accept_agreement!(by: "recipient") - ScholarshipMailer.accepted_fyi(scholarship).deliver_later if newly_accepted + if newly_accepted + ScholarshipMailer.accepted_fyi(scholarship).deliver_later + ScholarshipMailer.accepted_confirmation(scholarship).deliver_later + end redirect_to registration_scholarship_path(@event_registration.slug), notice: "Thanks — your agreement has been recorded." else redirect_to registration_scholarship_path(@event_registration.slug), alert: "Something went wrong recording your agreement. Please try again." diff --git a/app/mailers/scholarship_mailer.rb b/app/mailers/scholarship_mailer.rb index 4e6f98076e..b9792972a5 100644 --- a/app/mailers/scholarship_mailer.rb +++ b/app/mailers/scholarship_mailer.rb @@ -20,6 +20,15 @@ def accepted_fyi(scholarship) mail(subject: fyi_subject("Scholarship accepted")) end + # Confirmation to the recipient that their acceptance was recorded, with the + # award amount and a link back to their ticket. Sent to them, not the team. + def accepted_confirmation(scholarship) + assign_agreement(scholarship) + registration = scholarship.allocation&.allocatable + @ticket_url = registration_ticket_url(registration.slug) if registration.is_a?(EventRegistration) + mail(to: @recipient&.preferred_email, subject: "AWBW Portal: Your scholarship agreement is confirmed") + end + private def assign_agreement(scholarship) diff --git a/app/views/scholarship_mailer/accepted_confirmation.html.erb b/app/views/scholarship_mailer/accepted_confirmation.html.erb new file mode 100644 index 0000000000..86ab1746ed --- /dev/null +++ b/app/views/scholarship_mailer/accepted_confirmation.html.erb @@ -0,0 +1,24 @@ +

Your scholarship agreement is confirmed

+ +

+ Thank you<% if @recipient&.first_name.present? %>, <%= @recipient.first_name %><% end %> — we've recorded your agreement + to complete your scholarship tasks<% if @event.present? %> for <%= @event.title %><% end %>. +

+ +
+

Award confirmed

+

<%= dollars_from_cents(@scholarship.amount_cents) %>

+
+ +<% if @ticket_url.present? %> +

+ + View your ticket + +

+

+ Your ticket has your award details, tasks, and next steps. +

+<% end %> diff --git a/app/views/scholarship_mailer/accepted_confirmation.text.erb b/app/views/scholarship_mailer/accepted_confirmation.text.erb new file mode 100644 index 0000000000..c893ae2ef6 --- /dev/null +++ b/app/views/scholarship_mailer/accepted_confirmation.text.erb @@ -0,0 +1,10 @@ +Your scholarship agreement is confirmed +======================================= + +Thank you<% if @recipient&.first_name.present? %>, <%= @recipient.first_name %><% end %> — we've recorded your agreement to complete your scholarship tasks<% if @event.present? %> for <%= @event.title %><% end %>. + +Award confirmed: <%= dollars_from_cents(@scholarship.amount_cents) %> + +<% if @ticket_url.present? %>View your ticket (award details, tasks, and next steps): +<%= @ticket_url %> +<% end %> diff --git a/config/features.yml b/config/features.yml index ac5b68a043..73f53ac2a0 100644 --- a/config/features.yml +++ b/config/features.yml @@ -27,6 +27,18 @@ # ── 2026-08 ───────────────────────────────────────────────────────────────── +- name: "Scholarship recipients get an agreement confirmation email" + area: scholarships + display_status: public_facing + released_on: 2026-08-30 + action_path: /registration/sample + summary: >- + When a recipient accepts their scholarship agreement, they now get a confirmation + email with the award amount and a link back to their ticket — so they have a record + that their acceptance went through, not just the on-page notice. + pro_tips: + - The trainings team still gets its own heads-up on every accept, decline, or support request. + - name: "Navigate a form's pages, and fix the forms list actions" area: content display_status: admin_facing diff --git a/spec/mailers/scholarship_mailer_spec.rb b/spec/mailers/scholarship_mailer_spec.rb index 9ab03fe683..4cc22cd05e 100644 --- a/spec/mailers/scholarship_mailer_spec.rb +++ b/spec/mailers/scholarship_mailer_spec.rb @@ -64,4 +64,31 @@ expect(mail.subject).to include(scholarship.recipient.full_name) end end + + describe "#accepted_confirmation" do + let(:event) { create(:event, cost_cents: 10_000, title: "Healing Circle Training") } + let(:registration) { create(:event_registration, event:) } + let(:scholarship) { create(:scholarship, recipient: registration.registrant, amount_cents: 5_000) } + let(:mail) { described_class.accepted_confirmation(scholarship) } + + before do + create(:allocation, source: scholarship, allocatable: registration, amount: 5_000) + scholarship.reload.accept_agreement! + end + + it "renders without raising" do + expect { mail.deliver_now }.not_to raise_error + end + + it "sends to the recipient, not the trainings/programs team" do + expect(mail.to).to eq([ scholarship.recipient.preferred_email ]) + end + + it "confirms the agreement in the subject and shows the award and ticket link" do + expect(mail.subject).to include("Your scholarship agreement is confirmed") + body = mail.body.encoded + expect(body).to include("$50") + expect(body).to include(registration.slug) + end + end end diff --git a/spec/requests/events/callouts_spec.rb b/spec/requests/events/callouts_spec.rb index 2fb21dad1e..2849c46c9e 100644 --- a/spec/requests/events/callouts_spec.rb +++ b/spec/requests/events/callouts_spec.rb @@ -722,14 +722,15 @@ expect(scholarship.reload.agreement_signed?).to be(false) end - it "emails the trainings team on a fresh acceptance, but not on a repeat" do + it "emails the trainings team and the recipient on a fresh acceptance, but not on a repeat" do expect { post registration_scholarship_agreement_path(registration.slug), params: { agreement: "yes" } }.to have_enqueued_mail(ScholarshipMailer, :accepted_fyi) + .and have_enqueued_mail(ScholarshipMailer, :accepted_confirmation) expect { post registration_scholarship_agreement_path(registration.slug), params: { agreement: "yes" } - }.not_to have_enqueued_mail(ScholarshipMailer, :accepted_fyi) + }.not_to have_enqueued_mail(ScholarshipMailer, :accepted_confirmation) end it "redirects to the scholarship page when there is no awarded scholarship" do diff --git a/test/mailers/previews/scholarship_mailer_preview.rb b/test/mailers/previews/scholarship_mailer_preview.rb index a0f6713052..e9fd7c2189 100644 --- a/test/mailers/previews/scholarship_mailer_preview.rb +++ b/test/mailers/previews/scholarship_mailer_preview.rb @@ -22,6 +22,13 @@ def accepted_fyi ScholarshipMailer.accepted_fyi(scholarship) end + def accepted_confirmation + scholarship = a_scholarship + scholarship.accept_agreement! unless scholarship.agreement_signed? + + ScholarshipMailer.accepted_confirmation(scholarship) + end + private def a_scholarship