Skip to content

Feature: add @comark/email renderer powered by MJML for responsive email layout #425

Description

@miguelrk

Description

Add a first-party @comark/email renderer package that uses MJML under the hood to compile Comark Markdown documents (including frontmatter, components, and data bindings) into responsive, inline-styled HTML compatible with all major email clients.

Motivation

Currently, Comark supports multiple rendering targets (@comark/html, @comark/react, @comark/vue, @comark/svelte, @comark/angular and @comark/ansi). However, generating transactional emails that render consistently across major email clients (Gmail, Outlook, Apple Mail) requires specialized table-driven HTML and inline CSS.

Using raw @comark/html output for emails results in broken layouts because email clients strip modern CSS primitives like Flexbox, CSS Grid, external stylesheets, and custom web fonts. Users currently have to build custom post-processing pipelines with third-party tools like Juice or Maizzle to make Comark HTML email-safe.

With Comark's built-in data binding (::if conditionals and ::for loops) and component syntax, Markdown documents are an ideal authoring format for transactional email templates. We lack a first-party rendering package tailored for email client compatibility and first-class responsive email layout.

Proposed solution

Introduce @comark/email as an official workspace package. @comark/email will transform a Comark document AST into an MJML JSON tree, then compile it with mjml2html into inbox-ready HTML.

The package will map Comark AST nodes and frontmatter metadata into native MJML tags (mj-text, mj-button, mj-section, mj-column, mj-divider, and others). Email directives map directly to those MJML tags rather than emitting Tailwind HTML.

@comark/email is Node-first. MJML compilation runs on the server (Node.js). Live preview demos use a Vite server middleware that calls renderEmail rather than running MJML in the browser.

Key Features & Requirements

  1. Frontmatter Configuration: Parse email metadata to configure subject, preview text, brand color, and theme options:

    ---
    email:
      subject: "Your order #{{ order.id }} has shipped!"
      previewText: "Track your package delivery status."
      brandColor: "#0066cc"
      theme:
        primary: "#0066cc"
        background: "#f4f5f7"
    ---
  2. Built-in Email Components & MJML Attributes: Support custom syntax for common email patterns with native MJML attributes:

    • ::email-button{href="..." background-color="#0066cc" color="#ffffff"}<mj-button>
    • ::email-columns<mj-section> with one <mj-column> per child
    • ::email-divider{border-color="#cccccc"}<mj-divider>
  3. AST-to-MJML Transformation: Walk the Comark AST and emit an MJML JSON tree. Use @comark/html only for inner HTML of mj-text / mj-table / mj-raw (inline tags, lists, custom components).

  4. MJML Compilation: Output fully compiled, inline-styled HTML ready to pass directly to email service providers (Resend, SendGrid, Postmark, AWS SES). Soft validation by default; errors returned in the result rather than thrown.

  5. Integration with AST & Data Binding: Process AST output generated by comark and the binding plugin prior to compilation.

API Design Proposal

import { parseMarkdown } from 'comark'
import { renderEmail, renderEmailFromDocument } from '@comark/email'

const doc = await parseMarkdown(markdownText)

// Render to responsive, inbox-safe HTML string
const { html, subject, previewText, errors } = await renderEmailFromDocument(doc, {
  email: {
    brandColor: '#0066cc',
  },
  mjmlOptions: {
    minify: true,
  },
  headCss: 'a { color: #0066cc; }',
})

// Or parse + render in one step
const result = await renderEmail(markdownText, {
  email: { brandColor: '#0066cc' },
})

// Pass output directly to your email provider
await sendEmail({
  to: user.email,
  subject, // Extracted from frontmatter: "Your order #1234 has shipped!"
  html, // Inline table-based HTML compiled by MJML
})

Render flow

Markdown → parseMarkdown → resolveEmailConfig
  → documentToMjmlJson (AST → MjmlNode)
  → compileMjml (mjml2html)
  → EmailRenderResult { html, subject, previewText, errors }

Alternatives Considered

  • Maizzle: Tailwind-first PostHTML pipeline with strong CSS inlining. Rejected in favor of MJML's maintained semantic layout engine and native responsive email tags. Maizzle would also require Tailwind class authoring on email directives.
  • React Email: Excellent component-driven ecosystem, but requires a React runtime. MJML provides a framework-agnostic pipeline better suited for @comark/email across multi-framework backends (Node, Vue, Svelte, Angular).
  • Low-Level CSS Inlining (Juice): Lightweight for single-column text, but fails to handle Outlook-specific conditional ghost tables (<!--[if mso]>) for complex multi-column layouts automatically.

Additional context

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions