Node.js (@frontmail/node)
@frontmail/node es para servidores, funciones serverless y scripts. Se autentica con una clave
privada, así que no se aplican las comprobaciones de origen ni de Turnstile, y además permite los
envíos por lotes, el historial y los esquemas de las plantillas.
npm install @frontmail/nodeRequiere Node.js 20+ (usa el fetch integrado). También funciona en Bun, Deno y en los entornos
edge que ofrecen fetch.
Crear un cliente
Sección titulada «Crear un cliente»import { Frontmail } from '@frontmail/node';
const frontmail = new Frontmail({ privateKey: process.env.FRONTMAIL_PRIVATE_KEY!, // sk_… // retry: { retries: 3 }, timeoutMs: 15_000, apiUrl: 'https://api.frontmail.dev'});Sin privateKey, el cliente lee FRONTMAIL_PRIVATE_KEY (y FRONTMAIL_API_URL) del entorno. No
expongas nunca la clave privada en un bundle para el navegador: guárdala en variables de entorno o
en un gestor de secretos. Consulta Claves públicas y privadas.
El paquete se niega a funcionar en un navegador web: al importarlo allí muestra una advertencia y
new Frontmail() lanza private_key_in_browser (puedes desactivarlo con
dangerouslyAllowPrivateKeyInBrowser: true solo en herramientas internas en equipos de confianza).
En el navegador, usa @frontmail/browser con la clave pública.
const result = await frontmail.send({ serviceId: 'svc_01J9…', // opcional: omítelo para usar el servicio por defecto templateId: 'tpl_order', params: { order_id: 'A-1042', items: [{ title: 'Habitación', qty: 2, price: 89 }], total: 178, currency: 'EUR', },});console.log(result.messageId, result.status); // 'queued' | 'held'Otros campos de entrada: idempotencyKey (recomendado cuando el envío lo desencadena algo que puede
repetirse, p. ej. un webhook: usa su ID de evento), attachments, locale, signal.
locale (p. ej. 'de') envía el correo en ese idioma de la plantilla:
coincidencia exacta, después el mismo idioma base (de-AT → de) y, si no, el idioma
predeterminado. result.locale es el idioma usado:
const { locale } = await frontmail.send({ templateId: 'tpl_order', params: { order_id: 'A-1042' }, locale: user.language });Envíos por lotes
Sección titulada «Envíos por lotes»Envía hasta 100 mensajes en una sola solicitud. Cada mensaje se valida y se factura por
separado: un mensaje no válido no hace fallar a los demás. sendBatch no lanza errores por los
fallos de mensajes concretos; comprueba ok en cada resultado.
const results = await frontmail.sendBatch([ { serviceId: 'svc_01J9…', templateId: 'tpl_reminder', params: { name: 'Jana' }, idempotencyKey: 'reminder-42-jana' }, { serviceId: 'svc_01J9…', templateId: 'tpl_reminder', params: { name: 'Petr' }, idempotencyKey: 'reminder-42-petr' },]);
for (const r of results) { if (!r.ok) console.error(r.index, r.error.code); else console.log(r.index, r.messageId, r.status);}Un lote cuenta como una sola solicitud para los límites de solicitudes.
Si tienes más de 100 mensajes, divídelos en bloques. Cada mensaje puede tener su propio locale; los
resultados correctos incluyen el locale usado.
Historial
Sección titulada «Historial»const page = await frontmail.history({ status: 'failed', templateId: 'tpl_order', from: '2026-09-01T00:00:00Z', limit: 50,});
for (const m of page.items) console.log(m.messageId, m.status, m.to, m.subject);
// Página siguienteif (page.nextCursor) await frontmail.history({ cursor: page.nextCursor });
// O recorre todofor await (const m of frontmail.history({ status: 'bounced' })) console.log(m.to);Filtros: status, templateId, serviceId, from, to, q (búsqueda por destinatario),
limit (≤ 100), cursor. Hacer await de history() devuelve una página; for await recorre
todas las páginas de forma perezosa. El historial abarca el periodo de conservación de tu plan.
Estado de un mensaje
Sección titulada «Estado de un mensaje»const msg = await frontmail.getMessage('msg_01J9Z3K7Q2');// { messageId, status, createdAt, sentAt, templateId, serviceId, events: [{ type, at }, …] }Plantillas
Sección titulada «Plantillas»const templates = await frontmail.templates.list(); // esquemas de parámetros, los usa `frontmail types`const contact = await frontmail.templates.get('tpl_contact');contact.locales; // ['en', 'de', 'pt-BR']: primero el idioma predeterminado, después las traduccionesfrontmail types añade los idiomas de cada plantilla a su comentario de documentación y genera un
tipo unión para la opción locale:
/** Contact form (tpl_contact) – languages: en (default), de, pt-BR */export interface ContactFormParams { /* … */ }/** Languages of tpl_contact (default first) – for the `locale` send option. */export type ContactFormLocale = "en" | "de" | "pt-BR";El paquete incluye la CLI frontmail: consulta Parámetros tipados.
Proyecto de ejemplo: examples/node.