From 7044c37ec9197463b09f769c724a986a2bb0c48b Mon Sep 17 00:00:00 2001 From: Serge RAKOTO HARRY-NAIVO Date: Thu, 7 May 2026 15:43:30 +0200 Subject: [PATCH] feat(worker): re-enable welcome-back email via Worker + Resend (footer 2026) Replaces EmailJS-based welcome-back (disabled in PR #6 because the EmailJS account is Melissa-only with obsolete '(c) 2025' footer) with a Worker action that delegates to Resend, ensuring same '\xc2\xa9 2026' footer as all other transactional emails. Worker side: - New action 'sendWelcomeBack' validates Turnstile + searches HubSpot contact by email (404 if not found, prevents spam to non-existing). - New function sendWelcomeBackViaResend: same navy/gold visual identity as other Resend emails. Subject includes existing ref (= 'Vous \xc3\xaates d\xc3\xa9j\xc3\xa0 inscrit chez MVA Global Fret \xe2\x80\x94 R\xc3\xa9f\xc3\xa9rence MVA-XXX'). Idempotent: zero HubSpot writes. Frontend (form-handler.js): - sendWelcomeBackEmail rewritten to POST Worker action sendWelcomeBack instead of emailjs.send. Reuses window.turnstileToken. - Re-enabled call inside showAlreadyRegistered. Smoke tests: - 400 'email requis' without email - 403 'Turnstile validation failed' without valid token - Full flow testable via browser only (= real Turnstile challenge). Refs: post-cutover polish, fixes Issue 1 footer 2025 from E2E retest. --- cloudflare-worker/hubspot-proxy.js | 106 +++++++++++++++++++++++++++++ js/form-handler.js | 30 ++++---- 2 files changed, 124 insertions(+), 12 deletions(-) diff --git a/cloudflare-worker/hubspot-proxy.js b/cloudflare-worker/hubspot-proxy.js index d40eb48..77d93f1 100644 --- a/cloudflare-worker/hubspot-proxy.js +++ b/cloudflare-worker/hubspot-proxy.js @@ -238,6 +238,39 @@ export default { } } + // ── action: sendWelcomeBack ───────────────────────────── + // Envoie un email "Vous êtes déjà inscrit" au client qui tente + // une ré-inscription. Idempotent côté HubSpot (= aucune création + // ni update de contact). Anti-bot via Turnstile + sanity check + // que l'email existe vraiment dans HubSpot avant d'envoyer. + if (action === 'sendWelcomeBack') { + if (!body.email) return jsonResponse({ error: 'email requis' }, 400); + + const turnstileOk = await verifyTurnstile(env, body.turnstile_token, request); + if (!turnstileOk) { + return jsonResponse({ ok: false, error: 'Turnstile validation failed' }, 403); + } + + try { + // Vérification : le contact existe bien (= prevent spam vers + // emails inconnus en passant un faux turnstile) + const search = await searchContactByEmail(token, body.email); + const existing = (search.results || [])[0]; + if (!existing) { + return jsonResponse({ ok: false, error: 'Contact not found' }, 404); + } + + await sendWelcomeBackViaResend(env, { + firstname : body.firstname || existing.properties?.firstname || '', + email : body.email, + reference_client : existing.properties?.reference_client || '', + }); + return jsonResponse({ ok: true }); + } catch (err) { + return jsonResponse({ ok: false, error: err.message }, 500); + } + } + // ── action: listSubscriptions (debug : trouver les IDs) ── if (action === 'listSubscriptions') { // Endpoint legacy email/public/v1 nécessite scope content au lieu de @@ -530,6 +563,79 @@ async function sendWelcomeViaResend(env, contact) { }); } +// Email "Ravis de vous revoir" pour les clients déjà inscrits qui retentent +// le formulaire de contact. Rappelle le numéro de référence existant et +// précise que le contact n'est pas re-créé (= anti-doublon HubSpot). +// Idempotent côté HubSpot (= zéro write). Footer 2026 cohérent avec les +// autres emails Resend. +async function sendWelcomeBackViaResend(env, contact) { + const siteUrl = env.SITE_URL || 'https://mva-globalfret.com'; + const logoUrl = `${siteUrl}/PNG%20MVA%20GLOBAL%20FRET.png`; + const firstname = escapeHtml(contact.firstname || ''); + const ref = escapeHtml(contact.reference_client || ''); + const refBlock = ref + ? `
+
VOTRE NUMÉRO DE RÉFÉRENCE EXISTANT
+
${ref}
+
` + : ''; + + const html = ` + + +
+
+ + + + + +
+ MVA Global Fret + +
MVA GLOBAL FRET
+
Fret Aérien Paris — Antananarivo
+
+
+
+

Bonjour ${firstname || 'cher client'},

+

+ Votre adresse email est déjà enregistrée chez MVA Global Fret. + Pas besoin de vous ré-inscrire — votre compte est actif. +

+ ${refBlock} +

+ Utilisez ce numéro pour étiqueter vos colis et les rendre traçables tout au long du transport Paris → Antananarivo. +

+

+ Pour toute question, n'hésitez pas à nous contacter : +

+ +

+ À très bientôt pour votre prochain envoi,
+ L'équipe MVA Global Fret +

+
+
+ © 2026 MVA Global Fret — Antananarivo 101, Madagascar +
+
+ +`; + + return resendSend(env, { + to: contact.email, + subject: ref + ? `Vous êtes déjà inscrit chez MVA Global Fret — Référence ${contact.reference_client}` + : 'Vous êtes déjà inscrit chez MVA Global Fret', + html, + }); +} + function escapeHtml(s) { return String(s) .replace(/&/g, '&').replace(//g, '>') diff --git a/js/form-handler.js b/js/form-handler.js index d83ae00..94f6a31 100644 --- a/js/form-handler.js +++ b/js/form-handler.js @@ -310,18 +310,27 @@ function showSuccess(_refNumber, _clientData) { // ── EMAIL "RAVIS DE TE REVOIR" (client déjà inscrit) ────────────────────────── // Rappelle au client son numéro de référence existant — n'écrit RIEN dans HubSpot. +// Passe par le Cloudflare Worker (action: sendWelcomeBack) qui délègue à Resend +// — footer 2026 cohérent avec les autres emails transactionnels. Anti-bot via +// Turnstile : on transmet le token déjà validé au moment du submit du formulaire. async function sendWelcomeBackEmail(contact) { - if (typeof emailjs === 'undefined') return; + if (!WORKER_PROXY_URL) return; if (!contact || !contact.email) return; + if (!window.turnstileToken) return; try { - await emailjs.send(EMAILJS_SERVICE_ID, EMAILJS_TEMPLATE_WELCOME_BACK, { - firstname: contact.firstname || '', - email: contact.email, - reference_client: contact.reference_client || '', + await fetch(WORKER_PROXY_URL, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + action : 'sendWelcomeBack', + email : contact.email, + firstname : contact.firstname || '', + turnstile_token : window.turnstileToken, + }), }); } catch (err) { - // Si le template n'existe pas encore ou erreur réseau : on n'interrompt rien - console.warn('EmailJS welcome-back email failed:', err); + // Erreur réseau : on n'interrompt pas l'UX (le client voit déjà sa ref dans le UI) + console.warn('Worker sendWelcomeBack failed:', err); } } @@ -351,11 +360,8 @@ function showAlreadyRegistered(contact) { // Envoi d'une notification interne à MVA (sans modifier les données du client) notifyDuplicateViaFormspree(contact); - // Email "Ravis de te revoir" via EmailJS désactivé (= post-migration 2026-05-07). - // Le compte EmailJS n'est plus accessible (Melissa-only) et son template a un - // footer "(c) 2025" obsolète. Le client a déjà sa référence affichée via - // `showAlreadyRegistered` ci-dessus — l'email est cosmétique. À ré-implémenter - // via Worker + Resend en follow-up si jugé utile. + // Email "Ravis de te revoir" via Worker + Resend (= footer 2026 cohérent) + sendWelcomeBackEmail(contact); } function showError() { -- 2.45.2