Compare commits

...

1 Commits

Author SHA1 Message Date
Serge RAKOTO HARRY-NAIVO
7044c37ec9 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.
2026-05-07 15:43:30 +02:00
2 changed files with 124 additions and 12 deletions

View File

@ -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
? `<div style="margin:24px 0;padding:18px 24px;background:#1a1a3e;border:2px solid #c5a55a;border-radius:10px;text-align:center;">
<div style="color:#c5a55a;font-size:11px;font-weight:700;letter-spacing:1.5px;margin-bottom:6px;">VOTRE NUMÉRO DE RÉFÉRENCE EXISTANT</div>
<div style="color:#fff;font-size:28px;font-weight:700;letter-spacing:3px;">${ref}</div>
</div>`
: '';
const html = `<!DOCTYPE html>
<html lang="fr">
<body style="margin:0;padding:0;font-family:Arial,sans-serif;background:#f5f5f5;">
<div style="max-width:600px;margin:0 auto;background:#fff;">
<div style="background:#1a1a3e;padding:24px 30px;">
<table role="presentation" cellspacing="0" cellpadding="0" border="0" style="width:100%;border-collapse:collapse;">
<tr>
<td style="width:80px;vertical-align:middle;">
<img src="${logoUrl}" alt="MVA Global Fret" style="display:block;width:70px;height:auto;border:0;">
</td>
<td style="vertical-align:middle;text-align:center;padding-right:80px;">
<div style="color:#c5a55a;font-size:24px;font-weight:700;letter-spacing:2px;">MVA GLOBAL FRET</div>
<div style="color:#fff;font-size:13px;margin-top:6px;">Fret Aérien Paris Antananarivo</div>
</td>
</tr>
</table>
</div>
<div style="padding:40px;">
<p style="font-size:18px;color:#1a1a3e;font-weight:bold;">Bonjour ${firstname || 'cher client'},</p>
<p style="color:#333;line-height:1.6;">
Votre adresse email est <strong>déjà enregistrée</strong> chez MVA Global Fret.
Pas besoin de vous -inscrire votre compte est actif.
</p>
${refBlock}
<p style="color:#333;line-height:1.6;">
Utilisez ce numéro pour étiqueter vos colis et les rendre traçables tout au long du transport Paris Antananarivo.
</p>
<p style="color:#333;line-height:1.6;">
Pour toute question, n'hésitez pas à nous contacter :
</p>
<ul style="color:#333;line-height:1.8;padding-left:18px;">
<li><a href="mailto:mvaglobalfret@gmail.com" style="color:#c5a55a;text-decoration:none;">mvaglobalfret@gmail.com</a></li>
<li><a href="tel:+33780970825" style="color:#c5a55a;text-decoration:none;">+33 7 80 97 08 25</a> (France)</li>
<li><a href="tel:+261384973751" style="color:#c5a55a;text-decoration:none;">+261 38 49 737 51</a> (Madagascar)</li>
</ul>
<p style="color:#666;font-size:13px;line-height:1.6;border-top:1px solid #eee;padding-top:18px;margin-top:30px;">
À très bientôt pour votre prochain envoi,<br>
<strong>L'équipe MVA Global Fret</strong>
</p>
</div>
<div style="background:#1a1a3e;padding:18px;text-align:center;color:#c5a55a;font-size:12px;">
© 2026 MVA Global Fret Antananarivo 101, Madagascar
</div>
</div>
</body>
</html>`;
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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')

View File

@ -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() {