MJML o React Email: ¿con cuál conviene construir tus correos?

Por Mara Levin · Actualizado el 7 de octubre de 2026 · Precios y documentación revisados ese día

Los dos son gratuitos, los dos compilan a HTML con tablas y los dos sirven en producción. Lo que cambia es qué escribes, quién puede editarlo y cómo es tu backend.

La respuesta corta

Elige React Email si tu producto ya es React con TypeScript: las plantillas pasan a ser componentes tipados con props y el servidor de desarrollo te da una vista previa en vivo. Elige MJML si tu backend es otra cosa, si varios lenguajes deben renderizar las mismas plantillas o si prefieres un vocabulario de marcado pequeño que pueda leer alguien que no viene de JavaScript. Ninguno cobra licencia, así que el coste real está en el paso de compilación y en el servicio con el que envíes.

De un vistazo

Pregunta MJML React Email
Qué escribesEtiquetas tipo XML como mj-section, mj-column, mj-textComponentes de React en TSX o JSX
Instalaciónnpm install mjmlnpm install react-email react react-dom
Obtener el HTMLCLI mjml entrada.mjml -o salida.html o mjml2html en Noderender devuelve el HTML; toPlainText devuelve la parte de texto
Ciclo de desarrolloModo watch (mjml -w), plugin de VS Code, editor en líneaServidor local (email dev) con vista previa en vivo
Datos dinámicosNinguno incorporado; un motor de plantillas después de compilarProps y JavaScript normal
Lenguaje del backendCualquiera, mediante ports y bindingsNode, con compilación de React
LicenciaMIT, según la FAQ de MJMLMIT, según el repositorio de GitHub
Quién lo mantieneMailjetResend

El mismo correo de confirmación de pedido, escrito de las dos formas

Una comparación justa necesita el mismo encargo. Aquí es un correo de confirmación de pedido: texto de vista previa, un título con el nombre, una línea de detalle del pedido, un botón y la lengua del documento declarada. La versión MJML sigue la estructura de la documentación de MJML; la de React Email usa las importaciones de la guía de instalación de React Email.

MJML

<mjml lang="es">
  <mj-head>
    <mj-title>Pedido confirmado</mj-title>
    <mj-preview>Hemos recibido tu pedido 1042</mj-preview>
    <mj-attributes>
      <mj-all font-family="Helvetica, Arial, sans-serif" />
      <mj-button background-color="#0b5fff" color="#ffffff" />
    </mj-attributes>
  </mj-head>
  <mj-body background-color="#f4f4f5">
    <mj-section background-color="#ffffff">
      <mj-column>
        <mj-text font-size="22px" font-weight="bold">Gracias por tu pedido, {{nombre}}</mj-text>
        <mj-divider border-width="1px" border-color="#e5e7eb" />
        <mj-table>
          <tr style="text-align:left;">
            <th>Artículo</th>
            <th>Cantidad</th>
            <th>Precio</th>
          </tr>
          <tr>
            <td>Camiseta lisa</td>
            <td>2</td>
            <td>18,00 €</td>
          </tr>
        </mj-table>
        <mj-button href="https://tienda.example.com/pedidos/1042">Ver pedido</mj-button>
      </mj-column>
    </mj-section>
  </mj-body>
</mjml>

Se compila desde la línea de comandos con mjml pedido.mjml -o pedido.html o desde Node, donde la llamada documentada es asíncrona:

import mjml2html from 'mjml';

const { html, errors } = await mjml2html(source, { validationLevel: 'strict' });
if (errors.length) throw new Error(errors.map((e) => e.message).join('\n'));
// html conserva {{nombre}}: sustitúyelo después con tu motor de plantillas

Dos detalles salen directamente de la documentación. validationLevel admite strict, soft o skip y por defecto vale soft, así que una errata en el nombre de una etiqueta no detiene la compilación salvo que lo pidas. Y el atributo lang de la etiqueta mjml se copia a html y al div del cuerpo; si lo omites, el valor por defecto es und, que no le dice nada a un lector de pantalla. El mj-table es una etiqueta "final": acepta HTML de tabla normal, pero no otros componentes MJML dentro.

React Email

import { Html, Head, Body, Preview, Container, Heading, Text, Hr, Button } from "react-email";

type Linea = { articulo: string; cantidad: number; precio: string };

export default function PedidoConfirmado({ nombre, lineas }: { nombre: string; lineas: Linea[] }) {
  return (
    <Html lang="es">
      <Head />
      <Preview>Hemos recibido tu pedido 1042</Preview>
      <Body style={{ backgroundColor: "#f4f4f5", fontFamily: "Helvetica, Arial, sans-serif" }}>
        <Container style={{ backgroundColor: "#ffffff", padding: "24px" }}>
          <Heading as="h1" style={{ fontSize: "22px" }}>Gracias por tu pedido, {nombre}</Heading>
          <Hr />
          {lineas.map((l) => (
            <Text key={l.articulo}>{l.articulo} × {l.cantidad}: {l.precio}</Text>
          ))}
          <Button
            href="https://tienda.example.com/pedidos/1042"
            style={{ background: "#0b5fff", color: "#fff", padding: "12px 20px" }}
          >
            Ver pedido
          </Button>
        </Container>
      </Body>
    </Html>
  );
}

Para sacar HTML y una parte de texto plano, la documentación de render muestra este patrón:

import { render, toPlainText } from "react-email";
import PedidoConfirmado from "./emails/pedido-confirmado";

const html = await render(<PedidoConfirmado nombre="Lucía" lineas={[{ articulo: "Camiseta lisa", cantidad: 2, precio: "18,00 €" }]} />);
const texto = toPlainText(html);

Qué enseñan las dos muestras

El archivo MJML es más corto y casi no tiene lógica alrededor: las etiquetas describen la maquetación y los valores comunes (tipografía, color del botón) se declaran una vez en mj-attributes. El de React Email es más largo, pero los datos entran como una prop tipada, una prop mal escrita falla al compilar y cualquier pieza puede convertirse en un componente compartido. En cuanto el pedido tenga cinco líneas generadas desde un array, un bloque condicional para pedidos con envío gratuito y un saludo según el idioma del cliente, la versión React sigue legible, mientras que la de MJML necesita un segundo lenguaje de plantillas para hacer lo mismo.

Idioma, acentos y accesibilidad en correos en español

Un detalle que las comparativas en inglés pasan por alto: declarar la lengua. En MJML basta con lang="es" en la etiqueta mjml; la documentación explica que se añade al html y al div del cuerpo, que además recibe los atributos ARIA role="article" y aria-label tomado de mj-title. De ahí que convenga rellenar siempre mj-title. En React Email, el componente Html admite la misma declaración con su prop lang. Los dos producen HTML, y que los acentos, la eñe y el símbolo del euro lleguen bien depende de que guardes los archivos en UTF-8 y de que tu servicio de envío mande el cuerpo con esa codificación; compruébalo con un pedido de prueba que contenga "ñ", "á" y "€" antes de lanzar.

El día a día de desarrollo

React Email trae un servidor de desarrollo: añade "email:dev": "email dev" a tus scripts, ejecútalo y la guía te pide abrir localhost:3000, donde ves los cambios en vivo al editar el archivo. Hay además un componente de Tailwind; usa Tailwind 4.1.12 en el momento de escribir esto, y su documentación indica envolverlo con pixelBasedPreset porque algunos clientes de correo no admiten la unidad rem.

MJML ofrece mjml -w para recompilar al guardar, plugins para Visual Studio Code y Sublime Text, y un editor en línea gratuito. Hay un detalle de seguridad por defecto que conviene conocer: mj-include se ignora a menos que actives las inclusiones, así que cabeceras y pies compartidos desaparecen en silencio hasta que pasas --config.allowIncludes true (o ignoreIncludes: false en Node).

En la práctica es velocidad de respuesta frente a alcance. React Email da el ciclo más rápido dentro de un proyecto React. El de MJML es un paso de compilación que puede ejecutar cualquier sistema de integración continua o cualquier desarrollador ajeno a JavaScript.

Componentes y maquetación

Los componentes de cuerpo de MJML incluyen mj-section, mj-column, mj-group, mj-text, mj-image, mj-button, mj-hero, mj-social, mj-navbar, mj-accordion, mj-carousel, mj-spacer y mj-raw para HTML escrito a mano. La regla de maquetación es estricta: las columnas van dentro de una sección y, según la documentación, se apilan en móvil; si las quieres lado a lado, envuélvelas en mj-group. Algunos componentes interactivos se degradan según el cliente: el menú hamburguesa de la barra de navegación solo funciona en Apple Mail y el resto muestra los enlaces en línea.

Los de React Email son Html, Head, Body, Container, Section, Row, Column, Heading, Text, Button, Link, Image, Hr, Font, Preview, Markdown, CodeBlock, CodeInline y Tailwind. Son más delgados que los de MJML: no hay carrusel, acordeón ni hero, porque en React los compones tú. La documentación de Section muestra Row y Column para poner columnas lado a lado, pero no promete que se apilen en pantallas estrechas, así que prueba ese caso o añade tu propia media query.

Renderizado y compatibilidad con clientes

Ninguna de las dos herramientas promete resultados idénticos en todas partes, y las dos lo reconocen al hablar de pruebas en vez de garantías. React Email afirma que todos sus componentes se probaron en Gmail, Apple Mail, Outlook, Yahoo! Mail, HEY y Superhuman, y el HTML que documenta para un botón lleva comentarios condicionales mso para el relleno en Outlook. MJML afirma que sus componentes son responsivos en los clientes más populares, incluso Outlook, y su documentación avisa expresamente de que mj-button no será totalmente clicable por el soporte limitado de algunos clientes; haz clic en las esquinas del botón en tus pruebas.

Casi siempre decide tu propia plantilla, no el framework: imágenes sin dimensiones, tipografías web que se sustituyen, inversión de colores en modo oscuro. Antes de elegir por este criterio, envía tu correo real por un servicio de vista previa de clientes y compara los resultados. Nuestras guías de compatibilidad entre clientes, CSS en correo y modo oscuro explican en qué fijarse.

Ecosistema y lo que cuesta enviar

MJML tiene más historia y más alcance. Su documentación enumera ports y bindings para Rust (MRML), .NET, Elixir, Ruby, Python, Django y Laravel, además del envoltorio mjml-react, y componentes de la comunidad como mj-qr-code, mj-chart y mjml-msobutton (un botón que usa VML para esquinas redondeadas en Outlook de escritorio). El editor de arrastrar y soltar de Mailjet está construido sobre MJML, de modo que lo que se crea allí de forma visual comparte estructura con lo escrito a mano.

Las integraciones documentadas de React Email cubren Resend, Nodemailer, SendGrid, Postmark, Mailgun, AWS SES, Brevo, MailerSend y varios servicios menores. Su integración con Resend permite subir una plantilla para que quien no programa la edite en el editor visual de Resend.

Como los dos autores tienen negocio de envío, mucha gente supone que MJML va con Mailjet y React Email con Resend. No es obligatorio, pero las cifras sirven de contexto para presupuestar. En la página de precios de Resend, el plan Free cuesta 0 $ por 3.000 correos al mes (100 al día), Pro cuesta 20 $ por 50.000 y Scale 90 $ por 100.000, con correos extra a 0,90 $ por cada 1.000. En la de Mailjet, Free cuesta 0 $ por 6.000 correos al mes (200 al día), Starter 9 $ por 8.000 y Essential 19 $ por 15.000. La misma página lista 500 vistas previas de correo al mes en Essential y 2.000 en Premium (29 $), lo que cubre parte de las pruebas de clientes que de otro modo exigen una herramienta aparte. Compara los dos con tu volumen mensual y con el tope diario en mente antes de elegir remitente. Si además comparas servicios de envío, mira servicios de correo API-first y Resend vs Postmark.

Página de precios de Resend, captura de octubre de 2026
Resend pricing page as of Oct 7, 2026. Source: https://resend.com/pricing
Página de precios de Mailjet, captura de octubre de 2026
Mailjet pricing page as of Oct 7, 2026. Source: https://mailjet.com/pricing

Cuándo elegir cada uno

Elige React Email cuando

  • tu aplicación ya es React o Next.js y quieres que los datos del correo compartan tipos con la app.
  • las plantillas tienen lógica real: bloques condicionales, listas de líneas de pedido, localización, componentes de marca compartidos.
  • los desarrolladores son dueños de las plantillas y te importa más el ciclo de vista previa en vivo que el alcance.

Elige MJML cuando

  • tu backend es Rails, Django, Laravel, .NET, Go o Elixir y no quieres montar una cadena de Node y React solo para el correo.
  • varios equipos o servicios deben renderizar desde las mismas plantillas compiladas.
  • prefieres un vocabulario pequeño y declarativo con componentes ya hechos, como acordeones, carruseles y secciones hero.

Plantéate otra cosa cuando

Te atraen Tailwind y Vue: mira Maizzle vs MJML y React Email vs Maizzle. Si quienes editan son personas de marketing y necesitan un editor visual, ninguna de estas es la herramienta; la guía de constructores de correo HTML repasa editores como Stripo y Beefree.

Migrar de una a otra

No hay un conversor fiable, y las maquetaciones difieren lo bastante como para que reescribir salga más rápido que traducir. Un camino seguro es mantener las plantillas antiguas en producción, rehacer un correo de poco riesgo (un restablecimiento de contraseña, por ejemplo) en la herramienta nueva, enviar ambos por las mismas vistas previas de clientes y comparar las capturas lado a lado. Solo después pasa el resto, empezando por los correos de maquetación más simple.

Preguntas frecuentes

¿React Email es una capa de React encima de MJML?

No. Son proyectos independientes con motores distintos. MJML es un lenguaje de marcado que compila su propio motor, y su documentación menciona un port de la comunidad, mjml-react, que envuelve las etiquetas MJML en componentes de React. React Email es un conjunto aparte de componentes que se convierten en HTML con su función render, sin ningún compilador de MJML por medio.

¿Cuál se lleva mejor con Outlook?

Los dos apuntan a Outlook y ninguno lo garantiza. React Email declara que sus componentes se probaron en Gmail, Apple Mail, Outlook, Yahoo! Mail, HEY y Superhuman, y el botón que renderiza incluye comentarios condicionales para Outlook. MJML dice que sus componentes son responsivos en los clientes más usados, incluido Outlook, y avisa de que mj-button no es del todo clicable en algunos clientes. Pasa tu plantilla por una prueba de renderizado antes de fiarte de cualquiera de las dos afirmaciones.

¿Puedo usar MJML si mi backend no es JavaScript?

Sí, y es donde MJML saca más ventaja. Su documentación enumera ports y bindings para Rust (MRML), .NET, Elixir, Ruby, Python y Laravel. También puedes compilar una vez en tu integración continua con la CLI de Node y entregar el HTML como un archivo estático que tu backend rellena. React Email da por hecho un paso de compilación con Node y React allí donde se rendericen las plantillas.

¿Cómo meto datos dinámicos, como el nombre o las líneas del pedido, en MJML?

MJML no tiene variables propias. Escribes marcadores, compilas y aplicas un motor de plantillas al HTML resultante. La documentación recomienda ejecutar ese motor después de la compilación de MJML, porque las etiquetas de plantilla que parecen HTML pueden alterarse cuando MJML analiza el documento. En React Email el dato entra como una prop corriente y se resuelve antes del render, de modo que un bucle sobre las líneas del pedido es un simple map.

¿Necesito Resend para usar React Email?

No. React Email lo hace Resend y tiene una integración documentada con su servicio, pero también documenta integraciones con Nodemailer, SendGrid, Postmark, Mailgun, AWS SES, Brevo y otros. La función render devuelve una cadena de HTML que cualquier servicio de envío puede aceptar.