Comment envoyer un email en Javascript
Apprends à configurer Nodemailer pour envoyer des emails en Javascript via SMTP et améliorer l'expérience utilisateur dans React Router 7.
Pourquoi gérer l’envoi d’e-mails côté serveur ?
Dans le module précédent tu as mis en place le flow « Mot de passe oublié ». Tout fonctionne... sauf l’e-mail : le lien de réinitialisation n’apparaît que dans la console ! Pas très user-friendly. Nous allons :
- Installer et configurer Nodemailer pour envoyer un mail depuis notre serveur Express.
- Typiser les variables d’environnement afin d’éviter les « undefined » en production.
- Utiliser un provider SMTP (gratuit au départ) comme Mailgun ou Resend.
- Factoriser un helper
sendPasswordResetEmail()réutilisable. - Brancher ce helper dans
triggerResetPasswordRequest()afin de transformer tonconsole.logen vrai e-mail.
Avec cette brique, tu peux ensuite envoyer n’importe quel courrier : newsletter, confirmation d’inscription, alerte, etc.
1 – Installer Nodemailer
1npm install nodemailer2npm install -D @types/nodemailer
La dépendance de type n’est nécessaire qu’en dev ; pas de surcoût en prod.
2 – Préparer les variables d’environnement
Ajoute les valeurs SMTP dans ton .env (ici un compte Mailgun « Pay-as-you-go » : 1 000 mails/mois gratuits).
1SMTP_HOST=smtp.mailgun.org2SMTP_PORT=5873SMTP_USER=postmaster@sandbox123.mailgun.org4SMTP_PASSWORD=superSecret5SMTP_SENDER="Formation React Router 7 <contact@algomax.fr>"67FRONTEND_URL=http://localhost:5173 # lien absolu pendant le dev
Tip
FRONTEND_URL par ton nom de domaine.3 – Créer le transport Nodemailer
1import nodemailer from "nodemailer";23/**4* Le transporteur SMTP. Construit une seule fois (singleton).5*/6export const transporter = nodemailer.createTransport({7host: process.env.SMTP_HOST,8port: Number(process.env.SMTP_PORT ?? 587),9auth: {10user: process.env.SMTP_USER,11pass: process.env.SMTP_PASSWORD,12},13});
host+port: paramètres de ton provider.auth.user/auth.pass: identifiants SMTP.
Warning
.env est déjà listé dans ton .gitignore.4 – Factoriser un helper générique sendEmail
1export async function sendEmail({2to,3subject,4html,5text,6}: {7to: string8subject: string9html: string10text: string11}) {12const info = await transporter.sendMail({13from: process.env.SMTP_SENDER,14to,15subject,16text,17html,18});1920console.log("📧 Email envoyé :", info.messageId);21}
fromrécupère le nom + l’adresse définis dansSMTP_SENDER.- La fonction loggue l’id, pratique pour le debug local.
5 – Spécialiser l’e-mail « Réinitialisation de mot de passe »
1export async function sendPasswordResetEmail({2email,3token,4}: {5email: string6token: string7}) {8const resetUrl = `${process.env.FRONTEND_URL}/reset-password?token=${token}`910await sendEmail({11to: email,12subject: "Réinitialisation de votre mot de passe",13text: [14"Bonjour,",15"Vous avez demandé à réinitialiser votre mot de passe.",16`Lien de réinitialisation : ${resetUrl}`,17"Si ce n'est pas vous, ignorez cet e-mail.",18].join("\n"),19html: `20<p>Bonjour,</p>21<p>Vous avez demandé à réinitialiser votre mot de passe.</p>22<p><a href="${resetUrl}">Cliquez ici pour choisir un nouveau mot de passe</a></p>23<p style="font-size:12px;color:#666">Ce lien expire dans 30 min.</p>24`,25})26}
Important
FRONTEND_URL. Sans ça, le navigateur ne saurait pas où rediriger depuis une boîte mail.6 – Appeler le helper dans la logique serveur
1import { sendPasswordResetEmail } from "./emails.server";23// ...45export async function triggerResetPasswordRequest({ email }: { email: string }) {6const { userExists, userId } = await checkIfUserExists({ email, password: "" })7if (!userExists || !userId) return // pas d'info révélée89const token = crypto.randomUUID().slice(0, 32)10const expiresAt = new Date(Date.now() + 1000 * 60 * 30) // 30 min11await prisma.user.update({12where: { id: userId },13data: { resetToken: token, resetTokenExpiresAt: expiresAt },14})1516await sendPasswordResetEmail({ email, token })17}
- On vérifie l’existence du compte (sans distinguer « email inconnu » pour éviter le credential enumeration).
- On génère un token + date d’expiration.
- On sauvegarde en base, puis on envoie l’e-mail.
7 – Tester en local
- Re-lance le serveur pour recharger le
.env:Terminal1npm run dev - Clique sur « Mot de passe oublié », entre un e-mail valide.
- Observe la console :
1📧 Email envoyé : <202506021230@example.mailgun.org>
- Dans la boîte de réception (ou Mailgun sandbox) tu trouves bien ton mail avec le lien complet
http://localhost:5173/reset-password?token=….
8 – Ajout d’un feedback visuel côté client
Dans la page /forgot-password, profite du hook useNavigation() pour désactiver le bouton pendant l’envoi :
1const navigation = useNavigation()2/* … */3<button4type="submit"5disabled={navigation.state === "submitting"}6className="btn-primary w-full"7>8{navigation.state === "submitting" ? "Envoi en cours…" : "Réinitialiser"}9</button>
Tip
9 – Sécuriser et typiser tes variables .env (aperçu)
Un oubli d’SMTP_HOST en production = crash à chaud.
Dans une prochaine leçon nous verrons comment valider le fichier .env avec Zod au démarrage du serveur pour prévenir ces erreurs avant qu’elles ne touchent tes utilisateurs.
Points clés à retenir
- Nodemailer est la solution la plus simple pour envoyer un mail depuis Node.
- Utilise un provider SMTP (Mailgun, Resend…) gratuit jusqu’à quelques milliers de messages.
- Centralise la configuration : transporteur, helper
sendEmail, helper métiersendPasswordResetEmail. - Lien absolu
FRONTEND_URLindispensable pour cliquer depuis la messagerie. - Le token stocké en base est unique et temporaire (30 min dans l’exemple).
- En dev, regarde la console pour vérifier l’envoi ; en prod, branche un vrai domaine.
- Désactive le bouton via
navigation.state === "submitting"pour une UX soignéTu as désormais un système d’e-mail prêt pour la production !
Dans la prochaine étape, on s’assure que toutes tes variables d’environnement sont validées et typées afin d’éviter les surprises lors du déploiement.
Comprendre les concepts fondamentaux
Quelle est la principale différence entre les composants client et serveur dans React ?
Optimisation des performances
Quelle technique est recommandée pour éviter les rendus inutiles dans React ?
Architecture des données
Quel hook permet de gérer les effets de bord dans un composant React ?