Tutorial
Enviar emails con Node.js
Envía emails desde Node.js con el SDK @emailit/node o con Nodemailer por SMTP, y verifica los webhooks de Emailit en una ruta de Express.
En esta guía se explica cómo enviar emails desde una aplicación Node.js con el SDK oficial @emailit/node, cómo usar en su lugar Nodemailer por SMTP y cómo recibir webhooks firmados en Express.
Requisitos previos
- Node.js 18 o posterior.
- Un dominio de envío verificado, por ejemplo
acme.com. - Una clave de API. Para enviar basta con una clave Sending Only.
- Hasta que tu espacio de trabajo tenga acceso de producción, solo puedes enviar a las direcciones de email de las cuentas de los miembros del espacio de trabajo. Usa tu propia dirección mientras haces pruebas.
Instalar el SDK
npm install @emailit/nodeEl paquete es solo ESM. Usa la sintaxis import, o await import('@emailit/node') desde código CommonJS.
Configurar la clave de API
No pongas la clave en tu código. Guárdala en una variable de entorno:
EMAILIT_API_KEY=secret_••••••••••••••••••••••••••••••••Carga el archivo con node --env-file=.env (Node.js 20.6 y posterior), con un paquete como dotenv o con la configuración de secretos de tu plataforma. Añade .env a .gitignore.
Enviar un email
Crea un único cliente y reutilízalo en todas las peticiones:
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_…Ejecútalo con node --env-file=.env send.mjs. El email aparece en Email APIEmails en unos segundos.
En una aplicación Express, envía desde la ruta que desencadena el 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 acepta el alias o el ID tem_ de una plantilla, y variables la rellena. Consulta Plantillas y la lista completa de campos en Enviar un email.
Gestionar los errores
El SDK lanza errores tipados, así que puedes reaccionar a cada fallo:
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;
}
}Enviar por SMTP
Si tu aplicación ya usa Nodemailer, apúntalo al SMTP relay de Emailit. Instálalo 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_…Para el puerto 465, define port: 465 y secure: true. Si tu red bloquea el 587, usa el 2525 o el 2587 con la misma configuración. Consulta Configuración SMTP.
Recibir webhooks
Crea un webhook que apunte a https://your-app.com/webhooks/emailit y copia su secreto de firma (whsec_…) en EMAILIT_WEBHOOK_SECRET.
Emailit firma cada petición con un HMAC-SHA256 de timestamp.rawBody, así que la ruta debe leer el cuerpo en bruto. Usa express.raw() en esta ruta, no 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 el router con app.use(webhooks) antes de cualquier middleware global express.json(). Devuelve un 2xx en menos de 30 segundos; cualquier otra respuesta se reintenta. Para más detalles, lee Firma de las peticiones y Reintentos y fallos.
Consejos para producción
- Limita el permiso de la clave. Usa en el servidor de la aplicación una clave Sending Only limitada a tu dominio de envío. Reserva las claves Full Access para los scripts de administración.
- No superes tu límite de velocidad. Por defecto, los espacios de trabajo nuevos pueden enviar 2 emails por segundo y 5000 al día, compartidos entre la API y SMTP. Haz los envíos masivos desde una cola. Con Nodemailer,
pool: true, rateLimit: 2mantiene un transporte con pool a 2 mensajes por segundo. Consulta Límites y cuotas. - Haz que los reintentos sean seguros. Si reintentas un envío después de que se agote el tiempo de espera, envía una cabecera
Idempotency-Keypara que Emailit no lo envíe dos veces. El SDK no permite cabeceras personalizadas, así que usafetchpara esas llamadas; consulta Idempotencia. - Procesa los webhooks de forma idempotente. Guarda cada
event_idque hayas procesado y omite los duplicados, y haz el trabajo lento en una tarea en segundo plano después de devolver200. - ¿Usas TypeScript? El SDK aún no tiene declaraciones de tipos. Añade
declare module '@emailit/node';a un archivo.d.tsde tu proyecto.