Vai al contenuto
Docs

Tutorial

Invia email dai route handler e dalle server action di Next.js con l’SDK Node.js di Emailit, tieni la chiave API sul server e verifica i webhook.

Aggiornato il 1 ott 2026

Questa guida mostra come inviare email da un’app Next.js con l’SDK @emailit/node, sia da un route handler sia da una server action, e come verificare i webhook di Emailit. Gli esempi usano l’App Router; il Pages Router è trattato alla fine della sezione sull’invio.

Prerequisiti

  • Next.js 14 o versioni successive. L’SDK richiede Node.js 18 o versioni successive.
  • Un dominio di invio verificato, ad esempio acme.com.
  • Una chiave API. Basta una chiave di solo invio limitata al tuo dominio.
  • Finché il workspace non ha l’accesso alla produzione, puoi inviare solo agli indirizzi email degli account dei membri del workspace.

Installa l’SDK

Terminal
npm install @emailit/node server-only

server-only fa fallire la build se un client component importa il modulo che contiene la chiave.

Configura la chiave API

Aggiungi la chiave a .env.local per lo sviluppo e alle variabili d’ambiente del tuo hosting (ad esempio, le impostazioni del progetto Vercel) per la produzione:

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

Crea un unico client in un modulo solo server:

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

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

L’SDK non ha ancora dichiarazioni TypeScript. Se il compilatore segnala errori, aggiungi un file di dichiarazione:

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

Invia da un route handler

Un route handler è il posto giusto per gli invii avviati dal tuo frontend o da altri servizi. Questo handler per un modulo di contatto invia a un indirizzo interno fisso, così i visitatori non possono usarlo per scrivere a persone qualsiasi:

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 });
  }
}

Chiamalo dal browser con fetch('/api/contact', { method: 'POST', body: JSON.stringify(data) }). La chiave non lascia mai il server.

Invia da una server action

Le server action vengono eseguite sul server, quindi possono usare direttamente il client:

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 accetta l’alias di un template o un ID tem_; vedi Template. Le server action sono endpoint pubblici, quindi convalida l’input e aggiungi le consuete protezioni contro bot e abusi prima di inviare.

Pages Router

Con il Pages Router, invia invece da una API route:

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 });
}

Invia con SMTP

Se usi già Nodemailer, configuralo con il relay di Emailit all’interno di un route handler o di una server action (SMTP richiede il runtime Node.js, non il 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 },
});

Poi chiama await mailer.sendMail({ from, to, subject, html }). Le funzioni serverless aprono una nuova connessione SMTP quasi a ogni invocazione, quindi su Vercel e piattaforme simili l’API di solito è più veloce. Vedi Impostazioni SMTP.

Ricevi i webhook

Crea un webhook che punta a https://your-app.com/api/webhooks/emailit. L’handler deve verificare la firma sul corpo grezzo, quindi leggilo con request.text() prima di analizzarlo:

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 });
}

Restituisci un 2xx entro 30 secondi; le altre risposte vengono ritentate. Vedi Firma delle richieste e Tipi di evento.

Consigli per la produzione

  • Tieni gli invii sul server. Solo lib/emailit.ts, i route handler e le server action devono accedere alla chiave. server-only lo impone in fase di build.
  • Non lasciare mai che il client scelga il mittente. Scrivi from direttamente nel codice e accetta to dal browser solo quando è l’indirizzo dell’utente che ha effettuato l’accesso.
  • Proteggi gli endpoint pubblici. Limita la frequenza dei moduli di contatto e delle azioni di registrazione, e aggiungi un CAPTCHA se i bot li trovano. Ogni email costa crediti e rientra nei limiti di invio.
  • Imposta le variabili per ambiente. Usa chiavi separate per i deployment di anteprima e di produzione, così puoi revocarne una senza effetti sull’altra.

Passaggi successivi

Gestione degli errori e Nodemailer più nel dettaglio.
Allegati, programmazione e tracciamento.
Progetta le email una volta e inviale per alias.
Permessi, limitazioni a un dominio e rotazione.

Questa pagina ti è stata utile?

Grazie del feedback.

Grazie, leggiamo ogni messaggio.