SkillAgentSearch skills...

Gomjml

A native Go implementation of the MJML email framework, providing fast compilation of MJML markup to responsive HTML.

Install / Use

/learn @preslavrachev/Gomjml
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

gomjml - Native Go MJML Compiler

A native Go implementation of the MJML email framework, providing fast compilation of MJML markup to responsive HTML. This implementation targets full compliance with the official MJML specification, producing output that matches the MJML JavaScript reference implementation. See performance benchmarks for detailed comparison with other MJML implementations.

status Tests Go Report Card

Full Disclosure: This project has been created in cooperation with Claude Code. I wouldn't have been able to achieve such a feat without Claude's help in turning my bizarre requirements into Go code. Still, it wasn't all smooth sailing. While Claude was able to generate a plausible MVP relatively quickly, bringing it something even remotely usable took a lot more human guidance, going back and forth, throwing away a bunch of code and starting over. There's lots I have learned in the process, and I will soon write a series of blog posts addressing my experience.

🚀 Features

  • Complete MJML Specification Compliance: Feature-complete with all 26 MJML components aligned with the official MJML.io specification. A well-structured Go library with clean package separation
  • Enhanced Email Compatibility: Generates HTML that works reliably across all email clients with robust Microsoft Outlook support and VML background rendering for legacy versions
  • Fast Performance: Native Go performance, comparable to Rust MRML implementation
  • Optional AST Caching: Opt-in template caching for speedup on repeated renders
  • Complete Component System: Support for essential MJML components with proper inheritance
  • CLI & Library: Use as command-line tool or importable Go package
  • MJML Reference Testing: Integration tests validate output compatibility with official MJML implementation

📦 Installation

Install CLI

# Clone and build
git clone https://github.com/preslavrachev/gomjml
cd gomjml
go build -o bin/gomjml ./cmd/gomjml

# Add to PATH (optional)
export PATH=$PATH:$(pwd)/bin

Install as Go Package

# Import as library
go get github.com/preslavrachev/gomjml

🔧 Usage

Command Line Interface

The CLI provides a structured command system with individual commands:

# Basic compilation
./bin/gomjml compile input.mjml -o output.html

# Output to stdout
./bin/gomjml compile input.mjml -s

# Include debug attributes for component traceability
./bin/gomjml compile input.mjml -s --debug

# Enable caching for better performance on repeated renders
./bin/gomjml compile input.mjml -o output.html --cache

# Configure cache with custom TTL
./bin/gomjml compile input.mjml -o output.html --cache --cache-ttl=10m

# Run test suite
./bin/gomjml test

# Get help
./bin/gomjml --help
./bin/gomjml compile --help

CLI Commands

  • compile [input] - Compile MJML to HTML (main command)
  • test - Run test suite against MRML reference implementation
  • help - Show help information

Compile Command Options

  • -o, --output string: Output file path
  • -s, --stdout: Output to stdout
  • --debug: Include debug attributes for component traceability (default: false)
  • --cache: Enable AST caching for performance (default: false)
  • --cache-ttl: Cache TTL duration (default: 5m)
  • --cache-cleanup-interval: Cache cleanup interval (default: cache-ttl/2)

Go Package API

The implementation provides clean, importable packages:

package main

import (
	"fmt"
	"log"

	"github.com/preslavrachev/gomjml/mjml"
	"github.com/preslavrachev/gomjml/parser"
)

func main() {
	mjmlContent := `<mjml>
        <mj-head>
          <mj-title>My Newsletter</mj-title>
        </mj-head>
        <mj-body>
          <mj-section>
            <mj-column>
              <mj-text>Hello World!</mj-text>
              <mj-button href="https://example.com">Click Me</mj-button>
            </mj-column>
          </mj-section>
        </mj-body>
      </mjml>`

	// Method 1: Direct rendering (recommended)
	html, err := mjml.Render(mjmlContent)
	if err != nil {
		log.Fatal("Render error:", err)
	}
	fmt.Println(html)

	// Method 1b: Direct rendering with debug attributes
	htmlWithDebug, err := mjml.Render(mjmlContent, mjml.WithDebugTags(true))
	if err != nil {
		log.Fatal("Render error:", err)
	}
	fmt.Println(htmlWithDebug) // Includes data-mj-debug-* attributes

	// Method 1c: Enable caching for performance (opt-in feature)
	htmlWithCache, err := mjml.Render(mjmlContent, mjml.WithCache())
	if err != nil {
		log.Fatal("Render error:", err)
	}
	fmt.Println(htmlWithCache) // Uses cached AST if available

	// For long-running applications, configure cache TTL before first use
	mjml.SetASTCacheTTLOnce(10 * time.Minute)
	
	// For graceful shutdown in long-running applications (optional)
	// Not needed for CLI tools or short-lived processes
	defer mjml.StopASTCacheCleanup()

	// Method 2: Step-by-step processing
	ast, err := parser.ParseMJML(mjmlContent)
	if err != nil {
		log.Fatal("Parse error:", err)
	}

	component, err := mjml.NewFromAST(ast)
	if err != nil {
		log.Fatal("Component creation error:", err)
	}

	html, err = mjml.RenderComponentString(component)
	if err != nil {
		log.Fatal("Render error:", err)
	}
	fmt.Println(html)
}

Adding New Components

While it is not recommended to do so, because it will break the compatibility with the MJML specification, you can fork the repository and add new components by following these steps:

// 1. Create component file in mjml/components/
package components

import (
    "io"
    "strings"
    
    "github.com/preslavrachev/gomjml/mjml/options"
    "github.com/preslavrachev/gomjml/parser"
)

type MJNewComponent struct {
    *BaseComponent
}

func NewMJNewComponent(node *parser.MJMLNode, opts *options.RenderOpts) *MJNewComponent {
    return &MJNewComponent{
        BaseComponent: NewBaseComponent(node, opts),
    }
}

// Note: RenderString() is no longer part of the Component interface
// Use mjml.RenderComponentString(component) helper function instead

func (c *MJNewComponent) Render(w io.Writer) error {
    // Implementation here - write HTML directly to Writer
    // Use c.AddDebugAttribute(tag, "new") for debug traceability
    
    // Example implementation:
    // if _, err := w.Write([]byte("<div>Hello World</div>")); err != nil {
    //     return err
    // }
    return nil
}

func (c *MJNewComponent) GetTagName() string {
    return "mj-new"
}

// 2. Add to component factory in mjml/component.go
case "mj-new":
    return components.NewMJNewComponent(node, opts), nil

// 3. Add test cases in mjml/integration_test.go
// 4. Update README.md documentation

Component Interface Requirements

All MJML components must implement the Component interface, which requires:

  • Render(w io.Writer) error: Primary rendering method that writes HTML directly to a Writer for optimal performance
  • GetTagName() string: Returns the component's MJML tag name

For string-based rendering, use the helper function mjml.RenderComponentString(component) instead of a component method.

Delaying Component Implementation

If you need to register a component but won't implement its functionality right away, use the NotImplementedError pattern:

func (c *MJNewComponent) Render(w io.Writer) error {
    // TODO: Implement mj-new component functionality
    return &NotImplementedError{ComponentName: "mj-new"}
}

func (c *MJNewComponent) GetTagName() string {
    return "mj-new"
}

📋 Component Implementation Status

| Component | Status | Description | |-----------|--------|-------------| | Core Layout | | | | mjml | ✅ Implemented | Root document container with DOCTYPE and HTML structure | | mj-head | ✅ Implemented | Document metadata container | | mj-body | ✅ Implemented | Email body container with responsive layout | | mj-section | ✅ Implemented | Layout sections with background support | | mj-column | ✅ Implemented | Responsive columns with automatic width calculation | | mj-wrapper | ✅ Implemented | Wrapper component with border, background-color, and padding support | | mj-group | ✅ Implemented | Group multiple columns in a section | | Content Components | | | | mj-text | ✅ Implemented | Text content with full styling support | | mj-button | ✅ Implemented | Email-safe buttons with customizable styling and links | | mj-image | ✅ Implemented | Responsive images with link wrapping and alt text | | mj-divider | ✅ Implemented | Visual separators and spacing elements | | mj-social | ✅ Implemented | Social media icons container | | mj-social-element | ✅ Implemented | Individual social media icons | | mj-navbar | ✅ Implemented | Navigation bar component | | mj-navbar-link | ✅ Implemented | Navigation links within navbar | | mj-raw | ✅ Implemented | Raw HTML content insertion | | Head Components | | | | mj-title | ✅ Implemented | Document title for email clients | | mj-font | ✅ Implemented | Custom font imports with Google Fonts support | | mj-preview | ✅ Implemented | Preview text for email clients | | mj-style | ✅ Implemented | Custom CSS styles | | mj-attributes | ✅ Implemented | Global attribute definitions | | mj-all | ✅ Implemented | Global attributes for all components | | Other Components | | | | mj-accordion | ✅ Implemented | Collapsible content sections | | mj-accordion-text | ✅ Implemented | Text content within

Related Skills

View on GitHub
GitHub Stars108
CategoryDevelopment
Updated2d ago
Forks5

Languages

Go

Security Score

100/100

Audited on Apr 3, 2026

No findings