Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions functions/src/api/other/renderPendingEditDecisionEmail.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { describe, expect, test } from 'vitest'
import { renderApprovedEmail, renderRejectedEmail } from './renderPendingEditDecisionEmail'

const eventName = 'My Event'

describe('renderApprovedEmail', () => {
test('lists the applied fields and identifies OpenPlanner as the sender', () => {
const { subject, text } = renderApprovedEmail('Jane', eventName, {
name: 'Jane Doe',
jobTitle: 'Lead Engineer',
})
expect(subject).toMatch(/approved/i)
expect(text).toMatch(/Jane/)
expect(text).toMatch(/Lead Engineer/)
// Footer must always carry the OpenPlanner attribution + reply-to
// contact so the speaker knows who is writing regardless of the
// From header.
expect(text).toContain('OpenPlanner')
expect(text).toContain('contact@email.openplanner.fr')
expect(text).toContain(eventName)
})
})

describe('renderRejectedEmail', () => {
test('mentions the rejection note and identifies OpenPlanner as the sender', () => {
const { subject, text } = renderRejectedEmail('Jane', eventName, { bio: 'lorem' }, 'Bio too long')
expect(subject).toMatch(/not applied|rejected/i)
expect(text).toMatch(/Bio too long/)
expect(text).toContain('OpenPlanner')
expect(text).toContain('contact@email.openplanner.fr')
})

test('omits the reviewer-note section when none provided', () => {
const { text } = renderRejectedEmail('Jane', eventName, { bio: 'lorem' })
expect(text).not.toMatch(/Reviewer note/i)
expect(text).toContain('contact@email.openplanner.fr')
})
})
16 changes: 14 additions & 2 deletions functions/src/api/other/renderPendingEditDecisionEmail.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,16 @@ const renderChangedFieldsList = (patch: Partial<Speaker>): string => {
return lines.join('\n')
}

// Same OpenPlanner-attribution footer used by the request-edit-link mail.
// Per-deploy MAIL_FROM domains make the From header alone unreliable for
// the speaker to identify who is writing, so every speaker self-edit
// email carries an explicit "sent by OpenPlanner" line + a real reply-to
// address.
const OPENPLANNER_CONTACT = 'contact@email.openplanner.fr'
const buildFooter = (eventName: string): string =>
Comment thread
HugoGresse marked this conversation as resolved.
Outdated
`--\nThis email was sent by OpenPlanner on behalf of "${eventName}". ` +
`For questions, contact ${OPENPLANNER_CONTACT}.`
Comment thread
HugoGresse marked this conversation as resolved.
Outdated

export const renderApprovedEmail = (
speakerName: string,
eventName: string,
Expand All @@ -55,7 +65,8 @@ export const renderApprovedEmail = (
`Hello ${speakerName},\n\n` +
`Your profile changes for "${eventName}" have been approved by an administrator and will be public soon.\n\n` +
`Changes applied:\n${changes}\n\n` +
`If you did not request these changes, please contact the event organisers.`,
`If you did not request these changes, please contact the event organisers.\n\n` +
buildFooter(eventName),
}
}

Expand All @@ -73,6 +84,7 @@ export const renderRejectedEmail = (
`Hello ${speakerName},\n\n` +
`Your recent profile changes for "${eventName}" were not applied by the administrators.\n\n` +
`Changes you proposed:\n${changes}${noteSection}\n\n` +
`You can request a fresh edit link from the speaker self-edit page if you want to retry.`,
`You can request a fresh edit link from the speaker self-edit page if you want to retry.\n\n` +
buildFooter(eventName),
}
}
26 changes: 24 additions & 2 deletions functions/src/api/routes/speakers/requestEditLinkPOST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,16 +41,38 @@ export const requestEditLinkPOSTSchema = {
},
}

// Identifying the OpenPlanner platform in the email body is important
// because the From address can vary per deploy (each event organiser may
// configure their own MAIL_FROM domain). Without an explicit "sent by
// OpenPlanner" line, speakers receiving the link from an unfamiliar
// domain are likely to flag it as phishing. The contact address is a
// real inbox so speakers can reply if they have questions.
Comment thread
HugoGresse marked this conversation as resolved.
Outdated
const OPENPLANNER_CONTACT = 'contact@email.openplanner.fr'

const renderEmail = (speakerName: string, eventName: string, link: string, lang: 'fr' | 'en') => {
Comment thread
HugoGresse marked this conversation as resolved.
Outdated
if (lang === 'fr') {
return {
subject: `Modifier votre profil — ${eventName}`,
text: `Bonjour ${speakerName},\n\nVous avez demandé un lien pour modifier votre profil public pour l'événement "${eventName}".\n\nCliquez ici (valable 7 jours) :\n${link}\n\nVos modifications seront vérifiées par un administrateur avant d'être publiées.\n\nSi vous n'êtes pas à l'origine de cette demande, ignorez cet email.`,
text:
`Bonjour ${speakerName},\n\n` +
`Vous avez demandé un lien pour modifier votre profil public pour l'événement "${eventName}".\n\n` +
`Cliquez ici (valable 7 jours) :\n${link}\n\n` +
`Vos modifications seront vérifiées par un administrateur avant d'être publiées.\n\n` +
`Si vous n'êtes pas à l'origine de cette demande, ignorez cet email.\n\n` +
`--\nCet email est envoyé par OpenPlanner pour le compte de "${eventName}". ` +
`Pour toute question, contactez ${OPENPLANNER_CONTACT}.`,
}
}
return {
subject: `Edit your profile — ${eventName}`,
text: `Hello ${speakerName},\n\nYou requested a link to edit your public profile for "${eventName}".\n\nClick here (valid 7 days):\n${link}\n\nYour changes will be reviewed by an administrator before going live.\n\nIf you did not request this, ignore this email.`,
text:
`Hello ${speakerName},\n\n` +
`You requested a link to edit your public profile for "${eventName}".\n\n` +
`Click here (valid 7 days):\n${link}\n\n` +
`Your changes will be reviewed by an administrator before going live.\n\n` +
`If you did not request this, ignore this email.\n\n` +
`--\nThis email was sent by OpenPlanner on behalf of "${eventName}". ` +
`For questions, contact ${OPENPLANNER_CONTACT}.`,
}
}

Expand Down
Loading