Cómo automatizar plantillas de email con MJML y Node.js

Ilustración sobre cómo automatizar plantillas de email con MJML y Node.js, mostrando código, vista previa responsive y flujo de compilación.

Automatizar plantillas de email con MJML y Node.js es una de esas decisiones técnicas que empiezan pareciendo opcionales, pero que terminan marcando una gran diferencia cuando un proyecto crece. Si solo tienes que preparar una newsletter puntual, quizá puedas trabajar de forma manual. Pero cuando necesitas crear emails de bienvenida, confirmaciones, avisos, campañas recurrentes o mensajes personalizados, copiar y pegar HTML deja de ser sostenible.

El desarrollo de emails tiene sus propias reglas. No funciona igual que una web moderna, ni responde igual en todos los clientes de correo. Gmail, Outlook, Apple Mail, Yahoo y otros entornos interpretan HTML y CSS de forma distinta. Por eso, antes de automatizar, conviene entender bien las limitaciones reales del CSS en clientes de correo y asumir que el email development sigue teniendo una capa de compatibilidad muy particular.

Aquí es donde MJML resulta especialmente útil. MJML permite escribir emails responsive con una sintaxis más clara y semántica, que después se compila a HTML compatible con distintos clientes de correo. Si todavía estás familiarizándote con esta herramienta, te recomiendo empezar por el artículo qué es MJML y por qué facilita la maquetación de emails responsive, porque te ayudará a entender mejor la base antes de pasar a un flujo más automatizado.

En este artículo vamos a ver cómo automatizar emails con MJML, cómo combinar MJML con Node.js, cómo insertar datos dinámicos y cómo preparar un sistema más mantenible para generar plantillas de email sin repetir trabajo innecesario.

Qué significa automatizar emails con MJML y Node.js

Automatizar emails con MJML y Node.js significa crear un flujo en el que las plantillas no se editan, compilan y revisan manualmente cada vez. En lugar de abrir un archivo HTML final, modificar textos a mano y confiar en que nada se rompa, trabajamos con una plantilla base en MJML, la procesamos con Node.js y generamos el HTML final de forma controlada.

Dicho de forma sencilla: MJML se ocupa de la estructura responsive del email y Node.js se ocupa de automatizar el proceso.

  • Compilar archivos .mjml a HTML final.
  • Insertar datos dinámicos como nombre, producto, enlace, fecha o asunto.
  • Reutilizar cabeceras, footers, botones y bloques comunes.
  • Validar errores antes de enviar una campaña.
  • Generar varias versiones de una misma plantilla.
  • Integrar el email en una app, un CRM o un backend.
  • Enviar correos mediante SMTP o una API externa.

La ventaja principal no es solo ahorrar tiempo. La automatización también reduce errores, mejora la consistencia visual y facilita el mantenimiento. Cuando tienes varias plantillas que comparten estructura, estilos y componentes, trabajar de forma modular se vuelve mucho más cómodo. De hecho, si estás construyendo un sistema de emails más amplio, también puede interesarte leer sobre maquetación modular de emails y bloques reutilizables.

Por qué usar MJML en lugar de HTML tradicional para emails

Crear emails directamente en HTML puede ser una tarea bastante ingrata. Muchas veces implica trabajar con tablas, estilos inline, condicionales específicos para Outlook y una estructura poco agradable de mantener.

En una web moderna podemos usar CSS Grid, Flexbox, variables CSS, componentes reutilizables y una gran cantidad de recursos actuales. En email, en cambio, hay que ser mucho más prudentes. No todos los clientes soportan las mismas propiedades, y algunas técnicas habituales en web pueden fallar en correo. Por eso, si vienes del frontend tradicional, te puede ayudar revisar las diferencias entre diseñar una web y diseñar un email.

MJML abstrae parte de esa complejidad. En lugar de escribir manualmente una estructura llena de tablas anidadas, puedes utilizar componentes como <mj-section>, <mj-column>, <mj-text>, <mj-button> o <mj-image>.

Por ejemplo, una estructura básica en MJML podría ser:

<mjml>
  <mj-body>
    <mj-section>
      <mj-column>
        <mj-text font-size="20px" color="#333333">
          Hola, Marta
        </mj-text>

        <mj-button href="https://martagonzalez.dev">
          Leer el artículo
        </mj-button>
      </mj-column>
    </mj-section>
  </mj-body>
</mjml>

Ese código no es el HTML final que recibirá la persona usuaria. MJML lo compila y genera una salida mucho más extensa, preparada para funcionar mejor en distintos clientes de correo. Esta separación entre código de trabajo y código final es precisamente lo que permite crear un flujo automatizado más limpio.

Instalar MJML en un proyecto Node.js

Para empezar a trabajar con MJML y Node.js, lo primero es crear un proyecto básico. Desde la terminal, puedes hacerlo así:

mkdir emails-mjml-node
cd emails-mjml-node
npm init -y

Después, instalamos MJML:

npm install mjml

Una estructura inicial sencilla podría ser la siguiente:

emails-mjml-node/
├── package.json
├── src/
│   ├── templates/
│   │   └── welcome.mjml
│   └── render-email.js
└── dist/

La carpeta src/templates contendrá las plantillas MJML editables. La carpeta dist puede reservarse para guardar los HTML generados. Esta separación ayuda a no mezclar archivos fuente con archivos compilados.

Crear una primera plantilla MJML

Dentro de src/templates/welcome.mjml, podríamos crear una plantilla de bienvenida:

<mjml>
  <mj-head>
    <mj-title>Email de bienvenida</mj-title>
    <mj-preview>Gracias por unirte a nuestra comunidad</mj-preview>

    <mj-attributes>
      <mj-all font-family="Arial, sans-serif" />
      <mj-text color="#333333" font-size="16px" line-height="1.6" />
      <mj-button background-color="#CC2B5E" color="#ffffff" border-radius="6px" />
    </mj-attributes>
  </mj-head>

  <mj-body background-color="#F8E0EA">
    <mj-section background-color="#ffffff" padding="32px">
      <mj-column>
        <mj-text font-size="24px" font-weight="bold">
          Hola, {{name}}
        </mj-text>

        <mj-text>
          Gracias por suscribirte. A partir de ahora recibirás contenidos sobre desarrollo frontend, diseño web y buenas prácticas para crear interfaces más claras.
        </mj-text>

        <mj-button href="{{ctaUrl}}">
          Leer último artículo
        </mj-button>
      </mj-column>
    </mj-section>
  </mj-body>
</mjml>

En este ejemplo aparecen dos variables: {{name}} y {{ctaUrl}}. MJML no sustituye esas variables por sí solo. Para eso necesitamos una capa adicional en Node.js, normalmente mediante un motor de plantillas como Handlebars, Mustache, Eta o EJS.

Este punto es importante porque automatizar no consiste únicamente en compilar MJML. La verdadera utilidad aparece cuando podemos generar emails personalizados a partir de una plantilla común.

Compilar MJML a HTML con Node.js

MJML puede utilizarse desde la línea de comandos, pero para un flujo automatizado suele ser más interesante ejecutarlo desde JavaScript.

Creamos el archivo src/render-email.js:

import fs from "node:fs/promises";
import path from "node:path";
import mjml2html from "mjml";

const templatePath = path.resolve("src/templates/welcome.mjml");
const outputPath = path.resolve("dist/welcome.html");

const mjmlTemplate = await fs.readFile(templatePath, "utf8");

const result = mjml2html(mjmlTemplate, {
  validationLevel: "strict",
  minify: true
});

if (result.errors.length > 0) {
  console.error("Errores al compilar MJML:", result.errors);
  process.exit(1);
}

await fs.mkdir("dist", { recursive: true });
await fs.writeFile(outputPath, result.html);

console.log("Email generado correctamente:", outputPath);

Para poder usar import, añade en tu package.json:

{
  "type": "module",
  "scripts": {
    "build:email": "node src/render-email.js"
  }
}

Y ejecuta:

npm run build:email

Con este script ya tenemos una primera automatización: Node.js lee la plantilla MJML, la compila y guarda el HTML final en la carpeta dist.

Por qué usar validación estricta

En el ejemplo anterior usamos la opción validationLevel: "strict". Esto hace que el proceso sea más exigente con los errores de sintaxis o estructura.

En un flujo manual, puede parecer cómodo permitir ciertos avisos. Pero cuando vas a automatizar, es preferible que el sistema falle antes de enviar un email roto. Si una plantilla tiene un error, conviene detectarlo en compilación, no cuando ya se ha enviado a una lista de contactos.

Diferencia entre validación flexible y validación estricta

Una validación flexible puede ser útil mientras estás prototipando. Sin embargo, para producción es recomendable utilizar una configuración más estricta. De este modo, si hay un componente mal cerrado, una propiedad incorrecta o un include que no se encuentra, el proceso se detiene.

Esta lógica es similar a la que aplicamos en desarrollo frontend moderno: cuanto antes detectes el error, más barato será corregirlo.

Añadir variables dinámicas con Handlebars

Hasta ahora hemos compilado una plantilla, pero todavía no hemos reemplazado las variables dinámicas. Para hacerlo, podemos usar Handlebars.

Instalamos la dependencia:

npm install handlebars

Después modificamos nuestro script:

import fs from "node:fs/promises";
import path from "node:path";
import mjml2html from "mjml";
import Handlebars from "handlebars";

const templatePath = path.resolve("src/templates/welcome.mjml");
const outputPath = path.resolve("dist/welcome.html");

const mjmlTemplate = await fs.readFile(templatePath, "utf8");

const data = {
  name: "Marta",
  ctaUrl: "https://martagonzalez.dev/blog/"
};

const compiledTemplate = Handlebars.compile(mjmlTemplate);
const mjmlWithData = compiledTemplate(data);

const result = mjml2html(mjmlWithData, {
  validationLevel: "strict",
  minify: true
});

if (result.errors.length > 0) {
  console.error("Errores al compilar MJML:", result.errors);
  process.exit(1);
}

await fs.mkdir("dist", { recursive: true });
await fs.writeFile(outputPath, result.html);

console.log("Email dinámico generado correctamente:", outputPath);

Con este cambio, la plantilla ya no es estática. Ahora podemos generar diferentes emails usando la misma estructura y cambiando únicamente los datos.

Por ejemplo, podríamos pasar un nombre distinto, una URL diferente o un bloque de contenido personalizado. Esta es una de las claves para automatizar emails MJML de forma eficiente.

Crear una función reutilizable para renderizar emails

En un proyecto real no conviene tener toda la lógica en un único archivo. Lo ideal es crear una función reutilizable que reciba el nombre de la plantilla y los datos dinámicos.

import fs from "node:fs/promises";
import path from "node:path";
import mjml2html from "mjml";
import Handlebars from "handlebars";

export async function renderEmail(templateName, data) {
  const templatePath = path.resolve(`src/templates/${templateName}.mjml`);
  const mjmlTemplate = await fs.readFile(templatePath, "utf8");

  const compiledTemplate = Handlebars.compile(mjmlTemplate);
  const mjmlWithData = compiledTemplate(data);

  const result = mjml2html(mjmlWithData, {
    validationLevel: "strict",
    minify: true,
    filePath: templatePath
  });

  if (result.errors.length > 0) {
    throw new Error(
      `Error al compilar ${templateName}: ${JSON.stringify(result.errors, null, 2)}`
    );
  }

  return result.html;
}

Después podríamos usarla así:

const html = await renderEmail("welcome", {
  name: "Marta",
  ctaUrl: "https://martagonzalez.dev/blog/"
});

Este enfoque separa mejor las responsabilidades. Una cosa es generar el HTML del email y otra distinta es enviarlo. Mantener esa separación facilita el mantenimiento, las pruebas y la reutilización.

Si ya estás trabajando con Vite, React u otras herramientas modernas de frontend, esta idea encaja muy bien con un flujo más amplio como el que explico en cómo integrar MJML en un workflow frontend moderno.

Organizar componentes reutilizables en MJML

A medida que el proyecto crece, empiezan a repetirse elementos: cabeceras, footers, botones, separadores, bloques de redes sociales, tarjetas de contenido o llamadas a la acción. Si repites todo ese código en cada plantilla, cualquier cambio se vuelve lento y arriesgado.

Por eso es recomendable organizar el proyecto en parciales o componentes reutilizables. Una estructura posible sería:

src/
├── templates/
│   ├── welcome.mjml
│   └── reset-password.mjml
├── partials/
│   ├── header.mjml
│   ├── footer.mjml
│   └── button.mjml
└── render-email.js

Dentro de una plantilla, podríamos incluir un bloque común:

<mj-include path="../partials/header.mjml" />

Esta forma de trabajar mejora mucho la consistencia. Si mañana quieres cambiar el footer de todos tus emails, no tienes que editar diez archivos distintos: modificas un parcial y vuelves a compilar.

Para profundizar en esta parte, puedes enlazar este flujo con una estrategia de componentes reutilizables en MJML, especialmente si estás construyendo una librería interna de emails para varios tipos de comunicación.

Enviar el email generado desde Node.js

Una vez generado el HTML, el siguiente paso puede ser enviarlo. Para ello, una opción habitual en Node.js es Nodemailer, aunque también podrías usar servicios como Resend, Mailgun, SendGrid, Amazon SES u otro proveedor.

Instalamos Nodemailer:

npm install nodemailer

Y creamos un ejemplo básico de envío:

import nodemailer from "nodemailer";
import { renderEmail } from "./render-email.js";

const transporter = nodemailer.createTransport({
  host: process.env.SMTP_HOST,
  port: Number(process.env.SMTP_PORT),
  secure: true,
  auth: {
    user: process.env.SMTP_USER,
    pass: process.env.SMTP_PASS
  }
});

const html = await renderEmail("welcome", {
  name: "Marta",
  ctaUrl: "https://martagonzalez.dev/blog/"
});

await transporter.sendMail({
  from: '"Marta González" <hola@martagonzalez.dev>',
  to: "usuario@example.com",
  subject: "Bienvenida a la newsletter",
  html
});

console.log("Email enviado correctamente");

Este ejemplo es sencillo, pero muestra el flujo completo: plantilla MJML, datos dinámicos, compilación con Node.js y envío mediante SMTP.

Eso sí, conviene recordar algo importante: generar bien el HTML no garantiza una buena entregabilidad. La entregabilidad depende también de SPF, DKIM, DMARC, reputación del dominio, calidad de la lista, frecuencia de envío y comportamiento de los destinatarios.

Buenas prácticas para automatizar plantillas de email

Automatizar emails no consiste solo en escribir scripts. También implica tomar decisiones de arquitectura para que el sistema sea claro y mantenible.

Separa plantilla, datos y envío

La plantilla MJML debería encargarse de la estructura visual. Los datos deberían llegar como un objeto independiente. El envío debería vivir en una capa separada.

Esta separación permite probar cada parte por separado. Puedes generar un HTML sin enviarlo, revisar una plantilla con datos de prueba o cambiar de proveedor de envío sin reescribir toda la lógica de renderizado.

Usa variables claras

Evita nombres genéricos como text1, link2 o contentBlock. Es mejor trabajar con variables descriptivas:

{
  userName: "Marta",
  articleTitle: "Cómo automatizar plantillas de email con MJML y Node.js",
  articleUrl: "https://martagonzalez.dev/blog/"
}

Los nombres claros reducen la carga cognitiva cuando vuelves al proyecto semanas después. Y en email marketing, la claridad no solo importa en el código: también importa en el diseño y en el contenido. Por eso, automatización y experiencia de usuario deberían ir de la mano, como explico en UX en email marketing: claridad antes que decoración.

Valida antes de enviar

Antes de enviar una campaña o un email transaccional, conviene validar la plantilla. Puedes crear un script que compile todas las plantillas del proyecto con datos de prueba:

const templates = ["welcome", "reset-password", "weekly-summary"];

for (const template of templates) {
  await renderEmail(template, {
    name: "Usuario de prueba",
    ctaUrl: "https://example.com"
  });

  console.log(`Plantilla ${template} validada correctamente`);
}

Esto te ayuda a detectar errores antes de que el email llegue a una persona real.

Genera versiones de prueba

Antes de conectar el sistema a usuarios reales, es recomendable generar una versión HTML de cada plantilla y revisarla visualmente. Aunque el navegador no reproduce exactamente el comportamiento de Gmail, Outlook o Apple Mail, sí permite detectar errores evidentes: variables sin reemplazar, enlaces incorrectos, textos desbordados o imágenes mal configuradas.

Después, lo ideal es probar en clientes de correo reales. Esta parte es especialmente importante si tu audiencia usa Outlook, porque sigue siendo uno de los entornos más delicados para email development. Si este tema te interesa, puedes revisar por qué Outlook sigue siendo un dolor de cabeza en email development.

Controla el contenido dinámico

Si insertas datos que vienen de formularios, bases de datos o usuarios, debes controlar muy bien ese contenido. Un valor inesperado puede romper una plantilla o generar un problema de seguridad.

Con Handlebars, las variables con doble llave, como {{name}}, se escapan por defecto. En cambio, las variables con triple llave, como {{{htmlContent}}}, insertan HTML sin escapar. Esta segunda opción debe usarse con mucho cuidado.

Automatización avanzada: newsletters y emails transaccionales

Una vez tienes el flujo base, puedes aplicarlo a diferentes tipos de email.

En los emails transaccionales, como recuperación de contraseña, confirmación de cuenta o aviso de compra, el HTML suele generarse en el momento en que ocurre una acción. Por ejemplo, una persona se registra, Node.js recibe el evento, renderiza la plantilla con sus datos y envía el correo.

En las newsletters, el flujo puede ser distinto. Puedes obtener los últimos artículos de un blog, construir un resumen semanal y generar una plantilla MJML con varios bloques de contenido. Si estás empezando con este tipo de piezas, puedes complementar este artículo con la guía sobre cómo crear tu primera newsletter responsive con MJML.

  1. Obtener datos desde una API, una base de datos o un CMS.
  2. Pasar esos datos a una plantilla MJML.
  3. Compilar la plantilla con Node.js.
  4. Guardar el HTML generado.
  5. Enviar una prueba interna.
  6. Revisar enlaces, asunto y preheader.
  7. Enviar a la lista final desde el proveedor elegido.

Este enfoque permite trabajar con emails más profesionales sin depender de procesos manuales en cada envío.

Errores habituales al automatizar emails con MJML y Node.js

Uno de los errores más frecuentes es mezclar demasiadas responsabilidades en un solo archivo. Si el mismo script lee datos, compila MJML, reemplaza variables, envía emails y registra métricas, el sistema se vuelve difícil de mantener.

Otro error habitual es no revisar el HTML final. Aunque MJML facilita mucho la maquetación, siempre conviene probar el resultado. Los clientes de correo siguen teniendo comportamientos particulares, y algunos detalles visuales pueden cambiar según el entorno.

También es frecuente olvidar el texto alternativo de las imágenes. En email, muchas imágenes pueden aparecer bloqueadas por defecto. Un buen atributo alt ayuda a que el mensaje siga teniendo sentido aunque la imagen no cargue.

Por último, conviene evitar diseños excesivamente complejos. Un email no es una landing page. Cuanto más compleja sea la estructura, mayor será el riesgo de inconsistencias entre clientes. En muchos casos, un diseño claro, jerárquico y directo funciona mejor que una composición demasiado decorativa.

FAQs sobre automatizar emails con MJML y Node.js

¿MJML sirve para enviar emails automáticamente?

No directamente. MJML sirve para crear y compilar plantillas de email responsive, pero no se encarga del envío. Para enviar emails desde Node.js necesitas combinarlo con una herramienta como Nodemailer o con la API de un proveedor de email.

¿Puedo usar MJML con React?

Sí, existen formas de integrar MJML en flujos frontend modernos y también soluciones de la comunidad relacionadas con React. Sin embargo, para muchos casos no hace falta complicar demasiado el sistema. Una combinación de MJML, Node.js y un motor de plantillas como Handlebars puede ser suficiente para generar emails dinámicos y mantenibles.

¿Es mejor compilar los emails antes o en el momento de enviarlos?

Depende del tipo de email. Para newsletters, suele ser útil compilar antes, revisar el HTML y enviar después. Para emails transaccionales, normalmente se compilan en el momento del evento, porque necesitan datos personalizados. En ambos casos, lo importante es validar la plantilla y controlar los datos dinámicos.

Automatizar emails también es cuidar la experiencia final

Automatizar plantillas de email con MJML y Node.js no consiste únicamente en escribir menos código. Consiste en crear un sistema más fiable, más mantenible y más coherente.

Cuando trabajas los emails como piezas sueltas, cada cambio puede convertirse en una fuente de errores: un botón que se rompe, un enlace olvidado, una cabecera desactualizada, un footer diferente o una variable mal copiada. En cambio, cuando construyes un flujo automatizado, cada plantilla forma parte de un sistema común.

MJML aporta claridad en la maquetación. Node.js aporta automatización, integración de datos, validación y capacidad de envío. Juntos permiten pasar de un proceso artesanal y repetitivo a un flujo mucho más profesional.

Y esto también tiene impacto en la experiencia de quien recibe el email. Un correo claro, responsive, bien estructurado y coherente no solo se ve mejor: también reduce la carga cognitiva, facilita la lectura y mejora el tiempo de decisión. En email marketing, esa claridad suele ser más valiosa que cualquier adorno visual.

Por eso, si trabajas con newsletters, emails transaccionales o sistemas de comunicación recurrentes, merece la pena invertir en un workflow sólido. Automatizar emails con MJML y Node.js es una forma de cuidar tanto el desarrollo como la comunicación final.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *