Identify OpenPlanner in speaker self-edit emails#259
Merged
Conversation
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>
|
Visit the preview URL for this PR (updated for commit 7488dc7):
(expires Mon, 01 Jun 2026 18:06:42 GMT) 🔥 via Firebase Hosting GitHub Action 🌎 Sign: 0c15c45ea5a4c54095387eacf30c3755c9260f22 |
Contributor
There was a problem hiding this comment.
Pull request overview
Adds an attribution/contact footer to speaker-facing self-edit emails so recipients can identify OpenPlanner as the platform behind messages even when MAIL_FROM varies per deployment.
Changes:
- Append an OpenPlanner attribution + contact footer to the speaker magic-link email (FR/EN variants).
- Add a shared
buildFooter(eventName)helper and append it to approved/rejected self-edit decision emails. - Add Vitest coverage asserting the footer/contact address is present in approved/rejected decision emails.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| functions/src/api/routes/speakers/requestEditLinkPOST.ts | Adds a footer (and contact constant) to the speaker edit-link email body. |
| functions/src/api/other/renderPendingEditDecisionEmail.ts | Adds shared footer builder and appends it to approved/rejected notification bodies. |
| functions/src/api/other/renderPendingEditDecisionEmail.test.ts | New unit tests validating footer/contact presence in decision emails. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Speakers receiving the magic-link or the approve/reject notification have no easy way to recognise where the message comes from — each event organiser configures their own
MAIL_FROMdomain so the From header alone is not a stable cue. Add a short attribution footer to every speaker-facing email that names OpenPlanner and provides a real reply-to (contact@email.openplanner.fr).Changes
requestEditLinkPOST.tsrenderEmail(FR + EN) appends:renderPendingEditDecisionEmail.tsintroduces a sharedbuildFooter(eventName)helper and appends it to bothrenderApprovedEmailandrenderRejectedEmailso all three speaker-facing emails carry the same line.renderApprovedEmail/renderRejectedEmailassert the footer presence + contact address, in addition to the existing field/reviewer-note rendering.Why a body footer, not just the From header
MAIL_FROMmeans the From header varies (e.g.noreply@event-domain.tld). Speakers cannot rely on it to identify the sender.Test plan
renderPendingEditDecisionEmail.test.ts).🤖 Generated with Claude Code