# API Reference (/docs/api-reference) render(markdown, options?) [#rendermarkdown-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`](#renderwarning). ```typescript import { render } from "emailmd"; const { html, text, meta } = await render("# Hello"); ``` RenderOptions [#renderoptions] ```typescript { theme?: Partial; wrapper?: 'default' | WrapperFn; minify?: boolean; fonts?: Record; validationLevel?: 'skip' | 'soft' | 'strict'; templateSyntax?: Array<{ prefix: string; suffix: string }>; sanitizeStyles?: boolean; beautify?: boolean; strings?: RenderStrings; darkTheme?: true | Partial; breaks?: boolean; allowHtml?: boolean; css?: string; partials?: Record; } ``` | Option | Type | Description | | ----------------- | ------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `theme` | `Partial` | Override default theme values | | `wrapper` | `'default' \| WrapperFn` | Custom email wrapper function | | `minify` | `boolean` | Minify output HTML. Default `false`. | | `fonts` | `Record` | Custom web fonts: `{ [family]: url }` rendered as ``. | | `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`](#renderstrings). | | `darkTheme` | `true \| Partial` | Opt into [automatic dark mode](/docs/theme#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 `
`. Frontmatter `breaks:` wins per document. Default `false`. | | `allowHtml` | `boolean` | Allow raw HTML tags in the Markdown source. Default `true` (raw tags pass through). Set `false` to escape raw tags (`` renders as the literal characters rather than an element, while every Markdown feature (headings, emphasis, links, lists, tables, images, emoji, directives, buttons) keeps working. `javascript:`/`data:` URLs in Markdown links are blocked either way. `allowHtml: false` also closes the two other ways Markdown can emit markup: * **Attribute syntax.** The `{attr=…}` syntax attaches attributes to the generated element (`# Hi {onclick=…}` becomes `

`). In this mode event handlers (`on*`), inline `style`, and `href`/`src` overrides with a `javascript:`/`data:` scheme are dropped. Harmless attributes (`class`, `id`, `data-*`, and emailmd's own `button`, `caption`, `width`, `align`) are kept, so buttons, captions, and classes still work. * **Template tags.** Raw HTML wrapped in a template tag (`{{}}`) is escaped to text instead of spliced back verbatim. A legitimate template variable like `{{ profile_url }}` has no HTML-significant characters, so it stays unchanged. This is not a general HTML sanitizer. It prevents injection through emailmd's own syntax, but if your threat model demands high assurance, run the output through a dedicated sanitizer (tag/attribute allowlist) as a second layer. # Buttons (/docs/buttons) Add call-to-action buttons using the `{button}` attribute: ```markdown [Get Started](https://example.com){button} [Learn More](https://example.com){button.secondary} [Shop Sale](https://example.com){button color="#dc2626"} ``` Semantic Colors [#semantic-colors] Use preset color names for common actions: ```markdown [Confirm Account](https://example.com/confirm){button.success} [Delete Account](https://example.com/delete){button.danger} [Review Changes](https://example.com/review){button.warning} ``` These default to standard semantic colors (green, red, amber). You can customize them with `success_color`, `danger_color`, and `warning_color` in [frontmatter](/docs/frontmatter) or via the [theme API](/docs/theme). The secondary button color can be changed with `secondary_color`. Side-by-Side Buttons [#side-by-side-buttons] Place multiple buttons on the same line to render them side-by-side: ```markdown [Get Started](https://example.com){button} [Learn More](https://example.com/more){button.secondary} ``` Buttons on separate lines (separated by a blank line) stack vertically as usual. MJML handles responsive stacking automatically: side-by-side on desktop, stacked on mobile. Full-Width Buttons [#full-width-buttons] Make a button span the full width of the email content area with `width="full"`: ```markdown [Get Started](https://example.com){button width="full"} ``` This works with any variant or custom color: ```markdown [Learn More](https://example.com){button.secondary width="full"} [Shop Sale](https://example.com){button color="#dc2626" width="full"} ``` Border Radius [#border-radius] Override the default border radius (`8px`) on individual buttons with `border-radius="..."`: ```markdown [Get Started](https://example.com){button border-radius="24px"} [Sharp](https://example.com){button border-radius="0"} ``` To change the default border radius for all buttons (and callouts/highlights), set `border_radius` in [frontmatter](/docs/frontmatter) or via the [theme API](/docs/theme). Size [#size] Buttons take their font size from the theme's `fontSize`, so they scale with the body text. A smaller `fontSize` gives a smaller button. Styling [#styling] Every button carries the class `emd-btn`, so you can reach properties that have no dedicated option (weight, padding, …) through the [`css`](/docs/api-reference#renderoptions) render option: ```typescript await render(md, { css: '.emd-btn a { font-weight: 400 !important }', }); ``` The `!important` is needed because the button's own styles are inline. Because this relies on an embedded `