Gomjml
A native Go implementation of the MJML email framework, providing fast compilation of MJML markup to responsive HTML.
Install / Use
/learn @preslavrachev/GomjmlREADME
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.
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 implementationhelp- 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 performanceGetTagName() 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
node-connect
349.0kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
109.4kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
349.0kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
349.0kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。
