composeReactEmail function is the core of the editor’s email export system. It takes
the editor’s document tree, walks every node and mark, calls each extension’s
renderToReactEmail() method, applies theme styles, wraps everything in an email-ready
template, and produces both HTML and plain text output.
Import
Signature
Parameters
The TipTap editor instance. The function reads the editor’s JSON document and walks through
each registered extension to serialize nodes and marks.
Preview text shown in inbox list views before the email is opened. Pass
null to omit.Return value
Returns aPromise that resolves to an object with:
| Field | Type | Description |
|---|---|---|
html | string | Full HTML email string, ready to send |
text | string | Plain text version for email clients that don’t support HTML |
The serialization pipeline
Understanding howcomposeReactEmail works helps you write better custom extensions and
debug rendering issues.
1. Extract document and extensions
The function reads the editor’s JSON document (viaeditor.getJSON()) and collects all
registered extensions into a name-to-extension map for fast lookup.
2. Find the SerializerPlugin
It searches extensions for one that provides aSerializerPlugin — an interface with two
methods:
getNodeStyles(node, depth, editor)— returnsReact.CSSPropertiesfor a given nodeBaseTemplate({ previewText, children, editor })— wraps the serialized content in an email structure
EmailTheming extension implements this interface. If no
plugin is found, styles default to {} and the built-in DefaultBaseTemplate is used.
3. Traverse the document tree
It recursively walks the ProseMirror document. For each node it:- Resolves styles — calls
serializerPlugin.getNodeStyles(node, depth, editor)to get theme styles, then merges any inline styles from the node’s attributes - Renders unknown nodes as
null— if the node type isn’t registered or isn’t anEmailNode, it returnsnull - Renders the node — calls the extension’s
renderToReactEmail()component, passingchildren(from recursing into child nodes),style,node, andextension - Wraps with marks — iterates through the node’s marks (bold, italic, link, etc.) and
wraps the rendered output with each mark’s
renderToReactEmail()
4. Depth tracking
Depth starts at0 and only increments inside list nodes (bulletList, orderedList).
This enables different styling for nested vs. top-level elements — for example, paragraphs
inside list items use the listParagraph theme key instead of paragraph.
5. Style resolution order
Styles are resolved in this priority (highest wins):- Inline styles — styles set directly on a node via the editor (e.g., text alignment)
- Theme styles — styles from the active theme via
getNodeStyles() - Extension defaults — hardcoded styles in each extension’s
renderToReactEmail()
6. Wrap in BaseTemplate
The serialized content is wrapped in aBaseTemplate that provides the email’s
outer structure.
The default template renders:
EmailTheming is active, its BaseTemplate replaces the
default — it adds theme-specific body/container styles and can inject global CSS via a
<style> tag in the <Head>.
7. Render to HTML and plain text
The React tree is rendered to an HTML string using@react-email/components’ render()
function. Both the formatted HTML and a plain text version (tags stripped, text preserved)
are produced in parallel from the final React tree.
Usage
Basic export
With preview text
Thepreview parameter sets the inbox preview snippet — the text shown before the email
is opened:
null to omit preview text entirely.
With theming
When theEmailTheming extension is in your extensions array,
theme styles are automatically inlined into every node during export:
Full example with export panel
See also
EmailNode— defines how nodes serialize viarenderToReactEmail()EmailMark— defines how marks serialize viarenderToReactEmail()- Email Export — guide with full editor + export examples
- Theming — how
EmailThemingprovides styles and templates to the serializer