E-mail de bienvenue, réinitialisation de mot de passe, formulaire de contact, facture en pièce jointe… Tôt ou tard, ton application devra envoyer des e-mails avec Node.js. Dans ce guide pas à pas, tu apprends à utiliser Nodemailer de A à Z : configuration SMTP, e-mails HTML, pièces jointes, tests sans spammer personne, formulaire de contact sécurisé et bonnes pratiques pour ne pas finir en spam.
Envoyer des e-mails avec Node.js est l’une des tâches les plus courantes d’une application web, et pourtant l’une des plus mal maîtrisées. Entre les identifiants SMTP qui traînent dans le code, les e-mails qui arrivent dans les indésirables et les formulaires de contact transformés en machine à spam, les pièges sont nombreux.

La bonne nouvelle : avec Nodemailer, le module de référence pour l’envoi d’e-mails en Node.js, tout devient simple. Il n’a aucune dépendance, fonctionne avec n’importe quel serveur SMTP et gère le HTML, les pièces jointes, les images intégrées et bien plus.

- Comprendre ce qu’est SMTP et quel service choisir.
- Configurer Nodemailer proprement, avec les identifiants dans un fichier
.env. - Envoyer un e-mail texte, HTML, avec pièces jointes et images.
- Tester tes envois sans jamais toucher une vraie boîte mail.
- Créer un formulaire de contact sécurisé avec Express.
- Améliorer la délivrabilité (SPF, DKIM, DMARC).
Comment fonctionne l’envoi d’un e-mail ?
Ton application ne livre pas l’e-mail directement dans la boîte du destinataire. Elle le remet à un serveur SMTP (Simple Mail Transfer Protocol), un peu comme tu déposes une lettre à la poste. C’est ce serveur qui se charge de la transmettre au serveur de messagerie du destinataire (Gmail, Outlook…), qui la place dans sa boîte de réception… ou dans ses indésirables.
Pour envoyer des e-mails avec Node.js, il te faut donc un compte sur un serveur SMTP. Plusieurs options :
| Option | Idéal pour | Points forts | Limites |
|---|---|---|---|
| Mailpit / Ethereal | Développement et tests | Gratuit, aucun e-mail réel envoyé | Ne livre rien aux vrais destinataires |
| Gmail (mot de passe d’application) | Petits projets personnels | Gratuit, rapide à configurer | Quotas faibles, expéditeur @gmail.com |
| SMTP de ton hébergeur | Sites vitrines | Inclus avec ton nom de domaine | Délivrabilité variable |
| Service transactionnel (Brevo, Mailgun, Postmark, Amazon SES…) | Applications en production | Excellente délivrabilité, statistiques | Payant au-delà d’un quota gratuit |
Un e-mail transactionnel est déclenché par une action de l’utilisateur (inscription, commande, mot de passe oublié). Un e-mail marketing est envoyé en masse (newsletter). Nodemailer est parfait pour le transactionnel. Pour une newsletter à des milliers d’abonnés, utilise plutôt un outil dédié, qui gère les désinscriptions et la réputation d’envoi.
Étape 1 : installer Nodemailer
Crée un projet et installe Nodemailer :
mkdir mailer-app && cd mailer-app
npm init -y
npm install nodemailerBashAjoute "type": "module" dans ton package.json pour utiliser la syntaxe import. Nodemailer n’a besoin d’aucune autre dépendance.
Étape 2 : protéger tes identifiants avec un fichier .env
Ne mets jamais ton mot de passe SMTP directement dans le code : il finirait tôt ou tard sur GitHub. On le place dans un fichier .env, exclu du dépôt :
# .env (à ne jamais commiter)
SMTP_HOST=smtp.exemple.com
SMTP_PORT=587
SMTP_USER[email protected]
SMTP_PASS=mot-de-passe-smtp
MAIL_FROM="Mon Site <[email protected]>"INI# .gitignore
node_modules/
.envTexteDepuis Node.js 20.6, plus besoin du paquet dotenv : l’option --env-file charge le fichier au démarrage.
{
"type": "module",
"scripts": {
"start": "node --env-file=.env server.js",
"dev": "node --env-file=.env --watch server.js"
}
}JSONSupprimer le fichier dans un nouveau commit ne suffit pas : il reste dans l’historique Git. Considère le mot de passe comme compromis et change-le immédiatement auprès de ton fournisseur SMTP.
Étape 3 : créer le transporteur
Le transporteur (transporter) est l’objet qui se connecte au serveur SMTP. On le crée une seule fois et on le réutilise pour tous les envois :
// mailer.js
import nodemailer from 'nodemailer';
export const transporteur = nodemailer.createTransport({
host: process.env.SMTP_HOST,
port: Number(process.env.SMTP_PORT),
secure: Number(process.env.SMTP_PORT) === 465, // true pour 465, false pour 587
auth: {
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASS,
},
});
// Vérifie la connexion au démarrage : une erreur de configuration se voit tout de suite
export async function verifierConnexion() {
await transporteur.verify();
console.log('✅ Serveur SMTP prêt');
}JavaScript| Port | Chiffrement | Option secure | Usage |
|---|---|---|---|
| 587 | STARTTLS (la connexion passe en chiffré) | false | Le plus courant, recommandé |
| 465 | TLS dès la connexion | true | Courant aussi (Gmail, OVH…) |
| 25 | Variable | false | Souvent bloqué par les hébergeurs, à éviter |
Sur le port 587, la connexion démarre en clair puis passe en chiffré avec la commande STARTTLS. Nodemailer le fait automatiquement dès que le serveur le propose. Pour l’exiger, ajoute requireTLS: true.
Étape 4 : envoyer ton premier e-mail avec Node.js
// envoyer.js
import { transporteur, verifierConnexion } from './mailer.js';
await verifierConnexion();
const info = await transporteur.sendMail({
from: process.env.MAIL_FROM,
to: '[email protected]',
subject: 'Bienvenue sur Mon Site 👋',
text: 'Bonjour,\n\nMerci pour ton inscription !\n\nÀ bientôt,\nL’équipe Mon Site',
html: `
<p>Bonjour,</p>
<p>Merci pour ton inscription ! 🎉</p>
<p>À bientôt,
<strong>L’équipe Mon Site</strong></p>
`,
});
console.log('E-mail envoyé :', info.messageId);JavaScriptLance-le avec node --env-file=.env envoyer.js. Les options principales de sendMail :
| Option | Rôle |
|---|---|
from | Expéditeur, idéalement au format "Nom <adresse>" |
to, cc, bcc | Destinataires (chaîne ou tableau) |
replyTo | Adresse utilisée quand le destinataire clique sur « Répondre » |
subject | Objet de l’e-mail |
text | Version texte brut |
html | Version HTML |
attachments | Pièces jointes |
Certains logiciels de messagerie (et certains lecteurs d’écran) affichent la version texte. Surtout, un e-mail uniquement HTML est un signal suspect pour les filtres anti-spam. Avec les deux versions, tu gagnes en accessibilité et en délivrabilité.
Étape 5 : tester sans envoyer de vrais e-mails
Pendant le développement, tu ne veux pas envoyer cinquante e-mails de test à de vraies adresses. Deux solutions gratuites.
Ethereal : un compte SMTP de test en une ligne
Ethereal est un faux service SMTP créé par l’auteur de Nodemailer. Les e-mails sont capturés et consultables dans le navigateur, mais jamais livrés.
import nodemailer from 'nodemailer';
const compte = await nodemailer.createTestAccount();
const transporteurTest = nodemailer.createTransport({
host: 'smtp.ethereal.email',
port: 587,
auth: { user: compte.user, pass: compte.pass },
});
const info = await transporteurTest.sendMail({
from: '[email protected]',
to: '[email protected]',
subject: 'Test',
text: 'Ceci est un test',
});
// Lien pour voir l'e-mail dans le navigateur
console.log('Aperçu :', nodemailer.getTestMessageUrl(info));JavaScriptMailpit : une boîte de réception locale
Mailpit tourne sur ton poste (ou dans Docker) : un serveur SMTP sur le port 1025 et une interface web sur le port 8025.
docker run -d --name mailpit -p 1025:1025 -p 8025:8025 axllent/mailpitBash# .env de développement
SMTP_HOST=localhost
SMTP_PORT=1025
SMTP_USER=
SMTP_PASS=INIOuvre http://localhost:8025 : tous les e-mails envoyés par ton application s’y affichent, avec le rendu HTML, la source et les pièces jointes. Si Docker est nouveau pour toi, notre guide Docker pour développeur web explique les bases.
Mailpit n’exige pas d’identifiants par défaut. Si SMTP_USER est vide, retire l’option auth du transporteur, sinon certaines versions tentent une authentification et échouent.
Étape 6 : pièces jointes et images intégrées
Les pièces jointes se déclarent dans le tableau attachments. Nodemailer accepte un chemin de fichier, un Buffer, un flux ou même une URL :
await transporteur.sendMail({
from: process.env.MAIL_FROM,
to: '[email protected]',
subject: 'Ta facture n°2026-0142',
text: 'Bonjour, tu trouveras ta facture en pièce jointe.',
attachments: [
{ filename: 'facture-2026-0142.pdf', path: './factures/2026-0142.pdf' },
{ filename: 'conditions.txt', content: 'Conditions générales de vente…' },
],
});JavaScriptPour afficher une image dans le corps de l’e-mail (un logo, par exemple), on l’attache avec un identifiant cid et on y fait référence dans le HTML :
await transporteur.sendMail({
from: process.env.MAIL_FROM,
to: '[email protected]',
subject: 'Bienvenue !',
html: '<img src="cid:logo-mon-site" alt="Mon Site" width="120"><p>Bienvenue parmi nous !</p>',
attachments: [{ filename: 'logo.png', path: './images/logo.png', cid: 'logo-mon-site' }],
});JavaScriptLa plupart des fournisseurs refusent les messages au-delà de 20 à 25 Mo (et l’encodage des pièces jointes augmente leur taille d’environ 33 %). Pour un gros fichier, envoie plutôt un lien de téléchargement sécurisé.
Étape 7 : des e-mails HTML qui s’affichent partout
Écrire du HTML pour les e-mails n’a rien à voir avec écrire une page web. Gmail, Outlook et Apple Mail ont chacun leurs limites : Outlook sur Windows utilise même le moteur de rendu de Word. Quelques règles qui t’éviteront bien des surprises :
- utilise des tableaux pour la mise en page plutôt que
flexougrid; - mets le CSS en ligne (
style="…") : beaucoup de clients ignorent les balises<style>; - garde une largeur maximale d’environ 600 px ;
- utilise des polices standard (Arial, Helvetica, Georgia) ;
- donne un
altà chaque image : beaucoup de clients les bloquent par défaut.
Pour garder un code propre, sépare le gabarit du code d’envoi avec une petite fonction :
// gabarits.js
const echapper = s => String(s)
.replaceAll('&', '&').replaceAll('<', '<')
.replaceAll('>', '>').replaceAll('"', '"');
export function emailBienvenue({ prenom, lien }) {
return {
subject: `Bienvenue ${prenom} !`,
text: `Bonjour ${prenom},\n\nConfirme ton adresse en ouvrant ce lien : ${lien}\n\nL’équipe Mon Site`,
html: `
<table width="100%" cellpadding="0" cellspacing="0" style="background:#f4f4f5;padding:24px 0">
<tr><td align="center">
<table width="600" cellpadding="0" cellspacing="0" style="background:#ffffff;border-radius:12px;font-family:Arial,sans-serif">
<tr><td style="padding:32px">
<h1 style="margin:0 0 16px;font-size:22px;color:#111827">Bonjour ${echapper(prenom)} 👋</h1>
<p style="font-size:16px;color:#374151;line-height:1.5">Merci pour ton inscription. Confirme ton adresse pour activer ton compte :</p>
<p style="text-align:center;margin:28px 0">
<a href="${echapper(lien)}" style="background:#111827;color:#ffffff;padding:14px 26px;border-radius:8px;text-decoration:none;font-weight:bold">Confirmer mon adresse</a>
</p>
<p style="font-size:13px;color:#6b7280">Tu n’es pas à l’origine de cette inscription ? Ignore simplement cet e-mail.</p>
</td></tr>
</table>
</td></tr>
</table>`,
};
}JavaScriptimport { emailBienvenue } from './gabarits.js';
const contenu = emailBienvenue({ prenom: 'Léa', lien: 'https://mon-site.be/confirmer?jeton=abc123' });
await transporteur.sendMail({ from: process.env.MAIL_FROM, to: '[email protected]', ...contenu });JavaScriptLe prénom vient de l’utilisateur. Sans la fonction echapper(), quelqu’un pourrait s’inscrire avec un « prénom » contenant du HTML et injecter un faux lien dans l’e-mail. C’est la même faille que le XSS sur une page web.
Pour des e-mails plus complexes, des outils comme MJML (un langage qui compile vers du HTML compatible avec tous les clients) ou React Email font gagner beaucoup de temps.
Étape 8 : un formulaire de contact sécurisé avec Express
Assemblons tout dans un cas réel : un formulaire de contact qui t’envoie les messages des visiteurs.
npm install express express-rate-limitBash// server.js
import express from 'express';
import rateLimit from 'express-rate-limit';
import { transporteur, verifierConnexion } from './mailer.js';
const app = express();
app.use(express.json({ limit: '20kb' }));
app.use(express.static('public'));
// 5 messages maximum par adresse IP et par heure
const limiteContact = rateLimit({
windowMs: 60 * 60 * 1000,
limit: 5,
message: { erreur: 'Trop de messages envoyés. Réessaie plus tard.' },
});
const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/;
app.post('/api/contact', limiteContact, async (req, res) => {
const { nom, email, message, site } = req.body ?? {};
// Pot de miel : champ invisible pour les humains, rempli par les robots
if (site) return res.status(200).json({ ok: true });
if (typeof nom !== 'string' || nom.trim().length < 2 || nom.length > 80) {
return res.status(400).json({ erreur: 'Nom invalide.' });
}
if (typeof email !== 'string' || !EMAIL_REGEX.test(email) || email.length > 254) {
return res.status(400).json({ erreur: 'Adresse e-mail invalide.' });
}
if (typeof message !== 'string' || message.trim().length < 10 || message.length > 5000) {
return res.status(400).json({ erreur: 'Le message doit contenir entre 10 et 5 000 caractères.' });
}
try {
await transporteur.sendMail({
from: process.env.MAIL_FROM, // toujours TON adresse comme expéditeur
to: process.env.SMTP_USER, // tu reçois le message
replyTo: `"${nom.replace(/["\r\n]/g, '')}" <${email}>`, // « Répondre » écrit au visiteur
subject: `Nouveau message de ${nom.replace(/[\r\n]/g, ' ')}`,
text: `Nom : ${nom}\nE-mail : ${email}\n\n${message}`,
});
res.status(200).json({ ok: true });
} catch (erreur) {
console.error('Envoi impossible :', erreur.message);
res.status(502).json({ erreur: 'Le message n’a pas pu être envoyé. Réessaie plus tard.' });
}
});
await verifierConnexion();
app.listen(3000, () => console.log('Serveur sur http://localhost:3000'));JavaScriptQuatre protections sont à l’œuvre dans ce code :
- La limitation de débit empêche un robot d’envoyer des milliers de messages.
- La validation refuse les champs vides, trop longs ou mal formés.
- Le pot de miel (honeypot) : un champ caché en CSS que seuls les robots remplissent.
- L’expéditeur fixe : le message part toujours de ton adresse, et l’adresse du visiteur va dans
replyTo.
Ton serveur SMTP n’est pas autorisé à envoyer au nom de [email protected] : le message serait rejeté ou classé en spam (c’est exactement ce que SPF et DMARC empêchent). Et si tu laisses le visiteur choisir le destinataire, ton formulaire devient un relais de spam. Le destinataire doit toujours être fixé côté serveur.
Étape 9 : la délivrabilité, ou comment éviter les spams
Un e-mail parfaitement envoyé peut quand même finir dans les indésirables. Les fournisseurs de messagerie vérifient que tu as le droit d’envoyer au nom de ton domaine, grâce à trois enregistrements DNS :
| Enregistrement | Rôle | Exemple simplifié |
|---|---|---|
| SPF | Liste les serveurs autorisés à envoyer pour ton domaine | v=spf1 include:_spf.fournisseur.com ~all |
| DKIM | Signe chaque e-mail avec une clé, pour prouver qu’il n’a pas été modifié | Clé publique fournie par ton service SMTP |
| DMARC | Indique quoi faire si SPF ou DKIM échoue, et où envoyer les rapports | v=DMARC1; p=quarantine; rua=mailto:[email protected] |
Depuis 2024, Gmail et Yahoo exigent ces enregistrements pour les expéditeurs qui envoient beaucoup d’e-mails, et les appliquent de plus en plus strictement à tous. Ton fournisseur SMTP t’indique exactement les valeurs à ajouter dans la zone DNS de ton domaine.
- Un
fromsur ton domaine, jamais une adresse Gmail pour un site professionnel. - Un objet clair, sans MAJUSCULES ni « !!! ».
- Une version texte en plus du HTML.
- Un lien de désinscription pour tout e-mail non transactionnel.
- Vérifie ton score avec un outil comme mail-tester.com.
Envoyer beaucoup d’e-mails : pool et file d’attente
Pour quelques e-mails par jour, un transporteur simple suffit. Au-delà, deux améliorations :
// Réutiliser les connexions SMTP au lieu d'en ouvrir une par e-mail
export const transporteur = nodemailer.createTransport({
pool: true,
maxConnections: 3,
maxMessages: 100,
host: process.env.SMTP_HOST,
port: Number(process.env.SMTP_PORT),
secure: Number(process.env.SMTP_PORT) === 465,
auth: { user: process.env.SMTP_USER, pass: process.env.SMTP_PASS },
});JavaScriptEt surtout, n’envoie pas l’e-mail pendant la requête HTTP de l’utilisateur. Si le serveur SMTP met trois secondes à répondre, ta page met trois secondes à s’afficher. Place l’envoi dans une file d’attente (BullMQ avec Redis, par exemple) : la requête répond immédiatement, un processus séparé envoie l’e-mail et réessaie automatiquement en cas d’échec.
Envoyer des e-mails avec Node.js : les erreurs les plus fréquentes
| Erreur | Cause probable | Solution |
|---|---|---|
EAUTH / 535 Authentication failed | Identifiants incorrects | Vérifie le .env. Avec Gmail, utilise un mot de passe d’application (2FA obligatoire), pas ton mot de passe habituel |
ETIMEDOUT / ECONNREFUSED | Port bloqué ou mauvais hôte | Essaie 587 puis 465 ; certains hébergeurs et réseaux bloquent le SMTP sortant |
self signed certificate | Certificat du serveur non reconnu | Corrige le certificat. N’utilise tls: { rejectUnauthorized: false } qu’en local |
wrong version number | secure: true sur le port 587 | secure: false sur 587, true sur 465 |
| E-mail reçu en spam | SPF, DKIM ou DMARC absents | Configure les trois enregistrements DNS |
process.env.SMTP_HOST vaut undefined | Le fichier .env n’est pas chargé | Lance Node.js avec --env-file=.env |

FAQ
Peut-on envoyer des e-mails avec Node.js sans serveur SMTP ?
Pas vraiment. Nodemailer peut tenter une livraison directe, mais les e-mails seraient presque systématiquement rejetés ou classés en spam. Utilise toujours un serveur SMTP ou l’API d’un service transactionnel.
Nodemailer fonctionne-t-il dans le navigateur ?
Non. Nodemailer s’exécute côté serveur, avec Node.js. Depuis une page web, tu envoies les données à ton API (avec fetch), et c’est ton serveur qui envoie l’e-mail. Tes identifiants SMTP ne doivent jamais se retrouver dans le code du navigateur.
Gmail convient-il pour un site en production ?
Pour un petit projet personnel, oui, avec un mot de passe d’application. Pour un site professionnel, préfère l’adresse de ton domaine et un service transactionnel : meilleure délivrabilité, quotas plus élevés et statistiques d’envoi.
Comment savoir si l’e-mail a bien été reçu ?
sendMail confirme seulement que le serveur SMTP a accepté le message. Pour savoir s’il a été livré, ouvert ou rejeté, il faut les webhooks ou le tableau de bord d’un service transactionnel.
Conclusion
Tu sais maintenant envoyer des e-mails avec Node.js de façon professionnelle : un transporteur Nodemailer configuré avec un fichier .env, des e-mails texte et HTML compatibles avec tous les clients, des pièces jointes, des tests avec Ethereal ou Mailpit, un formulaire de contact protégé contre les abus et une configuration DNS qui garantit la délivrabilité.
Garde en tête les règles de sécurité vues ici : elles rejoignent celles de l’OWASP Top 10, en particulier la validation des entrées et la protection des secrets. Pour aller plus loin, la documentation officielle de Nodemailer détaille toutes les options : OAuth2, envoi via API, signature DKIM, et bien plus.

