Tutorial
Invia email con Node.js
Invia email da Node.js con l’SDK @emailit/node o con Nodemailer via SMTP, e verifica i webhook di Emailit in una route Express.
Questa guida mostra come inviare email da un’app Node.js con l’SDK ufficiale @emailit/node, come usare in alternativa Nodemailer via SMTP e come ricevere webhook firmati in Express.
Prerequisiti
- Node.js 18 o versioni successive.
- Un dominio di invio verificato, ad esempio
acme.com. - Una chiave API. Per inviare basta una chiave di solo invio.
- Finché il workspace non ha l’accesso alla produzione, puoi inviare solo agli indirizzi email degli account dei membri del workspace. Durante i test usa il tuo indirizzo.
Installa l’SDK
npm install @emailit/nodeIl pacchetto è solo ESM. Usa la sintassi import, oppure await import('@emailit/node') dal codice CommonJS.
Configura la chiave API
Tieni la chiave fuori dal codice. Mettila in una variabile d’ambiente:
EMAILIT_API_KEY=secret_••••••••••••••••••••••••••••••••Carica il file con node --env-file=.env (Node.js 20.6 e versioni successive), con un pacchetto come dotenv o con le impostazioni dei secret della tua piattaforma. Aggiungi .env a .gitignore.
Invia un’email
Crea un unico client e riutilizzalo tra le richieste:
import { Emailit } from '@emailit/node';
const emailit = new Emailit(process.env.EMAILIT_API_KEY);
const email = await emailit.emails.send({
from: 'Acme <hello@acme.com>',
to: 'ada@example.com',
subject: 'Welcome to Acme',
html: '<p>Thanks for signing up.</p>',
text: 'Thanks for signing up.',
});
console.log(email.id); // em_…Eseguilo con node --env-file=.env send.mjs. L’email compare in Email APIEmails in pochi secondi.
In un’app Express, invia dalla route che genera l’email:
import express from 'express';
import { Emailit } from '@emailit/node';
const app = express();
const emailit = new Emailit(process.env.EMAILIT_API_KEY);
app.post('/signup', express.json(), async (req, res) => {
const { email } = req.body;
// Create the user here, then send the welcome email.
const sent = await emailit.emails.send({
from: 'Acme <hello@acme.com>',
to: email,
template: 'welcome',
variables: { email },
});
res.status(201).json({ emailId: sent.id });
});
app.listen(3000);template accetta l’alias di un template o un ID tem_, e variables lo compila. Vedi Template e l’elenco completo dei campi in Invia un’email.
Gestisci gli errori
L’SDK lancia errori tipizzati, così puoi reagire a ogni tipo di errore:
import {
AuthenticationException,
RateLimitException,
UnprocessableEntityException,
ApiErrorException,
} from '@emailit/node';
try {
await emailit.emails.send(message);
} catch (err) {
if (err instanceof RateLimitException) {
// 429: wait err.jsonBody.retry_after seconds, then retry
} else if (err instanceof AuthenticationException) {
// 401: the API key is missing or invalid
} else if (err instanceof UnprocessableEntityException) {
// 422: for example, the from domain isn't verified
} else if (err instanceof ApiErrorException) {
console.error(err.httpStatus, err.jsonBody);
} else {
throw err;
}
}Invia con SMTP
Se la tua app usa già Nodemailer, indirizzalo all’SMTP relay di Emailit. Installalo con npm install nodemailer:
import nodemailer from 'nodemailer';
const transporter = nodemailer.createTransport({
host: 'smtp.emailit.com',
port: 587,
secure: false, // upgraded with STARTTLS after connecting
requireTLS: true,
auth: {
user: 'emailit',
pass: process.env.EMAILIT_API_KEY,
},
});
const info = await transporter.sendMail({
from: 'Acme <hello@acme.com>',
to: 'ada@example.com',
subject: 'Welcome to Acme',
text: 'Thanks for signing up.',
html: '<p>Thanks for signing up.</p>',
});
console.log(info.response); // 250 2.0.0 OK: queued as em_…Per la porta 465, imposta port: 465 e secure: true. Se la tua rete blocca la 587, usa la 2525 o la 2587 con le stesse impostazioni. Vedi Impostazioni SMTP.
Ricevi i webhook
Crea un webhook che punta a https://your-app.com/webhooks/emailit e copia il suo secret di firma (whsec_…) in EMAILIT_WEBHOOK_SECRET.
Emailit firma ogni richiesta con un HMAC-SHA256 di timestamp.rawBody, quindi la route deve leggere il corpo grezzo. Su questa route usa express.raw(), non express.json():
import express from 'express';
import { createHmac, timingSafeEqual } from 'node:crypto';
function verifyEmailitSignature(rawBody, signature, timestamp, secret) {
if (!signature || !timestamp) return false;
const age = Math.abs(Math.floor(Date.now() / 1000) - Number(timestamp));
if (!Number.isFinite(age) || age > 300) return false; // reject replays older than 5 minutes
const expected = createHmac('sha256', 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 const webhooks = express.Router();
webhooks.post('/webhooks/emailit', express.raw({ type: 'application/json' }), (req, res) => {
const rawBody = req.body.toString('utf8');
const valid = verifyEmailitSignature(
rawBody,
req.get('x-emailit-signature'),
req.get('x-emailit-timestamp'),
process.env.EMAILIT_WEBHOOK_SECRET,
);
if (!valid) return res.status(401).send('Invalid signature');
// The body is an array of up to 100 events.
const events = JSON.parse(rawBody);
for (const event of events) {
switch (event.type) {
case 'email.delivered':
// event.data.object.id is the em_ ID
break;
case 'email.bounced':
case 'email.complained':
// stop emailing event.data.object.to
break;
}
}
res.sendStatus(200);
});Monta il router con app.use(webhooks) prima di qualsiasi middleware globale express.json(). Restituisci un 2xx entro 30 secondi; qualsiasi altra risposta viene ritentata. Per i dettagli, leggi Firma delle richieste e Nuovi tentativi ed errori.
Consigli per la produzione
- Limita il permesso della chiave. Per il server dell’app usa una chiave di solo invio limitata al tuo dominio di invio. Riserva le chiavi con accesso completo agli script di amministrazione.
- Resta entro il limite di frequenza. Per impostazione predefinita, i nuovi workspace possono inviare 2 email al secondo e 5000 al giorno, condivise tra API e SMTP. Esegui gli invii massivi da una coda. Con Nodemailer,
pool: true, rateLimit: 2mantiene un transport in pool a 2 messaggi al secondo. Vedi Limiti e quote. - Rendi sicuri i nuovi tentativi. Se ritenti un invio dopo un timeout, invia un header
Idempotency-Keycosì Emailit non invia due volte. L’SDK non imposta header personalizzati, quindi per queste chiamate usafetch; vedi Idempotenza. - Elabora i webhook in modo idempotente. Salva ogni
event_idche hai gestito e salta i duplicati, ed esegui il lavoro lento in un job in background dopo aver restituito200. - Usi TypeScript? L’SDK non ha ancora dichiarazioni di tipo. Aggiungi
declare module '@emailit/node';a un file.d.tsnel tuo progetto.