Saltar al contenido
Docs

Tutorial

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.

Actualizado el 1 oct 2026

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

Terminal
npm install @emailit/node server-only

server-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:

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

Crea un único cliente en un módulo solo de servidor:

lib/emailit.ts
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:

types/emailit.d.ts
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:

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

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:

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 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:

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

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):

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

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:

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

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-only lo garantiza en tiempo de compilación.
  • No dejes nunca que el cliente elija el remitente. Fija from en el código y acepta to desde 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.

Próximos pasos

La gestión de errores y Nodemailer con más detalle.
Adjuntos, programación y seguimiento.
Diseña los emails una vez y envíalos por su alias.
Permisos, limitación por dominio y rotación.

¿Te ha resultado útil esta página?

Gracias por tu opinión.

Gracias. Leemos todos los mensajes.