From html-email-designer
Build responsive HTML emails using MJML (Mailjet) — semantic XML tags, very low friction, excellent client compatibility. Use when the user has chosen MJML, wants quick templates, or needs something a non-developer can edit.
npx claudepluginhub danielrosehill/claude-code-plugins --plugin html-email-designerThis skill uses the workspace's default tool permissions.
MJML (https://github.com/mjmlio/mjml) is Mailjet's open-source email framework. Semantic XML-like tags compile to robust, cross-client HTML. Lowest friction of the three frameworks and the easiest for non-developers.
Prevents silent decimal mismatch bugs in EVM ERC-20 tokens via runtime decimals lookup, chain-aware caching, bridged-token handling, and normalization. For DeFi bots, dashboards using Python/Web3, TypeScript/ethers, Solidity.
Share bugs, ideas, or general feedback.
MJML (https://github.com/mjmlio/mjml) is Mailjet's open-source email framework. Semantic XML-like tags compile to robust, cross-client HTML. Lowest friction of the three frameworks and the easiest for non-developers.
Global CLI (fastest for one-offs):
npm install -g mjml
mjml -h
Or per-project:
npm init -y
npm install mjml
For live editing, use the online editor at https://mjml.io/try-it-live or VSCode extension attilabuti.vscode-mjml.
email.mjml)<mjml>
<mj-head>
<mj-title>Your subject line</mj-title>
<mj-preview>Preview text — 80-90 chars</mj-preview>
<mj-attributes>
<mj-all font-family="Helvetica, Arial, sans-serif" />
<mj-text font-size="16px" line-height="1.5" color="#333333" />
<mj-button background-color="#2c3e50" color="#ffffff" border-radius="4px" />
</mj-attributes>
<mj-style>
.footer-link { color: #888888; text-decoration: underline; }
</mj-style>
</mj-head>
<mj-body background-color="#f3f3f3" width="600px">
<mj-section background-color="#ffffff" padding="24px">
<mj-column>
<mj-image src="https://example.com/logo.png" alt="Company" width="140px" />
</mj-column>
</mj-section>
<mj-section background-color="#ffffff" padding="0 24px 24px">
<mj-column>
<mj-text font-size="24px" font-weight="bold">Headline</mj-text>
<mj-text>Body copy.</mj-text>
<mj-button href="https://example.com">Call to action</mj-button>
</mj-column>
</mj-section>
<mj-section padding="16px">
<mj-column>
<mj-text align="center" font-size="12px" color="#888888">
<a href="https://example.com/unsubscribe" class="footer-link">Unsubscribe</a>
</mj-text>
</mj-column>
</mj-section>
</mj-body>
</mjml>
| Tag | Purpose |
|---|---|
<mjml> | Root |
<mj-head> | Metadata: title, preview, global attributes, custom CSS |
<mj-title> | Subject line (used in preview / by some clients) |
<mj-preview> | Preheader text |
<mj-attributes> | Set defaults for all instances of a tag |
<mj-style> | Custom CSS (inlined into output) |
<mj-body> | Email body wrapper |
<mj-section> | Full-width horizontal band |
<mj-group> | Keeps columns side-by-side on mobile |
<mj-column> | Vertical container inside a section |
<mj-text> | Paragraph/heading text |
<mj-image> | Responsive image |
<mj-button> | Bulletproof button (VML fallback automatic) |
<mj-divider> | Horizontal rule |
<mj-spacer> | Vertical space |
<mj-social> + <mj-social-element> | Social icon row |
<mj-navbar> + <mj-navbar-link> | Horizontal nav |
<mj-hero> | Full-width image banner with overlay content |
<mj-carousel> | Image carousel (limited client support) |
<mj-raw> | Inject raw HTML (e.g., VML, conditionals) — escape hatch |
mjml email.mjml -o email.html # single file
mjml email.mjml -o email.html --config.minify=true
mjml templates/*.mjml -o build/ # directory
mjml email.mjml --config.validationLevel=strict
Via Node API:
import mjml2html from 'mjml';
const { html, errors } = mjml2html(mjmlSource, { minify: true, validationLevel: 'strict' });
<mj-include path="./header.mjml" />
For variable substitution, combine MJML with a templating engine (Handlebars, Liquid, Nunjucks) before passing to mjml2html — MJML itself has no variable interpolation.
Example pipeline:
import Handlebars from 'handlebars';
import mjml2html from 'mjml';
import fs from 'fs';
const template = Handlebars.compile(fs.readFileSync('email.mjml', 'utf8'));
const rendered = template({ firstName: 'Daniel', cta: 'https://...' });
const { html } = mjml2html(rendered);
fs.writeFileSync('email.html', html);
<mj-attributes>Set once in <mj-head>, apply everywhere:
<mj-attributes>
<mj-all font-family="Inter, Helvetica, sans-serif" />
<mj-text color="#1a1a1a" line-height="1.6" />
<mj-button background-color="#2c3e50" color="#ffffff" border-radius="6px" padding="12px 24px" />
<mj-class name="muted" color="#888888" font-size="14px" />
</mj-attributes>
Use the named class: <mj-text mj-class="muted">Fine print</mj-text>.
<mj-column> stacks on mobile by default. Use <mj-group> to keep them side-by-side.<mj-style> is inlined by default — use inline="inline" explicitly for clarity, or <mj-style> without it for head-only CSS (survives for media queries).<mj-hero> for background-image banners — handles VML fallback automatically.--config.validationLevel=strict during dev.Hand off to email-client-gotchas (always) and inline-and-test for preview/send workflow.