Skip to content

Identify OpenPlanner in speaker self-edit emails#259

Merged
HugoGresse merged 2 commits into
mainfrom
magic-link-mention-openplanner
May 25, 2026
Merged

Identify OpenPlanner in speaker self-edit emails#259
HugoGresse merged 2 commits into
mainfrom
magic-link-mention-openplanner

Conversation

@HugoGresse
Copy link
Copy Markdown
Owner

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_FROM domain 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.ts renderEmail (FR + EN) appends:

    --
    This email was sent by OpenPlanner on behalf of "<event name>". For questions, contact contact@email.openplanner.fr.
    (FR variant uses the same shape in French.)

  • renderPendingEditDecisionEmail.ts introduces a shared buildFooter(eventName) helper and appends it to both renderApprovedEmail and renderRejectedEmail so all three speaker-facing emails carry the same line.
  • New unit tests on renderApprovedEmail / renderRejectedEmail assert the footer presence + contact address, in addition to the existing field/reviewer-note rendering.

Why a body footer, not just the From header

  • Per-event MAIL_FROM means the From header varies (e.g. noreply@event-domain.tld). Speakers cannot rely on it to identify the sender.
  • A plain-text footer survives every email client and copy/paste flow, and lets speakers filter on the line if they want.
  • The contact address is a real inbox so speakers can reply if they have questions or suspect abuse.

Test plan

  • 222/222 backend tests pass (3 new unit tests in renderPendingEditDecisionEmail.test.ts).
  • Backend tsc clean.

🤖 Generated with Claude Code

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>
Copilot AI review requested due to automatic review settings May 25, 2026 17:58
@HugoGresse HugoGresse temporarily deployed to GitHub Action PR May 25, 2026 17:58 — with GitHub Actions Inactive
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 25, 2026

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

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread functions/src/api/routes/speakers/requestEditLinkPOST.ts Outdated
Comment thread functions/src/api/routes/speakers/requestEditLinkPOST.ts Outdated
Comment thread functions/src/api/other/renderPendingEditDecisionEmail.ts Outdated
Comment thread functions/src/api/other/renderPendingEditDecisionEmail.ts Outdated
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>
@HugoGresse HugoGresse temporarily deployed to GitHub Action PR May 25, 2026 18:05 — with GitHub Actions Inactive
@HugoGresse HugoGresse merged commit f229b71 into main May 25, 2026
5 checks passed
@HugoGresse HugoGresse deleted the magic-link-mention-openplanner branch May 25, 2026 18:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants