> ## Documentation Index
> Fetch the complete documentation index at: https://react.email/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# composeReactEmail

> Convert editor content to email-ready HTML and plain text.

The `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

```tsx theme={"theme":{"light":"github-light","dark":"vesper"}}
import { composeReactEmail } from '@react-email/editor/core';
```

## Signature

```tsx theme={"theme":{"light":"github-light","dark":"vesper"}}
async function composeReactEmail(params: {
  editor: Editor;
  preview: string | null;
}): Promise<{ html: string; text: string }>;
```

## Parameters

<ResponseField name="editor" type="Editor" required>
  The TipTap editor instance. The function reads the editor's JSON document and walks through
  each registered extension to serialize nodes and marks.
</ResponseField>

<ResponseField name="preview" type="string | null" required>
  Preview text shown in inbox list views before the email is opened. Pass `null` to omit.
</ResponseField>

## Return value

Returns a `Promise` 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 |

Both are generated in parallel for performance.

***

## The serialization pipeline

Understanding how `composeReactEmail` works helps you write better custom extensions and
debug rendering issues.

### 1. Extract document and extensions

The function reads the editor's JSON document (via `editor.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 a `SerializerPlugin` — an interface with two
methods:

* **`getNodeStyles(node, depth, editor)`** — returns `React.CSSProperties` for a given node
* **`BaseTemplate({ previewText, children, editor })`** — wraps the serialized content in an email structure

The [`EmailTheming`](/editor/features/theming) 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:

1. **Resolves styles** — calls `serializerPlugin.getNodeStyles(node, depth, editor)` to get
   theme styles, then merges any inline styles from the node's attributes
2. **Renders unknown nodes as `null`** — if the node type isn't registered or isn't an
   [`EmailNode`](/editor/api-reference/email-node), it returns `null`
3. **Renders the node** — calls the extension's `renderToReactEmail()` component, passing
   `children` (from recursing into child nodes), `style`, `node`, and `extension`
4. **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 at `0` 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):

1. **Inline styles** — styles set directly on a node via the editor (e.g., text alignment)
2. **Theme styles** — styles from the active theme via `getNodeStyles()`
3. **Extension defaults** — hardcoded styles in each extension's `renderToReactEmail()`

Inside each extension's renderer, these are typically merged:

```tsx theme={"theme":{"light":"github-light","dark":"vesper"}}
renderToReactEmail({ children, style, node }) {
  return (
    <p style={{
      ...style,                            // theme styles
      ...inlineCssToJs(node.attrs?.style), // inline overrides
    }}>
      {children}
    </p>
  );
}
```

### 6. Wrap in BaseTemplate

The serialized content is wrapped in a `BaseTemplate` that provides the email's
outer structure.

The **default template** renders:

```tsx theme={"theme":{"light":"github-light","dark":"vesper"}}
<Html>
  <Head>
    <meta content="width=device-width" name="viewport" />
    <meta content="IE=edge" httpEquiv="X-UA-Compatible" />
    <meta name="x-apple-disable-message-reformatting" />
    <meta
      content="telephone=no,address=no,email=no,date=no,url=no"
      name="format-detection"
    />
  </Head>
  {previewText && <Preview>{previewText}</Preview>}
  <Body>
    <Section width="100%" align="center">
      <Section style={{ width: '100%' }}>
        {children}
      </Section>
    </Section>
  </Body>
</Html>
```

When [`EmailTheming`](/editor/features/theming) 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`' `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

```tsx theme={"theme":{"light":"github-light","dark":"vesper"}}
import { composeReactEmail } from '@react-email/editor/core';

const { html, text } = await composeReactEmail({ editor, preview: null });
```

### With preview text

The `preview` parameter sets the inbox preview snippet — the text shown before the email
is opened:

```tsx theme={"theme":{"light":"github-light","dark":"vesper"}}
const { html, text } = await composeReactEmail({
  editor,
  preview: 'Check out our latest updates!',
});
```

Pass `null` to omit preview text entirely.

### With theming

When the [`EmailTheming`](/editor/features/theming) extension is in your extensions array,
theme styles are automatically inlined into every node during export:

```tsx theme={"theme":{"light":"github-light","dark":"vesper"}}
import { StarterKit } from '@react-email/editor/extensions';
import { EmailTheming } from '@react-email/editor/plugins';

const extensions = [StarterKit, EmailTheming.configure({ theme: 'basic' })];

// Theme styles are injected automatically — no extra config needed
const { html } = await composeReactEmail({ editor, preview: null });
```

### Full example with export panel

```tsx theme={"theme":{"light":"github-light","dark":"vesper"}}
import { composeReactEmail } from '@react-email/editor/core';
import { useCurrentEditor } from '@tiptap/react';
import { useState } from 'react';

function ExportPanel() {
  const { editor } = useCurrentEditor();
  const [html, setHtml] = useState('');
  const [exporting, setExporting] = useState(false);

  const handleExport = async () => {
    if (!editor) return;
    setExporting(true);
    const result = await composeReactEmail({ editor, preview: null });
    setHtml(result.html);
    setExporting(false);
  };

  return (
    <div>
      <button onClick={handleExport} disabled={exporting}>
        {exporting ? 'Exporting...' : 'Export HTML'}
      </button>
      {html && (
        <textarea
          readOnly
          value={html}
          rows={16}
          style={{ width: '100%', fontFamily: 'monospace' }}
        />
      )}
    </div>
  );
}
```

***

## See also

* [`EmailNode`](/editor/api-reference/email-node) — defines how nodes serialize via `renderToReactEmail()`
* [`EmailMark`](/editor/api-reference/email-mark) — defines how marks serialize via `renderToReactEmail()`
* [Email Export](/editor/features/email-export) — guide with full editor + export examples
* [Theming](/editor/features/theming) — how `EmailTheming` provides styles and templates to the serializer
