BlockNote DocsFeaturesExportEmail Export

Email Export

It's possible to export BlockNote documents to email-compatible HTML, completely client-side.

This feature is provided by the @blocknote/xl-email-exporter. xl- packages are fully open source, but released under a copyleft license. A commercial license for usage in closed source, proprietary products comes as part of the Business subscription.

First, install the @blocknote/xl-email-exporter packages:

npm install @blocknote/xl-email-exporter

Then, create an instance of the ReactEmailExporter class. This exposes the following methods:

import {
  ReactEmailExporter,
  reactEmailDefaultSchemaMappings,
} from "@blocknote/xl-email-exporter";

// Create the exporter
const exporter = new ReactEmailExporter(
  editor.schema,
  reactEmailDefaultSchemaMappings,
);

// Convert the blocks to a react-email document
const html = await exporter.toReactEmailDocument(editor.document);

// Use react-email to write to file:
await ReactEmail.render(html, `filename.html`);

See the full example below:

Customizing the Email output

toReactEmailDocument takes an optional options parameter, which allows you to customize:

  • preview: Set the preview text for the email (can be a string or an array of strings)
  • header: Add content to the top of the email (must be a React-Email compatible component)
  • footer: Add content to the bottom of the email (must be a React-Email compatible component)
  • head: Inject elements into the Head element
  • container: Customize the container element (A component which will wrap the email content including the header and footer)
  • bodyStyles: Customize the body styles (a CSSProperties object), providing an object here will completely override the default styles with what you provide

Example usage:

import React from "react";
import {
  ReactEmailExporter,
  reactEmailDefaultSchemaMappings,
} from "@blocknote/xl-email-exporter";
import { BlockNoteEditor } from "@blocknote/core";
import { Text, Container } from "@react-email/components";

const editor = BlockNoteEditor.create();

// ---cut---
const exporter = new ReactEmailExporter(
  editor.schema,
  reactEmailDefaultSchemaMappings,
);

const html = await exporter.toReactEmailDocument(editor.document, {
  preview: "This is a preview of the email content",
  header: <Text>Header</Text>,
  footer: <Text>Footer</Text>,
  head: <title>My email</title>,
  container: ({ children }) => <Container>{children}</Container>,
  // These are the default body styles that are set by default
  bodyStyles: {
    fontFamily:
      "'SF Pro Display', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Helvetica Neue', Arial, sans-serif",
    fontSize: "16px",
    lineHeight: "1.5",
    color: "#333",
  },
});

Custom mappings / custom schemas

The ReactEmailExporter constructor takes a schema and mappings parameter. A mapping defines how to convert a BlockNote schema element (a Block, Inline Content, or Style) to a React-Email element. If you're using a custom schema in your editor, or if you want to overwrite how default BlockNote elements are converted to React Email, you can pass your own mappings:

For example, use the following code in case your schema has an extraBlock type:

import { ReactEmailExporter, reactEmailDefaultSchemaMappings } from "@blocknote/xl-email-exporter";
import { Text } from "@react-email/components";

new ReactEmailExporter(schema, {
    blockMapping: {
        ...reactEmailDefaultSchemaMappings.blockMapping,
        myCustomBlock: (block, exporter) => {
            return <Text>My custom block</Text>;
        },
    },
    inlineContentMapping: reactEmailDefaultSchemaMappings.inlineContentMapping,
    styleMapping: reactEmailDefaultSchemaMappings.styleMapping,
});

Math & diagram blocks

The math and diagram blocks live in separate packages, and so do their email mappings. Spread them into the default mappings to export math and diagrams as images, with the LaTeX/Mermaid source as the alt text:

import {
  ReactEmailExporter,
  reactEmailDefaultSchemaMappings,
} from "@blocknote/xl-email-exporter";
import {
  createInlineMathMapping,
  createMathBlockMapping,
} from "@blocknote/math-block/email-exporter";
import { createDiagramBlockMapping } from "@blocknote/diagram-block/email-exporter";

const exporter = new ReactEmailExporter(editor.schema, {
  ...reactEmailDefaultSchemaMappings,
  blockMapping: {
    ...reactEmailDefaultSchemaMappings.blockMapping,
    math: createMathBlockMapping(),
    diagram: createDiagramBlockMapping(),
  },
  inlineContentMapping: {
    ...reactEmailDefaultSchemaMappings.inlineContentMapping,
    inlineMath: createInlineMathMapping(),
  },
});

Invalid LaTeX or Mermaid sources render an error placeholder identifying the offending source, mirroring the editor.

Image delivery. By default the images are embedded as data URLs — self-contained, but some email clients (notably Gmail and Outlook for Windows) don't display data URL images and show the alt text instead. For those, deliver the images as inline cid: attachments (the most widely supported way to embed generated images) and pass the collected attachments to your mailer at send time:

import { createCIDImageDelivery } from "@blocknote/xl-email-exporter";

const imageDelivery = createCIDImageDelivery();
const exporter = new ReactEmailExporter(editor.schema, {
  ...reactEmailDefaultSchemaMappings,
  blockMapping: {
    ...reactEmailDefaultSchemaMappings.blockMapping,
    math: createMathBlockMapping({ imageDelivery }),
    diagram: createDiagramBlockMapping({ imageDelivery }),
  },
  inlineContentMapping: {
    ...reactEmailDefaultSchemaMappings.inlineContentMapping,
    inlineMath: createInlineMathMapping({ imageDelivery }),
  },
});

const html = await exporter.toReactEmailDocument(editor.document);

// e.g. with nodemailer (works the same with other mailers):
await transporter.sendMail({ html, attachments: imageDelivery.attachments });

Server-side rendering. Emails are usually rendered server-side at send time. Math handles this out of the box: block math is rasterized to PNG in the browser and embedded as SVG elsewhere (pass rasterize to createMathBlockMapping, e.g. backed by @resvg/resvg-js, to get PNGs server-side too — more email clients display them); inline math is always embedded as SVG. Rendering diagrams, however, requires a browser — when exporting server-side, pass a renderDiagram function to createDiagramBlockMapping (e.g. backed by @mermaid-js/mermaid-cli or a Kroki server); without one, the export throws.

Exporter options

The ReactEmailExporter constructor takes an optional options parameter. While conversion happens on the client-side, the default setup uses a server hosted proxy to resolve files:

const defaultOptions = {
  // a function to resolve external resources in order to avoid CORS issues
  // by default, this calls a BlockNote hosted server-side proxy to resolve files
  resolveFileUrl: corsProxyResolveFileUrl,
  // the strings rendered into the exported document (file link texts, error
  // placeholders); pass a locale from @blocknote/core/locales (or your
  // editor's dictionary) to export in another language
  dictionary: locales.en,
  // the colors to use in the email for things like highlighting, background colors and font colors.
  colors: COLORS_DEFAULT, // defaults from @blocknote/core
};

Custom styles

Want to tweak the default styles of the email? You can use reactEmailDefaultSchemaMappingsWithStyles to create a custom mapping with your own styles.

import {
  ReactEmailExporter,
  reactEmailDefaultSchemaMappingsWithStyles,
} from "@blocknote/xl-email-exporter";
import { Text } from "@react-email/components";

const { blockMapping, inlineContentMapping, styleMapping } =
  reactEmailDefaultSchemaMappingsWithStyles({
    textStyles: {
      paragraph: {
        style: {
          fontSize: 16,
          lineHeight: 1.5,
          margin: 3,
          minHeight: 24,
        },
      },
    },
  });

new ReactEmailExporter(schema, {
  // You can still use the default block mapping, but you can also overwrite it
  blockMapping: {
    ...blockMapping,
    audio: (block, exporter) => {
      return <Text>Audio block</Text>;
    },
  },
  inlineContentMapping,
  styleMapping,
});