diff --git a/src/i18n/resources/en/email.json b/src/i18n/resources/en/email.json
index 8b61caa7eb..3abeb3dabf 100644
--- a/src/i18n/resources/en/email.json
+++ b/src/i18n/resources/en/email.json
@@ -1,4 +1,10 @@
{
+ "invalidLinks": {
+ "messageBodyIntro": "The following countries have one or more invalid links:",
+ "messageFooter": "Please review and update the affected links in the FRA Platform.\n\nThank you,\nThe FRA team",
+ "messageHeader": "Dear {{recipientName}},",
+ "subject": "Action required: invalid links detected in your countries"
+ },
"invitationAccepted": {
"htmlMessage": "Dear {{recipientName}} {{recipientSurname}},\n
\nUser {{invitedUserName}} {{invitedUserSurname}} has accepted your invitation to {{assessmentName}} {{cycleName}} as {{role}} for {{country}}.\n
\nThe FRA team",
"subject": "User {{invitedUserName}} {{invitedUserSurname}} has accepted your invitation",
diff --git a/src/server/service/mail/mail.ts b/src/server/service/mail/mail.ts
index 7c25d84ceb..1bce49ce46 100644
--- a/src/server/service/mail/mail.ts
+++ b/src/server/service/mail/mail.ts
@@ -1,5 +1,6 @@
import nodemailer from 'nodemailer'
+import { notifyLinksInvalid } from 'server/service/mail/notifyLinksInvalid'
import { remindReviewers } from 'server/service/mail/remindReviewers'
import { ProcessEnv } from 'server/utils'
import { Logger } from 'server/utils/logger'
@@ -63,6 +64,7 @@ export const sendMail = async (email: MailServiceEmail): Promise => {
export const MailService = {
assessmentNotifyUsers,
+ notifyLinksInvalid,
oneToOneMessage,
remindReviewers,
resetPassword,
diff --git a/src/server/service/mail/notifyLinksInvalid/buildEmail.ts b/src/server/service/mail/notifyLinksInvalid/buildEmail.ts
new file mode 100644
index 0000000000..7616d77ce6
--- /dev/null
+++ b/src/server/service/mail/notifyLinksInvalid/buildEmail.ts
@@ -0,0 +1,104 @@
+import { createI18nPromise } from 'i18n/i18nFactory'
+
+import { CountryIso } from 'meta/area/countryIso'
+import { Assessment, AssessmentName } from 'meta/assessment/assessment'
+import { Cycle, CycleName } from 'meta/assessment/cycle'
+import { Link } from 'meta/cycleData/links/link'
+import { Lang } from 'meta/lang'
+import { Routes } from 'meta/routes/routes'
+import { SectionNames } from 'meta/routes/sectionNames'
+import { User } from 'meta/user/user'
+import { Users } from 'meta/user/users'
+
+import { MailServiceEmail } from 'server/service/mail/mail'
+import { ProcessEnv } from 'server/utils'
+
+type Props = {
+ assessment: Assessment
+ cycle: Cycle
+ linksByCountry: Record>
+ threshold: number
+ user: User
+}
+
+type CountryEntry = {
+ countryIso: CountryIso
+ countryName: string
+ countryLinksUrl: string
+ links: Array
+}
+
+type GetCountryEntriesProps = {
+ assessmentName: AssessmentName
+ cycleName: CycleName
+ linksByCountry: Record>
+ t: (key: string) => string
+}
+
+// Return links to the linkStatus page per assessment x cycle x country
+const getCountryLinksUrl = (params: {
+ assessmentName: AssessmentName
+ cycleName: CycleName
+ countryIso: CountryIso
+}): string =>
+ `${ProcessEnv.appUri}${Routes.CountryHomeSection.generatePath({ ...params, sectionName: SectionNames.Country.Home.linksStatus })}`
+
+// Returns the text version of the body of the email
+const _getTextLines = (countryEntries: Array): string =>
+ countryEntries
+ .map(({ countryIso, countryLinksUrl, countryName, links }) => {
+ const urlLines = links.map(({ link }) => ` - ${link}`).join('\n')
+ return ` ${countryName} (${countryIso}): ${links.length} invalid link(s)\n ${countryLinksUrl}\n${urlLines}`
+ })
+ .join('\n\n')
+
+// Returns the HTML body of the email
+const _getHtmlItems = (countryEntries: Array): string =>
+ countryEntries
+ .map(({ countryIso, countryLinksUrl, countryName, links }) => {
+ const urlItems = links.map(({ link }) => `