From c568af82855aef847dcad21b77d89ffd71742453 Mon Sep 17 00:00:00 2001 From: Leo Vasanko Date: Mon, 28 Sep 2026 15:45:14 +0000 Subject: [PATCH] contact messages: mailto recipient is Brand MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The contact-email frame now carries the RFC-encoded recipient (John Smith → John%20Smith%20%3Cjohn@example.com%3E), encoded server-side where the current brand lives; the link text stays the bare address and the localized subject rides the query as before. --- frontend/src/ContactOverlay.vue | 7 ++++--- frontend/src/contact-main.js | 2 ++ pagerite/messages.py | 12 +++++++++++- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/frontend/src/ContactOverlay.vue b/frontend/src/ContactOverlay.vue index bdc35b5..2ebf18f 100644 --- a/frontend/src/ContactOverlay.vue +++ b/frontend/src/ContactOverlay.vue @@ -20,10 +20,11 @@ const emit = defineEmits(['send', 'close']) const admin = computed(() => props.state.mode === 'admin') // The visitor dialog's title is the localized site contact label (the -// {contact:} buttons' default label); the mailto link carries the -// localized subject. +// {contact:} buttons' default label). The mailto href carries the +// server-encoded "Brand " recipient and the localized subject; +// the link text is the bare address. const mailto = computed( - () => `mailto:${props.state.email}?subject=${encodeURIComponent(props.state.subject)}` + () => `mailto:${props.state.mailtoTo}?subject=${encodeURIComponent(props.state.subject)}` ) // Escape closes, like the paskia dialogs whose backdrop we share. diff --git a/frontend/src/contact-main.js b/frontend/src/contact-main.js index 7c8b5d2..db0bfd4 100644 --- a/frontend/src/contact-main.js +++ b/frontend/src/contact-main.js @@ -31,6 +31,7 @@ const state = reactive({ // link with subject prefilled. label: 'Contact us', email: '', + mailtoTo: '', // RFC-encoded "Brand " recipient for the mailto href subject: '', }) @@ -145,6 +146,7 @@ function onPow(msg) { function onEmail(msg) { state.email = msg.email || '' + state.mailtoTo = msg.to || '' // RFC-encoded "Brand " recipient } // An upload hit the end of the PoW quota: re-register (the server pushes diff --git a/pagerite/messages.py b/pagerite/messages.py index 1595be4..554ca20 100644 --- a/pagerite/messages.py +++ b/pagerite/messages.py @@ -265,7 +265,17 @@ async def visitor_proven(ws: WebSocket, raw: dict) -> bytes | None: _quota[key] = POW_QUOTA await ws.send_json({"type": "pow", "quota": POW_QUOTA}) if data.contact_email: - await ws.send_json({"type": "contact-email", "email": data.contact_email}) + # The mailto recipient is "Brand " (RFC-encoded here; the + # link text is the bare address, the subject is the client's + # localized contact_subject). + recipient = ( + quote(f"{data.brand} <{data.contact_email}>", safe="@") + if data.brand.strip() + else quote(data.contact_email, safe="@") + ) + await ws.send_json( + {"type": "contact-email", "email": data.contact_email, "to": recipient} + ) return key