Install
$ agentstack add skill-jayoram-mjml-ai-knowledge-mjml ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
MJML
MJML compiles a small set of semantic tags into responsive, client-compatible HTML email. You write `/` instead of nested tables and bulletproof-button hacks; the engine emits the ugly, well-tested HTML. The whole value proposition is that you stay at the layout level and never hand-write the table soup.
This skill assumes MJML 5 (the current major). Where v5 changed behavior from v4, it is called out — those changes are the most common source of "it worked before and now it doesn't."
The mental model (internalize this first)
An MJML document is a strict, shallow hierarchy. Every tag has exactly one valid parent context, and most rendering bugs are a tag in the wrong slot.
mjml
├── mj-head (optional: metadata, fonts, styles, default attributes)
└── mj-body (width=600px by default)
└── mj-wrapper (optional: groups multiple sections, e.g. a bordered card)
└── mj-section (a row)
├── mj-column (a responsive cell — stacks on mobile)
│ └── content (mj-text, mj-image, mj-button, mj-divider, …)
└── mj-group (keeps columns side-by-side on mobile)
└── mj-column
Five rules cover almost everything:
- Content lives in columns, never directly in a section.
mj-text,mj-image,mj-buttonetc. must sit inside anmj-column. A single-column row still needs the column. - Columns live in sections.
mj-columnoutside anmj-sectionis ignored. - Sections don't nest, and columns don't nest. You cannot put an
mj-sectioninside anmj-column, or anmj-columninside anmj-column. To get nested-looking layouts, usemj-wrapper(to stack sections) ormj-group(to keep columns together). This is the single most common structural mistake. - Column widths in a section must sum to ≤ the section width (≤100%). With no explicit
width, the section splits evenly across its columns (2 columns → 50% each, 3 → 33%, 4 → 25%). Any component inside a column is 100% of that column's width — columns are containers, not offset/spacer tools. - Columns inside an
mj-groupmust use percentage widths, not pixels. Pixel widths break grouping.
Canonical starter template
When the user wants a fresh template, start from this and build outward. It is valid, accessible, and exercises the head/body split.
Newsletter
One-line inbox preview text
.link-light a { color: #1a73e8 !important; }
Hello World
Body copy goes here.
Read more
Ending tags (why you can't nest MJML in some places)
Some components are ending tags: they may contain raw HTML but not other MJML components. The set is mj-text, mj-button, mj-table, mj-raw, mj-social-element, mj-navbar-link, mj-accordion-title, mj-accordion-text, and mj-carousel-image.
Practical consequence: rich text formatting goes inside mj-text as ordinary HTML (`, , , , inline style), not as more mj-* tags. Putting an mj-button inside an mj-text will not work — close the text and add a sibling mj-button` in the same column.
Title
A paragraph with a link.
Sibling button, not nested
Styling and default attributes (DRY templates)
Set defaults once in mj-head rather than repeating attributes on every tag. The cascade, highest priority first: inline attribute on the tag → matching component tag inside mj-attributes → mj-all → MJML built-in default.
mj-attributes> `overrides the default for *every*mj-text`.mj-attributes> `` sets a default for all components.mj-attributes> `defines a reusable group applied withmj-class="big"(space-separate to combine:mj-class="big brand"`).mj-styleholds real CSS for the `; addinline="inline"to inline it (needed for properties many clients strip from). Because one MJML tag emits several HTML tags,css-class` lands on the outermost element — inspect the compiled HTML to find the right child selector.
Components at a glance
Reach for references/components.md for the full attribute tables. Quick index of what each tag is for:
Layout: mj-wrapper (stack sections as a unit), mj-section (row; full-width="full-width" makes the background span edge-to-edge while content stays 600px), mj-column (responsive cell), mj-group (lock columns side-by-side on mobile).
Content: mj-text, mj-image (responsive `; omit width to fill the column), mj-button (bulletproof CTA), mj-divider, mj-spacer (vertical gap), mj-table (raw HTML table for data), mj-social + mj-social-element (share rows), mj-hero (background-image banner; background-width/background-height are mandatory), mj-accordion (collapsible, Apple-Mail-only interactivity), mj-carousel (image gallery, limited client support), mj-navbar + mj-navbar-link (menu, optional hamburger on Apple Mail), mj-raw` (escape hatch for arbitrary HTML).
Head: mj-title, mj-preview, mj-attributes, mj-style, mj-font (import a hosted @font-face CSS, only injected if used), mj-breakpoint (set the desktop↔mobile switch width), mj-html-attributes (inject attributes onto generated HTML via CSS selectors), mj-include (partials — see below).
Shared partials with mj-include (changed in v5)
mj-include pulls in external .mjml, .css (type="css", optionally css-inline="inline"), or .html (type="html") files. In MJML 5, includes are disabled by default and silently ignored. If included content is missing from the output, the includes are off, not broken.
- CLI: add
--config.allowIncludes true. - Node: pass
ignoreIncludes: false. - Scope which folders are allowed with
includePath(string or array), resolved againstfilePath. Absolute paths and..escapes outside the allowed roots are rejected. SetfilePathto your templates root and write base-relative includes (./_common/header.mjml), or keep template-relative paths and allowlist sibling folders.
Compiling MJML → HTML
CLI (from npm install mjml):
mjml input.mjml -o output.html # write to file
mjml input.mjml -s # write to stdout
mjml -w input.mjml # watch a file or folder
mjml input.mjml -o out.html --config.validationLevel strict --config.minify true
Useful --config.* flags: allowIncludes, beautify (default true in CLI), minify (default false), minifyOptions ({"minifyCss": false | "lite" | "default" | } — minifyCss replaced the old minifyCSS key in v5, though the old key still maps through), validationLevel (strict | soft | skip), includePath, filePath.
Node API — mjml2html(input, options) returns { html, errors }:
import mjml2html from 'mjml'
const { html, errors } = await mjml2html(mjmlSource, {
validationLevel: 'soft', // 'strict' throws on invalid markup
minify: false,
ignoreIncludes: false, // enable mj-include
filePath: '/project/templates/email.mjml',
includePath: ['/project/templates/_common'],
})
Always surface errors to the user when compiling programmatically — they pinpoint the offending tag and line. There is also a hosted MJML API (mjml.io/api) for environments that can't run Node.
Validation levels
The validator catches misplaced tags, unknown attributes, and broken structure before they become silently wrong HTML.
strict— abort on any validation error. Best for CI and when generating templates you can't eyeball.soft(default) — collect errors but still produce output.skip— no validation (fastest; use only when you trust the input).
When writing or transforming MJML for someone, prefer compiling with strict (or at least reading the errors array) so structural mistakes surface immediately rather than rendering as a collapsed or duplicated layout.
High-value gotchas
These are the things that waste the most time, drawn from real client-rendering behavior:
- Outlook backgrounds. On
mj-section/mj-hero, ifbackground-sizeis omitted,no-repeatis ignored in Outlook; a single percentagebackground-sizemakes the image shrink-to-fit rather than crop. Always setbackground-size,background-position, andbackground-repeatexplicitly when a section has a background image. mj-buttonis not fully clickable. Only the text/inner area is the link, due to client support — don't promise an edge-to-edge tap target. For a true full-bleed clickable block, wrap anmj-image(or the whole column content) in anhrefinstead.mj-herorequiresbackground-widthandbackground-height, andheightis required only inmode="fixed-height". Provide abackground-colorfallback for clients that drop the image.- Templating languages (Handlebars/Liquid/AMPscript) +
minify. A ``. - Reverse column order without breaking mobile stacking. Author columns in mobile (stacked) order, then add
direction="rtl"to themj-sectionto flip the desktop order. - Self-hosted Outlook.com / media-query-less clients.
owa="desktop"on themjmltag forces the desktop layout for those clients. - Social icons.
mj-social-element name="facebook"is a convenience that pulls Mailjet-hosted icons; for production, supply your ownsrcrather than depending on those assets. Append-noshareto a network name to stop MJML rewriting yourhrefinto a share URL.
Working approach
When the user hands you MJML to fix, first locate the structural error against the five rules and the ending-tag list before touching styling — a "broken" email is far more often a column in the wrong place than a bad attribute. When building from scratch, sketch the section/column skeleton first, get it compiling clean under strict, then layer in content and head-level defaults. When the deliverable is the email itself, compile it and report the errors array rather than assuming the source is correct.
For exhaustive per-component attributes, defaults, and supported social networks, read references/components.md.
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: JayOram
- Source: JayOram/MJML-ai-knowledge
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.