API Reference
Complete API documentation for emailmd.
render(markdown, options?)
Renders markdown to email-safe HTML. Async since v0.3.0; returns a Promise.
Returns Promise<{ html, text, meta, warnings? }>
html: complete HTML email documenttext: plain text version for thetext/plainMIME partmeta: extracted frontmatter metadatawarnings: non-fatal issues encountered while rendering (e.g. invalid frontmatter YAML). Omitted when empty. SeeRenderWarning.
import { render } from "emailmd";
const { html, text, meta } = await render("# Hello");RenderOptions
{
theme?: Partial<Theme>;
wrapper?: 'default' | WrapperFn;
minify?: boolean;
fonts?: Record<string, string>;
validationLevel?: 'skip' | 'soft' | 'strict';
templateSyntax?: Array<{ prefix: string; suffix: string }>;
sanitizeStyles?: boolean;
beautify?: boolean;
strings?: RenderStrings;
darkTheme?: true | Partial<Theme>;
breaks?: boolean;
partials?: Record<string, string>;
}| Option | Type | Description |
|---|---|---|
theme | Partial<Theme> | Override default theme values |
wrapper | 'default' | WrapperFn | Custom email wrapper function |
minify | boolean | Minify output HTML. Default false. |
fonts | Record<string, string> | Custom web fonts: { [family]: url } rendered as <mj-font>. |
validationLevel | 'skip' | 'soft' | 'strict' | MJML validation level. Default 'soft'. |
templateSyntax | Array<{ prefix: string; suffix: string }> | Template delimiters preserved during compilation. Default [{{...}}, [[...]]]. |
sanitizeStyles | boolean | Sanitize template variables inside CSS before minification. Requires minify: true. |
beautify | boolean | Pretty-print output HTML. Ignored when minify: true. Default false. |
strings | RenderStrings | Override output strings for localization. See RenderStrings. |
darkTheme | true | Partial<Theme> | Opt into automatic dark mode. true = built-in dark palette; a partial theme merges over it. Overridden by a pinned frontmatter theme: light/dark. |
breaks | boolean | Render single newlines as <br>. Frontmatter breaks: wins per document. Default false. |
partials | Record<string, string> | Named markdown partials spliced in at ::: include <name> lines. |
RenderResult
{
html: string;
text: string;
meta: { preheader?: string; [key: string]: unknown };
warnings?: RenderWarning[];
}| Property | Type | Description |
|---|---|---|
html | string | Complete email-safe HTML document |
text | string | Plain text version |
meta | object | Extracted frontmatter metadata |
warnings | RenderWarning[]? | Non-fatal issues (e.g. invalid frontmatter YAML). Omitted when empty. |
RenderWarning
{
stage: 'frontmatter' | 'theme' | 'content' | 'mjml';
message: string;
cause?: Error;
}| Property | Type | Description |
|---|---|---|
stage | string | Which render stage produced the warning (see below) |
message | string | Human-readable message |
cause | Error? | Original thrown error, when one is available |
Warnings never stop the render: html/text output always contains a working email, so UIs can keep the preview alive and surface warnings alongside it. The stages:
| Stage | Meaning |
|---|---|
frontmatter | The YAML block could not be parsed. The body renders with empty meta. |
theme | A theme or font value was invalid (e.g. contained markup) and the default was used instead. |
content | A directive or button value was invalid (an unknown color, alignment, length, or unsafe URL) and was dropped or replaced with the theme default. |
mjml | The MJML compiler reported a validation error (at the default soft level). |
lint(markdown, options?)
Check an email for deliverability, accessibility, and readability problems. Unlike render warnings, which report things emailmd had to repair, lint findings flag content that renders fine but is likely to hurt the email in practice. Returns a Promise<LintFinding[]>, sorted by source line:
import { lint } from 'emailmd';
const findings = await lint(markdown, { partials });
// [{ rule: 'image-alt', severity: 'warning',
// message: 'Image is missing alt text — …', line: 12 }]interface LintFinding {
rule: string; // machine-readable rule id
severity: 'warning' | 'suggestion'; // warning = likely real problem
message: string;
line?: number; // 1-based line in the source markdown
}The full rule list, severity semantics, and CLI and builder integration are documented on the Linting page. Template tokens are respected throughout: a {{tracking_url}} link is never flagged insecure, and [Unsubscribe]({{unsubscribe_url}}) satisfies the unsubscribe check.
Input validation
All user-supplied values that end up in the HTML are escaped and/or validated before interpolation:
- The frontmatter
preheaderis HTML-escaped. - Directive params must be plausible values: colors must be hex, named,
rgb()/rgba(), orhsl()/hsla(); alignment must beleft,center, orright; lengths must be CSS length tokens like8pxor50%. - Hero background URLs with
javascript:,data:,vbscript:, orfile:schemes are dropped. - Theme values containing
<,>, or"are replaced with the base theme's value. - Font entries with unsafe family names or URLs are skipped.
Invalid values never throw; they fall back to safe defaults and surface as warnings. Template tags ({{ x }}, {% x %}, ${x}, %%x%%, [[x]]) pass through validation untouched so merge fields keep working. Note that raw inline HTML in the markdown body is passed through by design: treat the markdown source as trusted, and resolve template variables with properly encoded values in your sending app.
RenderStrings
Overridable output strings, for localization:
{
buttonFallback?: string;
}buttonFallback is the sentence shown under a button rendered with the fallback attribute. It supports the placeholders {text} (the button label) and {url} (a clickable copy of the button link), and may contain HTML entities:
await render(md, {
strings: {
buttonFallback: 'Probleme mit „{text}“? Nutzen Sie diesen Link: {url}',
},
});A per-button fallback="custom text" attribute still takes precedence over the global string. See Buttons.
Theme
The full theme interface with all customizable properties:
interface Theme {
brandColor: string; // Links, highlights, accents
headingColor: string; // Heading text
bodyColor: string; // Body text
backgroundColor: string; // Outer background
contentColor: string; // Content area background
cardColor: string; // Callout / code block background
buttonColor: string; // Primary button background
buttonTextColor: string; // Button text
secondaryColor: string; // Secondary button border
secondaryTextColor: string; // Secondary button text
successColor: string; // Success button background
successTextColor: string; // Success button text
dangerColor: string; // Danger button background
dangerTextColor: string; // Danger button text
warningColor: string; // Warning button background
warningTextColor: string; // Warning button text
fontFamily: string; // Font stack
fontSize: string; // Base font size (e.g. "16px")
lineHeight: string; // Base line height (e.g. "1.6")
contentWidth: string; // Email width (e.g. "600px")
borderRadius: string; // Border radius for buttons, callouts (e.g. "8px")
}Pass a Partial<Theme> to render() to override any subset of values. See Theme for defaults.
darkTheme
A pre-built dark theme object. Import and pass directly or spread with overrides:
import { render, darkTheme } from "emailmd";
// Use as-is
await render(md, { theme: darkTheme });
// Override specific values
await render(md, { theme: { ...darkTheme, brandColor: "#e11d48" } });buildHead(theme, preheader?)
function buildHead(theme: Theme, preheader?: string): string;Generates the MJML <mj-head> element containing global styles, font imports, and the hidden preheader text. Used when building custom wrappers.
segmentsToMjml(segments, theme, ctx?)
function segmentsToMjml(
segments: Segment[],
theme: Theme,
ctx?: { strings?: RenderStrings; warnings?: RenderWarning[] },
): string;Converts parsed content segments into MJML body elements (sections, columns, text, buttons, images, tables, etc.). Used when building custom wrappers.
The optional ctx argument forwards localized strings and a warnings array to collect content validation issues during segment rendering. Custom wrappers should pass through the values they receive on meta (the default wrapper does) so those warnings surface on the render result.
Segment
interface Segment {
type: 'text' | 'callout' | 'centered' | 'highlight' | 'header' | 'footer'
| 'button' | 'button-group' | 'image' | 'hr' | 'table' | 'hero';
content: string;
attrs?: Record<string, string>;
buttons?: Array<Record<string, string>>;
}Represents a parsed block of content from the markdown. The segments array is what gets passed to segmentsToMjml and to custom wrapper functions.
WrapperFn
type WrapperFn = (
segments: Segment[],
theme: Theme,
meta?: WrapperMeta,
) => string;A function that receives parsed segments, the resolved theme, and render context, and returns a complete MJML string. See Wrappers for usage examples.
WrapperMeta
The context object passed to wrapper functions:
{
preheader?: string;
frontmatter?: Record<string, unknown>;
strings?: RenderStrings;
warnings?: RenderWarning[];
}| Property | Type | Description |
|---|---|---|
preheader | string? | Preheader text from frontmatter |
frontmatter | Record<string, unknown>? | The full frontmatter map, including custom keys |
strings | RenderStrings? | Localized strings from render options; forward to segmentsToMjml |
warnings | RenderWarning[]? | The render's warnings collector; forward to segmentsToMjml |
Sanitize helpers
The validation primitives used internally are exported for reuse:
import { escapeHtml, escapeAttrValue, isCssColor, isCssLength, isSafeUrl } from "emailmd";escapeHtml(value): escape a raw string for HTML text or attribute contextescapeAttrValue(value): escape"and<without double-escaping existing entitiesisCssColor(value): hex, named,rgb()/hsl()functional color, or template tokenisCssLength(value): 1–4 CSS length tokens (e.g.8px 16px), or template tokenisSafeUrl(value): no scheme, a safe scheme (http,https,mailto,tel), or template token