Skip to content
SP StackPractices
beginner Por Mathias Paulenko

Templates de Email Responsivos con MJML

Crea templates de email responsivos compatibles con multiples clientes usando MJML, variables dinamicas con Handlebars y CSS inline para renderizado confiable en Gmail, Outlook y Apple Mail

Nota para desarrolladores hispanohablantes: Esta guía incluye ejemplos y convenciones de nomenclatura adaptadas a equipos que trabajan en español. Cuando existen diferencias significativas en terminología técnica entre el inglés y el español, se indican explícitamente para facilitar la comunicación en equipos multiculturales.

Templates de Email Responsivos con MJML

El HTML de email es notoriamente dificil debido a motores de renderizado inconsistentes entre clientes. MJML abstrae estas complejidades en un lenguaje de markup declarativo que compila a HTML responsivo y probado en batalla con estilos inline. Esta recipe cubre estructura MJML, templating dinamico con Handlebars y envio via SMTP/API.

Cuando Usar Esto

  • Emails transaccionales (reset de password, confirmaciones de orden) deben renderizar confiablemente
  • Newsletters de marketing necesitan layouts responsivos en mobile y desktop
  • Quieres evitar escribir HTML basado en tablas manualmente

Solucion

1. Template Basico de MJML

<!-- emails/welcome.mjml -->
<mjml>
  <mj-head>
    <mj-attributes>
      <mj-text font-family="Arial, sans-serif" color="#333333" />
      <mj-button background-color="#3b82f6" color="#ffffff" border-radius="4px" />
    </mj-attributes>
  </mj-head>
  <mj-body background-color="#f3f4f6">
    <mj-section>
      <mj-column>
        <mj-image width="120px" src="https://example.com/logo.png" alt="Logo" />
        <mj-text font-size="24px" font-weight="bold" align="center">
          Bienvenido, {{name}}!
        </mj-text>
        <mj-text font-size="16px" line-height="24px">
          Gracias por unirte. Tu cuenta esta lista y puedes empezar a explorar.
        </mj-text>
        <mj-button href="{{dashboardUrl}}" font-size="16px" padding="16px 32px">
          Ir al Dashboard
        </mj-button>
        <mj-text font-size="12px" color="#6b7280" align="center">
          Si no te registraste, ignora este email.
        </mj-text>
      </mj-column>
    </mj-section>
  </mj-body>
</mjml>

2. Compilar y Renderizar

// email/EmailRenderer.ts
import mjml2html from 'mjml';
import Handlebars from 'handlebars';

interface WelcomeData {
  name: string;
  dashboardUrl: string;
}

function compileTemplate(mjmlSource: string, data: WelcomeData): { html: string; errors: unknown[] } {
  const { html: rawHtml, errors } = mjml2html(mjmlSource, {
    validationLevel: 'strict',
    minify: true,
  });

  const template = Handlebars.compile(rawHtml);
  const html = template(data);

  return { html, errors };
}

3. Enviar via SMTP con Nodemailer

// email/EmailSender.ts
import nodemailer from 'nodemailer';

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

async function sendWelcomeEmail(to: string, data: WelcomeData): Promise<void> {
  const mjmlSource = await fs.readFile('./templates/welcome.mjml', 'utf8');
  const { html, errors } = compileTemplate(mjmlSource, data);

  if (errors.length > 0) {
    console.warn('Advertencias de compilacion MJML:', errors);
  }

  await transporter.sendMail({
    from: '"StackPractices" <noreply@example.com>',
    to,
    subject: 'Bienvenido a StackPractices',
    html,
    text: `Bienvenido ${data.name}! Visita: ${data.dashboardUrl}`,
  });
}

4. Libreria de Componentes Reutilizables

<!-- emails/components/Button.mjml -->
<mj-button
  href="{{url}}"
  background-color="{{#if color}}{{color}}{{else}}#3b82f6{{/if}}"
  color="#ffffff"
  border-radius="4px"
  font-size="16px"
  padding="16px 32px"
>
  {{text}}
</mj-button>

5. Soporte de Dark Mode

<mj-raw>
  <meta name="color-scheme" content="light dark" />
  <meta name="supported-color-schemes" content="light dark" />
</mj-raw>
<mj-style>
  @media (prefers-color-scheme: dark) {
    .dark-bg { background-color: #1f2937 !important; }
    .dark-text { color: #f3f4f6 !important; }
  }
</mj-style>

Como Funciona

  • Componentes MJML abstraen layouts basados en tablas en tags semanticos como <mj-section> y <mj-column>
  • Compilacion genera HTML con estilos inline seguro para Outlook con conditional comments
  • Handlebars inyecta variables runtime despues de la compilacion MJML para preservar markup
  • Minificacion reduce tamano de payload para delivery mas rapido

Consideraciones de Produccion

  • Testea templates en Litmus o Email on Acid antes de deployment en produccion
  • Manten el ancho total del email bajo 600px para compatibilidad mobile
  • Usa URLs absolutas para todas las imagenes; la mayoria de clientes bloquean CSS externo

Errores Comunes

  • Usar web fonts (no soportadas en la mayoria de clientes; usa system fonts)
  • Depender de flexbox o grid (usa el sistema de columnas de MJML en su lugar)
  • Olvidar versiones plain-text, que afectan scores de deliverability

FAQ

P: Necesito MJML si uso un servicio como SendGrid? R: SendGrid provee templates, pero MJML te da markup versionado y reusable que funciona con cualquier provider.

P: Puedo usar React para renderizar MJML? R: Si. Usa mjml-react para escribir MJML como componentes JSX manteniendo el mismo pipeline de compilacion.