You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Identify OpenPlanner in speaker self-edit emails (#259)
* Identify OpenPlanner in speaker self-edit emails
Each event organiser configures their own MAIL_FROM domain, so the
From header alone is not enough for a speaker to recognise that the
magic-link email or the approval/rejection notification comes from
OpenPlanner. Speakers receiving the link from an unfamiliar address
are likely to flag it as phishing.
- requestEditLinkPOST.ts renderEmail (FR + EN) now appends a short
footer: "This email was sent by OpenPlanner on behalf of '<event
name>'. For questions, contact contact@email.openplanner.fr."
- renderPendingEditDecisionEmail.ts (approve + reject) gains the same
footer via a shared buildFooter helper so all three speaker-facing
emails carry the OpenPlanner attribution + a real reply-to.
- New unit tests on renderApprovedEmail / renderRejectedEmail lock in
the footer presence, the contact address, and the existing
field/reviewer-note rendering. 222/222 backend tests pass.
The footer text is identical to the request-link mail so any address
filter / signature detection the speaker has set up keeps working.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Address Copilot review on PR 259: shared footer + real Reply-To header
Two themes from the review, both fixed:
1) "Reply-to" comment was inaccurate — only the body carried the
contact address, no SMTP header was set. sendEmail now accepts an
optional `replyTo` on EmailMessage and forwards it to nodemailer so a
real Reply-To header is emitted. The audit row in `mail/` also persists
the value (or `null`) so ops can see what was advertised. All three
speaker-facing call sites (request-link, approve, reject) pass
`replyTo: OPENPLANNER_CONTACT_EMAIL`, meaning a speaker who hits Reply
lands in the OpenPlanner contact inbox even when MAIL_FROM is a
per-event no-reply alias.
2) `OPENPLANNER_CONTACT` and `buildFooter` were duplicated between
requestEditLinkPOST.ts and renderPendingEditDecisionEmail.ts —
exactly the drift risk the review flagged. Introduce a single
`functions/src/api/other/speakerEmailFooter.ts` module that exports
`OPENPLANNER_CONTACT_EMAIL` + `buildSpeakerEmailFooter(eventName)`.
The FR variant of the magic-link mail keeps its own localised wording
but pulls the contact constant from the shared module so the address
only lives in one place. The English footer + the approve/reject
notifications consume `buildSpeakerEmailFooter` directly.
New tests:
- speakerEmailFooter.test.ts pins the contact constant and the footer
shape (leading `--\n`, OpenPlanner attribution, event name + contact
interpolation).
- sendEmail.test.ts gains two cases for the replyTo path: header is
forwarded to nodemailer + persisted on the audit row when present;
undefined / null respectively when caller omits it.
226/226 backend tests pass; tsc clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.`,
58
+
text:
59
+
`Bonjour ${speakerName},\n\n`+
60
+
`Vous avez demandé un lien pour modifier votre profil public pour l'événement "${eventName}".\n\n`+
61
+
`Cliquez ici (valable 7 jours) :\n${link}\n\n`+
62
+
`Vos modifications seront vérifiées par un administrateur avant d'être publiées.\n\n`+
63
+
`Si vous n'êtes pas à l'origine de cette demande, ignorez cet email.\n\n`+
64
+
buildFrenchFooter(eventName),
49
65
}
50
66
}
51
67
return{
52
68
subject: `Edit your profile — ${eventName}`,
53
-
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.`,
69
+
text:
70
+
`Hello ${speakerName},\n\n`+
71
+
`You requested a link to edit your public profile for "${eventName}".\n\n`+
72
+
`Click here (valid 7 days):\n${link}\n\n`+
73
+
`Your changes will be reviewed by an administrator before going live.\n\n`+
74
+
`If you did not request this, ignore this email.\n\n`+
0 commit comments