Tutorial
Enviar emails con Next.js
Envía emails desde route handlers y server actions de Next.js con el SDK de Node.js de Emailit, mantén la clave de API en el servidor y verifica los webhooks.
En esta guía se explica cómo enviar emails desde una aplicación Next.js con el SDK @emailit/node, tanto desde un route handler como desde una server action, y cómo verificar los webhooks de Emailit. Los ejemplos usan el App Router; el Pages Router se trata al final de la sección de envío.
Requisitos previos
- Next.js 14 o posterior. El SDK requiere Node.js 18 o posterior.
- Un dominio de envío verificado, por ejemplo
acme.com. - Una clave de API. Basta con una clave Sending Only limitada a tu dominio.
- 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.
Instalar el SDK
npm install @emailit/node server-onlyserver-only hace que la compilación falle si un componente de cliente importa el módulo que contiene tu clave.
Configurar la clave de API
Añade la clave a .env.local para el desarrollo y a las variables de entorno de tu proveedor de hosting (por ejemplo, la configuración del proyecto en Vercel) para producción:
EMAILIT_API_KEY=secret_••••••••••••••••••••••••••••••••
EMAILIT_WEBHOOK_SECRET=whsec_••••••••Crea un único cliente en un módulo solo de servidor:
import 'server-only';
import { Emailit } from '@emailit/node';
export const emailit = new Emailit(process.env.EMAILIT_API_KEY!);El SDK aún no tiene declaraciones de TypeScript. Si el compilador da error, añade un archivo de declaraciones:
declare module '@emailit/node';Enviar desde un route handler
Un route handler es el sitio adecuado para los envíos que inicia tu propio frontend u otros servicios. Este handler de un formulario de contacto envía a una dirección interna fija, así que los visitantes no pueden usarlo para enviar emails a cualquiera:
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 });
}
}Llámalo desde el navegador con fetch('/api/contact', { method: 'POST', body: JSON.stringify(data) }). La clave nunca sale del servidor.
Enviar desde una server action
Las server actions se ejecutan en el servidor, así que pueden usar el cliente directamente:
'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 acepta el alias o el ID tem_ de una plantilla; consulta Plantillas. Las server actions son endpoints públicos, así que valida los datos de entrada y añade tu protección habitual contra bots y abusos antes de enviar.
Pages Router
Con el Pages Router, envía en su lugar desde una ruta de API:
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 });
}Enviar por SMTP
Si ya usas Nodemailer, configúralo con el relay de Emailit dentro de un route handler o una server action (SMTP necesita el runtime de Node.js, no el 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 },
});Después, llama a await mailer.sendMail({ from, to, subject, html }). Las funciones serverless abren una conexión SMTP nueva en la mayoría de las invocaciones, así que la API suele ser más rápida en Vercel y en plataformas similares. Consulta Configuración SMTP.
Recibir webhooks
Crea un webhook que apunte a https://your-app.com/api/webhooks/emailit. El handler debe verificar la firma con el cuerpo en bruto, así que léelo con request.text() antes de analizarlo:
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 });
}Devuelve un 2xx en menos de 30 segundos; las demás respuestas se reintentan. Consulta Firma de las peticiones y Tipos de eventos.
Consejos para producción
- Haz los envíos en el servidor. Solo
lib/emailit.ts, los route handlers y las server actions deben acceder a la clave.server-onlylo garantiza en tiempo de compilación. - No dejes nunca que el cliente elija el remitente. Fija
fromen el código y aceptatodesde el navegador solo cuando sea la dirección del propio usuario que ha iniciado sesión. - Protege los endpoints públicos. Limita la frecuencia de los formularios de contacto y de las acciones de registro, y añade un CAPTCHA si los encuentran los bots. Cada email cuesta créditos y cuenta para tus límites de envío.
- Define las variables por entorno. Usa claves distintas para los despliegues de vista previa y de producción para poder revocar una sin afectar a la otra.