Switch from Resend to Brevo for transactional emails
Resend requires a verified domain to send to arbitrary recipients — mvaglobalfret.com isn't registered. Brevo accepts single-sender verification on a free email address, so we can send from mvaglobalfret@gmail.com without owning a domain. - Worker: replace resendSend() with brevoSend() (api.brevo.com/v3/smtp/email) - Env vars: BREVO_API_KEY, BREVO_SENDER_EMAIL, BREVO_SENDER_NAME - Update comments in confirmation.js and form-handler.js Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
eb5c4f1cee
commit
82bc8ba358
@ -131,7 +131,7 @@ export default {
|
||||
|
||||
// ── action: requestVerification ──────────────────────────
|
||||
// Génère un token unique, le stocke en KV avec les infos du contact,
|
||||
// et envoie un email de validation via Resend (avec un lien de
|
||||
// et envoie un email de validation via Brevo (avec un lien de
|
||||
// confirmation). Le contact ne reçoit la référence et l'adresse
|
||||
// Paris qu'APRÈS avoir cliqué sur le lien.
|
||||
// Anti-bot : Turnstile vérifié d'abord.
|
||||
@ -170,7 +170,7 @@ export default {
|
||||
// ── action: verifyToken ──────────────────────────────────
|
||||
// Appelé par confirmation.html quand l'utilisateur clique sur
|
||||
// le lien dans l'email de validation. Lit le token en KV,
|
||||
// envoie le welcome (avec ref + adresse Paris) via Resend,
|
||||
// envoie le welcome (avec ref + adresse Paris) via Brevo,
|
||||
// puis supprime le token (one-time use).
|
||||
if (action === 'verifyToken') {
|
||||
if (!body.token) return jsonResponse({ error: 'token requis' }, 400);
|
||||
@ -184,7 +184,7 @@ export default {
|
||||
const tokenData = JSON.parse(raw);
|
||||
|
||||
try {
|
||||
await sendWelcomeViaResend(env, tokenData);
|
||||
await sendWelcomeViaBrevo(env, tokenData);
|
||||
// Marque le token consommé (gardé 7j pour idempotence en cas de
|
||||
// double clic du lien, mais avec flag "used")
|
||||
await env.WELCOME_KV.put(key, JSON.stringify({ ...tokenData, used: true, usedAt: new Date().toISOString() }), {
|
||||
@ -426,38 +426,47 @@ async function sendWelcomeEmail(env, params) {
|
||||
}
|
||||
|
||||
// =============================================================
|
||||
// Resend : envoi d'emails (verification + welcome)
|
||||
// Brevo (ex-Sendinblue) : envoi d'emails (verification + welcome)
|
||||
// =============================================================
|
||||
// Resend est utilisé pour envoyer les emails car il ne nécessite
|
||||
// pas de template séparé (on construit le HTML directement dans le
|
||||
// Worker). Free tier : 100 emails/jour, 3000/mois.
|
||||
// Brevo est utilisé pour l'envoi car il accepte la "single-sender
|
||||
// verification" : on valide juste une adresse email (mvaglobalfret@gmail.com)
|
||||
// au lieu de devoir vérifier tout un domaine. Free tier : 300 emails/jour.
|
||||
//
|
||||
// Setup requis :
|
||||
// - env.RESEND_API_KEY = clé API Resend (re_...)
|
||||
// - env.RESEND_FROM = adresse expéditrice vérifiée chez Resend
|
||||
// (ex: "MVA Global Fret <noreply@mvaglobalfret.com>")
|
||||
// Pour test : "onboarding@resend.dev"
|
||||
// - env.SITE_URL = base URL du site (ex: "https://mva-global-fret.github.io/site-mva-global-fret")
|
||||
// - env.BREVO_API_KEY = clé API Brevo (xkeysib-...)
|
||||
// - env.BREVO_SENDER_EMAIL = adresse expéditrice validée chez Brevo
|
||||
// (ex: "mvaglobalfret@gmail.com")
|
||||
// - env.BREVO_SENDER_NAME = nom affiché à l'expéditeur (ex: "MVA Global Fret")
|
||||
// - env.SITE_URL = base URL du site (ex: "https://mva-global-fret.github.io/site-mva-global-fret")
|
||||
//
|
||||
// API doc : https://developers.brevo.com/reference/sendtransacemail
|
||||
|
||||
const RESEND_API = 'https://api.resend.com/emails';
|
||||
const BREVO_API = 'https://api.brevo.com/v3/smtp/email';
|
||||
|
||||
async function resendSend(env, { to, subject, html }) {
|
||||
if (!env.RESEND_API_KEY) {
|
||||
throw new Error('RESEND_API_KEY env var not set');
|
||||
async function brevoSend(env, { to, subject, html }) {
|
||||
if (!env.BREVO_API_KEY) {
|
||||
throw new Error('BREVO_API_KEY env var not set');
|
||||
}
|
||||
const from = env.RESEND_FROM || 'MVA Global Fret <onboarding@resend.dev>';
|
||||
const senderEmail = env.BREVO_SENDER_EMAIL || 'mvaglobalfret@gmail.com';
|
||||
const senderName = env.BREVO_SENDER_NAME || 'MVA Global Fret';
|
||||
|
||||
const res = await fetch(RESEND_API, {
|
||||
const res = await fetch(BREVO_API, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${env.RESEND_API_KEY}`,
|
||||
'Content-Type': 'application/json',
|
||||
'api-key' : env.BREVO_API_KEY,
|
||||
'accept' : 'application/json',
|
||||
'content-type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ from, to: [to], subject, html }),
|
||||
body: JSON.stringify({
|
||||
sender : { name: senderName, email: senderEmail },
|
||||
to : [{ email: to }],
|
||||
subject : subject,
|
||||
htmlContent: html,
|
||||
}),
|
||||
});
|
||||
if (!res.ok) {
|
||||
const text = await res.text();
|
||||
throw new Error(`Resend ${res.status}: ${text}`);
|
||||
throw new Error(`Brevo ${res.status}: ${text}`);
|
||||
}
|
||||
return res.json();
|
||||
}
|
||||
@ -504,14 +513,14 @@ async function sendVerificationEmail(env, contact, verToken) {
|
||||
</body>
|
||||
</html>`;
|
||||
|
||||
return resendSend(env, {
|
||||
return brevoSend(env, {
|
||||
to: contact.email,
|
||||
subject: 'Confirmez votre inscription chez MVA Global Fret',
|
||||
html,
|
||||
});
|
||||
}
|
||||
|
||||
async function sendWelcomeViaResend(env, contact) {
|
||||
async function sendWelcomeViaBrevo(env, contact) {
|
||||
const firstname = escapeHtml(contact.firstname || '');
|
||||
const ref = escapeHtml(contact.reference_client || '');
|
||||
const parisAddrRaw = env.PARIS_DEPOT_ADDRESS || '';
|
||||
@ -559,7 +568,7 @@ async function sendWelcomeViaResend(env, contact) {
|
||||
</body>
|
||||
</html>`;
|
||||
|
||||
return resendSend(env, {
|
||||
return brevoSend(env, {
|
||||
to: contact.email,
|
||||
subject: `Bienvenue chez MVA Global Fret — Votre référence ${ref}`,
|
||||
html,
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
// MVA Global Fret — Page de confirmation post-validation email
|
||||
// ============================================================
|
||||
// Cette page est la cible du lien dans l'email de validation
|
||||
// (envoyé par Resend après soumission du formulaire).
|
||||
// (envoyé par Brevo après soumission du formulaire).
|
||||
//
|
||||
// URL : https://mva-global-fret.github.io/site-mva-global-fret/confirmation.html?token=XXX
|
||||
//
|
||||
@ -10,7 +10,7 @@
|
||||
// 1. Lire le token depuis l'URL
|
||||
// 2. POST au Worker avec action 'verifyToken'
|
||||
// 3. Worker valide le token, envoie le welcome email (avec ref +
|
||||
// adresse Paris) via Resend, puis renvoie OK
|
||||
// adresse Paris) via Brevo, puis renvoie OK
|
||||
// 4. Page affiche "Inscription confirmée !"
|
||||
//
|
||||
// Si le token est invalide / expiré : affichage d'un message d'erreur
|
||||
|
||||
@ -304,7 +304,7 @@ function showSuccess(refNumber, clientData) {
|
||||
|
||||
// ── EMAIL DE VÉRIFICATION ─────────────────────────────────────────────────────
|
||||
// Le Worker génère un token unique, le stocke en KV, et envoie un email
|
||||
// de validation via Resend (avec un lien de confirmation). Le contact ne
|
||||
// de validation via Brevo (avec un lien de confirmation). Le contact ne
|
||||
// reçoit la référence et l'adresse Paris qu'APRÈS avoir cliqué sur le lien.
|
||||
async function sendWelcomeEmail(data) {
|
||||
if (!WORKER_PROXY_URL) return;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user