Tutorial
Invia email con Next.js
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.
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
npm install @emailit/node server-onlyserver-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:
EMAILIT_API_KEY=secret_••••••••••••••••••••••••••••••••
EMAILIT_WEBHOOK_SECRET=whsec_••••••••Crea un unico client in un modulo solo server:
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:
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:
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:
'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 },
});
}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:
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):
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:
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-onlylo impone in fase di build. - Non lasciare mai che il client scelga il mittente. Scrivi
fromdirettamente nel codice e accettatodal 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.