Repository: DFE-Digital/apply-for-teacher-training
Pull request #10490: Send email after pool invite has been published
Author: login: CatalinVoineag
Labels: name: deploy_v2
description: Deploy the review app to AKS
Created: 2025-03-11T12:33:26Z | Merged: 2025-03-12T08:40:33Z

## Context

This will send an email to the candidate after the pool_invite has been
published.

We don't allow another pool_invite for the same candidate with the same
course to be published.

## Changes proposed in this pull request

New email

## Guidance to review

![Screenshot from 2025-03-11 13-50-10](https://github.com/user-attachments/assets/3e1aad44-5dfd-418a-ad42-d6673254f7e3)



## Things to check

- [ ] If the code removes any existing feature flags, a data migration has also been added to delete the entry from the database
- [ ] API release notes have been updated if necessary
- [ ] If it adds a significant user-facing change, is it documented in the [CHANGELOG](CHANGELOG.md)?
- [x] Attach the PR to the Trello card
- [ ] This code adds a column or table to the database
  - [ ] This code does not rely on migrations in the same Pull Request
  - [ ] decide whether it needs to be in analytics yml file or analytics blocklist
  - [ ] data insights team has been informed of the change and have updated the pipeline
  - [ ] the sanitise.sql script and 0025-protecting-personal-data-in-production-dump.md ADR have been updated
  - [ ] does the code safely backfill existing records for consistency



Diff:
diff --git a/app/controllers/provider_interface/candidate_pool/publish_invites_controller.rb b/app/controllers/provider_interface/candidate_pool/publish_invites_controller.rb
index 6e06b4a46e6..0372dcb0f9b 100644
--- a/app/controllers/provider_interface/candidate_pool/publish_invites_controller.rb
+++ b/app/controllers/provider_interface/candidate_pool/publish_invites_controller.rb
@@ -18,6 +18,8 @@ def create
             course: invite.course.name_code_and_course_provider,
           )
           redirect_to provider_interface_candidate_pool_root_path
+
+          CandidateMailer.course_invite(invite).deliver_later
         else
           render '/provider_interface/candidate_pool/draft_invites/edit'
         end
diff --git a/app/mailers/candidate_mailer.rb b/app/mailers/candidate_mailer.rb
index 9fdf46f7bb1..af22c43e3b1 100644
--- a/app/mailers/candidate_mailer.rb
+++ b/app/mailers/candidate_mailer.rb
@@ -513,6 +513,22 @@ def apply_to_multiple_courses_after_30_working_days(application_form)
     )
   end
 
+  def course_invite(pool_invite)
+    @application_form = pool_invite.candidate.current_cycle_application_form
+    @provider = pool_invite.provider
+    @course = pool_invite.course
+
+    email_for_candidate(
+      @application_form,
+      subject: I18n.t!(
+        'candidate_mailer.course_invite.subject',
+        provider: @provider.name,
+        course: @course.name,
+      ),
+      layout: false,
+    )
+  end
+
 private
 
   def email_for_candidate(application_form, args = {})
diff --git a/app/models/candidate.rb b/app/models/candidate.rb
index 9c040dc8151..3219572815d 100644
--- a/app/models/candidate.rb
+++ b/app/models/candidate.rb
@@ -142,6 +142,10 @@ def subscribed_to_emails?
     !unsubscribed_from_emails
   end
 
+  def current_cycle_application_form
+    application_forms.current_cycle.last
+  end
+
 private
 
   def downcase_email
diff --git a/app/views/candidate_mailer/course_invite.text.erb b/app/views/candidate_mailer/course_invite.text.erb
new file mode 100644
index 00000000000..f59945752e6
--- /dev/null
+++ b/app/views/candidate_mailer/course_invite.text.erb
@@ -0,0 +1,9 @@
+Dear <%= @application_form.first_name %>
+
+<%= @provider.name %> have viewed your application details and are inviting you to submit an application for the following course:
+
+[<%= @provider.name %> – <%= @course.name %>](<%= @course.find_url %>)
+
+View the course details and decide if you are interested in applying.
+
+If you do not want to receive invitations like this, you can [stop providers from viewing your application details](#).
diff --git a/config/locales/emails/candidate_mailer.yml b/config/locales/emails/candidate_mailer.yml
index 0fda4d93f53..029c8846718 100644
--- a/config/locales/emails/candidate_mailer.yml
+++ b/config/locales/emails/candidate_mailer.yml
@@ -1,5 +1,7 @@
 en:
   candidate_mailer:
+    course_invite:
+      subject: "%{provider} have invited you to apply to %{course}"
     reference_received:
       subject: "%{referee_name} has given you a reference"
     application_choice_submitted:
diff --git a/spec/mailers/previews/candidate_mailer_preview.rb b/spec/mailers/previews/candidate_mailer_preview.rb
index 0fac9d79874..b35babaa083 100644
--- a/spec/mailers/previews/candidate_mailer_preview.rb
+++ b/spec/mailers/previews/candidate_mailer_preview.rb
@@ -549,6 +549,18 @@ def apply_to_multiple_courses_after_30_working_days
     CandidateMailer.apply_to_multiple_courses_after_30_working_days(application_form)
   end
 
+  def course_invite
+    application_form = FactoryBot.create(
+      :application_form,
+      :minimum_info,
+      first_name: 'Fred',
+    )
+    candidate = FactoryBot.create(:candidate, application_forms: [application_form])
+    pool_invite = FactoryBot.create(:pool_invite, candidate:)
+
+    CandidateMailer.course_invite(pool_invite)
+  end
+
 private
 
   def candidate
diff --git a/spec/requests/provider_interface/candidate_pool/publish_invites_controller_spec.rb b/spec/requests/provider_interface/candidate_pool/publish_invites_controller_spec.rb
new file mode 100644
index 00000000000..d1c2c85977f
--- /dev/null
+++ b/spec/requests/provider_interface/candidate_pool/publish_invites_controller_spec.rb
@@ -0,0 +1,48 @@
+require 'rails_helper'
+
+RSpec.describe 'ProviderInterface::CandidatePool::PublishInvitesController' do
+  describe 'POST /provider/find-candidates/:candidate_id/invite/:draft_invite_id/review' do
+    let!(:provider_user) { create(:provider_user, :with_provider, dfe_sign_in_uid: 'DFE_SIGN_IN_UID') }
+    let(:provider) { provider_user.providers.first }
+
+    before do
+      allow(DfESignInUser).to receive(:load_from_session)
+        .and_return(
+          DfESignInUser.new(
+            email_address: provider_user.email_address,
+            dfe_sign_in_uid: provider_user.dfe_sign_in_uid,
+            first_name: provider_user.first_name,
+            last_name: provider_user.last_name,
+          ),
+
+        )
+      mailer = instance_double(ActionMailer::MessageDelivery, deliver_later: true)
+      allow(CandidateMailer).to receive(:course_invite).and_return(mailer)
+    end
+
+    it 'redirects to candidate_interface_interstitial_path' do
+      candidate = create(:candidate, pool_status: 'opt_in')
+      application_form = create(:application_form, :completed, candidate:)
+      course = create(:course, provider:, exposed_in_find: true)
+      course_option = create(:course_option, course: course)
+      create(:application_choice, :rejected, application_form:, course_option:)
+
+      draft_invite = create(
+        :pool_invite,
+        status: :draft,
+        candidate:,
+        provider:,
+        course: course,
+      )
+
+      post provider_interface_candidate_pool_candidate_draft_invite_publish_invite_path(
+        candidate,
+        draft_invite,
+      )
+
+      expect(response).to redirect_to(provider_interface_candidate_pool_root_path)
+
+      expect(CandidateMailer).to have_received(:course_invite).with(draft_invite)
+    end
+  end
+end
diff --git a/spec/system/support_interface/docs_spec.rb b/spec/system/support_interface/docs_spec.rb
index b79de1e0a67..197be957a32 100644
--- a/spec/system/support_interface/docs_spec.rb
+++ b/spec/system/support_interface/docs_spec.rb
@@ -72,6 +72,7 @@ def and_it_contains_documentation_for_all_emails
       candidate_mailer-offer_30_day
       candidate_mailer-offer_40_day
       candidate_mailer-offer_50_day
+      candidate_mailer-course_invite
     ]
 
     # extract all the emails that we send into a list of strings like "referee_mailer-reference_request_chaser_email"


Reviews:
user: login: CatalinVoineag
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-11T14:49:20Z
user: login: elceebee
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-11T14:49:35Z
user: login: elceebee
body: Assuming it's not missing a link for candidates to remove themselves from the pool. 

nice one!
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-03-11T14:50:12Z
user: login: CatalinVoineag
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-11T14:50:13Z
user: login: dcyoung-dev
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-03-11T14:51:16Z

Review comments:
diff_hunk: @@ -0,0 +1,9 @@
+Dear <%= @application_form.first_name %>
+
+<%= @provider.name %> have viewed your application details and are inviting you to submit an application for the following course:
+
+[<%= @provider.name %> – <%= @course.name %>](<%= @course.find_url %>)
+
+View the course details and decide if you are interested in applying.
+
+If you do not want to receive invitations like this, you can [stop providers from viewing your application details](#).
path: app/views/candidate_mailer/course_invite.text.erb
user: login: CatalinVoineag
body: We don't have a page where the candidate can unsubscribe yet
created_at: 2025-03-11T14:49:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,9 @@
+Dear <%= @application_form.first_name %>
+
+<%= @provider.name %> have viewed your application details and are inviting you to submit an application for the following course:
+
+[<%= @provider.name %> – <%= @course.name %>](<%= @course.find_url %>)
+
+View the course details and decide if you are interested in applying.
+
+If you do not want to receive invitations like this, you can [stop providers from viewing your application details](#).
path: app/views/candidate_mailer/course_invite.text.erb
user: login: elceebee
body: Presumably this is just a placeholder for testing purposes and we'll have a real end point for a candidate to remove themselves from the pool?
created_at: 2025-03-11T14:49:34Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,9 @@
+Dear <%= @application_form.first_name %>
+
+<%= @provider.name %> have viewed your application details and are inviting you to submit an application for the following course:
+
+[<%= @provider.name %> – <%= @course.name %>](<%= @course.find_url %>)
+
+View the course details and decide if you are interested in applying.
+
+If you do not want to receive invitations like this, you can [stop providers from viewing your application details](#).
path: app/views/candidate_mailer/course_invite.text.erb
user: login: CatalinVoineag
body: Correct 👍🏻 
created_at: 2025-03-11T14:50:13Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,9 @@
+Dear <%= @application_form.first_name %>
+
+<%= @provider.name %> have viewed your application details and are inviting you to submit an application for the following course:
+
+[<%= @provider.name %> – <%= @course.name %>](<%= @course.find_url %>)
+
+View the course details and decide if you are interested in applying.
+
+If you do not want to receive invitations like this, you can [stop providers from viewing your application details](#).
path: app/views/candidate_mailer/course_invite.text.erb
user: login: dcyoung-dev
body: Can we add a Trello card to make sure we update this before sending these emails to real Candidates 
created_at: 2025-03-11T14:50:13Z
author_association: COLLABORATOR

PR comments:
user: login: sentry[bot]
created_at: 2025-03-11T12:33:40Z
body: ## 🔍 Existing Issues For Review
Your pull request is modifying functions with the following pre-existing issues:

📄 File: **app/controllers/provider_interface/candidate_pool/publish_invites_controller.rb**

| Function | Unhandled Issue |
| :------- | :----- |
| **`create`** | [**NoMethodError: undefined method `candidate' for nil (NoMethodError)**](https://dfe-teacher-services.sentry.io/issues/6367518871/?referrer=github-open-pr-bot) ProviderInterface::CandidatePool::PublishInvit... <br> `Event Count:` **1** |
---

<sub>Did you find this useful? React with a 👍 or 👎</sub>
author_association: NONE
user: login: CatalinVoineag
created_at: 2025-03-11T14:58:48Z
body: ![](https://github.trello.services/images/mini-trello-icon.png) [Send invite course email to candidates](https://trello.com/c/EoQLT2h9/745-send-invite-course-email-to-candidates)
author_association: CONTRIBUTOR
user: login: RebeccaCryan
created_at: 2025-03-11T15:56:00Z
body: Looks good - thanks Catalin. 
author_association: NONE