_emailmd_

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 document
  • text: plain text version for the text/plain MIME part
  • meta: extracted frontmatter metadata
  • warnings: non-fatal issues encountered while rendering (e.g. invalid frontmatter YAML). Omitted when empty. See RenderWarning.
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>;
}
OptionTypeDescription
themePartial<Theme>Override default theme values
wrapper'default' | WrapperFnCustom email wrapper function
minifybooleanMinify output HTML. Default false.
fontsRecord<string, string>Custom web fonts: { [family]: url } rendered as <mj-font>.
validationLevel'skip' | 'soft' | 'strict'MJML validation level. Default 'soft'.
templateSyntaxArray<{ prefix: string; suffix: string }>Template delimiters preserved during compilation. Default [{{...}}, [[...]]].
sanitizeStylesbooleanSanitize template variables inside CSS before minification. Requires minify: true.
beautifybooleanPretty-print output HTML. Ignored when minify: true. Default false.
stringsRenderStringsOverride output strings for localization. See RenderStrings.
darkThemetrue | 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.
breaksbooleanRender single newlines as <br>. Frontmatter breaks: wins per document. Default false.
partialsRecord<string, string>Named markdown partials spliced in at ::: include <name> lines.

RenderResult

{
  html: string;
  text: string;
  meta: { preheader?: string; [key: string]: unknown };
  warnings?: RenderWarning[];
}
PropertyTypeDescription
htmlstringComplete email-safe HTML document
textstringPlain text version
metaobjectExtracted frontmatter metadata
warningsRenderWarning[]?Non-fatal issues (e.g. invalid frontmatter YAML). Omitted when empty.

RenderWarning

{
  stage: 'frontmatter' | 'theme' | 'content' | 'mjml';
  message: string;
  cause?: Error;
}
PropertyTypeDescription
stagestringWhich render stage produced the warning (see below)
messagestringHuman-readable message
causeError?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:

StageMeaning
frontmatterThe YAML block could not be parsed. The body renders with empty meta.
themeA theme or font value was invalid (e.g. contained markup) and the default was used instead.
contentA directive or button value was invalid (an unknown color, alignment, length, or unsafe URL) and was dropped or replaced with the theme default.
mjmlThe 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 preheader is HTML-escaped.
  • Directive params must be plausible values: colors must be hex, named, rgb()/rgba(), or hsl()/hsla(); alignment must be left, center, or right; lengths must be CSS length tokens like 8px or 50%.
  • Hero background URLs with javascript:, data:, vbscript:, or file: 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 &bdquo;{text}&ldquo;? 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[];
}
PropertyTypeDescription
preheaderstring?Preheader text from frontmatter
frontmatterRecord<string, unknown>?The full frontmatter map, including custom keys
stringsRenderStrings?Localized strings from render options; forward to segmentsToMjml
warningsRenderWarning[]?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 context
  • escapeAttrValue(value): escape " and < without double-escaping existing entities
  • isCssColor(value): hex, named, rgb()/hsl() functional color, or template token
  • isCssLength(value): 1–4 CSS length tokens (e.g. 8px 16px), or template token
  • isSafeUrl(value): no scheme, a safe scheme (http, https, mailto, tel), or template token

On this page