Aller au contenu
Docs

Tutoriel

Envoyer des e-mails avec Next.js

Envoyez des e-mails depuis les route handlers et les server actions de Next.js avec le SDK Node.js d’Emailit, gardez la clé API côté serveur et vérifiez les webhooks.

Mis à jour le 1 oct. 2026

Ce guide montre comment envoyer des e-mails depuis une application Next.js avec le SDK @emailit/node, à la fois depuis un route handler et depuis une server action, et comment vérifier les webhooks Emailit. Les exemples utilisent l’App Router ; le Pages Router est traité à la fin de la partie consacrée à l’envoi.

Prérequis

  • Next.js 14 ou version ultérieure. Le SDK nécessite Node.js 18 ou version ultérieure.
  • Un domaine d’envoi vérifié, par exemple acme.com.
  • Une clé API. Une clé Sending Only limitée à votre domaine suffit.
  • Tant que votre espace de travail n’a pas l’accès production, vous ne pouvez envoyer qu’aux adresses e-mail des comptes des membres de l’espace de travail.

Installer le SDK

Terminal
npm install @emailit/node server-only

server-only fait échouer le build si un composant client importe le module qui contient votre clé.

Configurer votre clé API

Ajoutez la clé dans .env.local pour le développement, et dans les variables d’environnement de votre hébergeur (par exemple les paramètres du projet Vercel) pour la production :

.env.local
EMAILIT_API_KEY=secret_••••••••••••••••••••••••••••••••
EMAILIT_WEBHOOK_SECRET=whsec_••••••••

Créez un seul client dans un module réservé au serveur :

lib/emailit.ts
import 'server-only';
import { Emailit } from '@emailit/node';

export const emailit = new Emailit(process.env.EMAILIT_API_KEY!);

Le SDK n’a pas encore de déclarations TypeScript. Si le compilateur signale une erreur, ajoutez un fichier de déclaration :

types/emailit.d.ts
declare module '@emailit/node';

Envoyer depuis un route handler

Un route handler est l’endroit idéal pour les envois déclenchés par votre propre frontend ou par d’autres services. Ce handler de formulaire de contact envoie à une adresse interne fixe : les visiteurs ne peuvent donc pas l’utiliser pour envoyer des e-mails à n’importe qui :

app/api/contact/route.ts
import { emailit } from '@/lib/emailit';

export async function POST(request: Request) {
  const { email, message } = await request.json();

  if (typeof email !== 'string' || typeof message !== 'string' || !email.includes('@')) {
    return Response.json({ error: 'Invalid input' }, { status: 400 });
  }

  try {
    const sent = await emailit.emails.send({
      from: 'Acme website <website@acme.com>',
      to: 'support@acme.com',
      reply_to: email,
      subject: 'New contact form message',
      text: message,
    });
    return Response.json({ id: sent.id });
  } catch (error) {
    console.error(error);
    return Response.json({ error: 'Could not send the message' }, { status: 502 });
  }
}

Appelez-le depuis le navigateur avec fetch('/api/contact', { method: 'POST', body: JSON.stringify(data) }). La clé ne quitte jamais le serveur.

Envoyer depuis une server action

Les server actions s’exécutent sur le serveur : elles peuvent donc utiliser le client directement :

app/signup/actions.ts
'use server';

import { emailit } from '@/lib/emailit';

export async function signUp(formData: FormData) {
  const email = formData.get('email');
  if (typeof email !== 'string' || !email.includes('@')) return;

  // Create the account here, then send the welcome email.
  await emailit.emails.send({
    from: 'Acme <hello@acme.com>',
    to: email,
    template: 'welcome',
    variables: { email },
  });
}
app/signup/page.tsx
import { signUp } from './actions';

export default function SignupPage() {
  return (
    <form action={signUp}>
      <input type="email" name="email" required />
      <button type="submit">Create account</button>
    </form>
  );
}

template accepte un alias de modèle ou un ID tem_ ; consultez Modèles. Les server actions sont des endpoints publics : validez donc les entrées et ajoutez vos protections habituelles contre les robots et les abus avant d’envoyer.

Pages Router

Avec le Pages Router, envoyez plutôt depuis une route d’API :

pages/api/contact.ts
import type { NextApiRequest, NextApiResponse } from 'next';
import { emailit } from '@/lib/emailit';

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  if (req.method !== 'POST') return res.status(405).end();
  const sent = await emailit.emails.send({
    from: 'Acme website <website@acme.com>',
    to: 'support@acme.com',
    subject: 'New contact form message',
    text: String(req.body.message ?? ''),
  });
  res.status(200).json({ id: sent.id });
}

Envoyer plutôt via SMTP

Si vous utilisez déjà Nodemailer, configurez-le avec le relais Emailit dans un route handler ou une server action (le SMTP nécessite le runtime Node.js, pas le runtime Edge) :

lib/mailer.ts
import 'server-only';
import nodemailer from 'nodemailer';

export const mailer = nodemailer.createTransport({
  host: 'smtp.emailit.com',
  port: 587,
  secure: false,
  requireTLS: true,
  auth: { user: 'emailit', pass: process.env.EMAILIT_API_KEY },
});

Appelez ensuite await mailer.sendMail({ from, to, subject, html }). Les fonctions serverless ouvrent une nouvelle connexion SMTP à la plupart des invocations : l’API est donc généralement plus rapide sur Vercel et les plateformes similaires. Consultez Paramètres SMTP.

Recevoir des webhooks

Créez un webhook qui pointe vers https://your-app.com/api/webhooks/emailit. Le handler doit vérifier la signature à partir du corps brut : lisez-le donc avec request.text() avant de l’analyser :

app/api/webhooks/emailit/route.ts
import { createHmac, timingSafeEqual } from 'node:crypto';

export const runtime = 'nodejs';

type EmailitEvent = {
  event_id: string;
  type: string;
  data: { object: Record<string, unknown> };
};

function isValid(rawBody: string, signature: string | null, timestamp: string | null) {
  if (!signature || !timestamp) return false;
  const age = Math.abs(Math.floor(Date.now() / 1000) - Number(timestamp));
  if (!Number.isFinite(age) || age > 300) return false;
  const expected = createHmac('sha256', process.env.EMAILIT_WEBHOOK_SECRET!)
    .update(`${timestamp}.${rawBody}`)
    .digest('hex');
  const a = Buffer.from(expected);
  const b = Buffer.from(signature);
  return a.length === b.length && timingSafeEqual(a, b);
}

export async function POST(request: Request) {
  const rawBody = await request.text();
  const valid = isValid(
    rawBody,
    request.headers.get('x-emailit-signature'),
    request.headers.get('x-emailit-timestamp'),
  );
  if (!valid) return new Response('Invalid signature', { status: 401 });

  // Each request carries an array of up to 100 events.
  const events = JSON.parse(rawBody) as EmailitEvent[];
  for (const event of events) {
    if (event.type === 'email.bounced') {
      // Flag event.data.object.to in your database.
    }
  }

  return new Response(null, { status: 200 });
}

Renvoyez un 2xx dans les 30 secondes ; pour toute autre réponse, Emailit réessaie. Consultez Signature des requêtes et Types d’événements.

Conseils pour la production

  • Gardez les envois côté serveur. Seuls lib/emailit.ts, les route handlers et les server actions doivent accéder à la clé. server-only l’impose au moment du build.
  • Ne laissez jamais le client choisir l’expéditeur. Codez from en dur, et n’acceptez to depuis le navigateur que s’il s’agit de l’adresse de l’utilisateur connecté.
  • Protégez les endpoints publics. Limitez le débit des formulaires de contact et des actions d’inscription, et ajoutez un CAPTCHA si des robots les trouvent. Chaque e-mail coûte des crédits et compte dans vos limites d’envoi.
  • Définissez les variables par environnement. Utilisez des clés distinctes pour les déploiements de prévisualisation et de production, afin de pouvoir révoquer l’une sans affecter l’autre.

Étapes suivantes

La gestion des erreurs et Nodemailer plus en détail.
Pièces jointes, programmation et suivi.
Concevez vos e-mails une fois, envoyez-les par alias.
Portées, limitation à un domaine et rotation.

Cette page vous a-t-elle été utile ?

Merci pour votre retour.

Merci, nous lisons chaque message.