Skip to main content
Resend Docs

Search documentation

Type to search this documentation.

On this pageOverview

Embed the React Email editor in your app

The React Email editor is an open-source visual editor that lets your users author email templates inside your own product. It ships with bubble menus, slash commands, an Inspector sidebar, and extensible nodes. It also exports email-ready HTML you can hand straight to Resend.

This guide walks through embedding the editor in a React app, styling it to match your brand, customizing the Inspector Sidebar, and adding your own custom node.

YouTube video player
  • A React 18+ application (for example, Vite, Next.js, or Remix)
  • A bundler with package exports support (the defaults for the frameworks above)
  1. Install the editor

    Install @react-email/editor and import the bundled theme stylesheet once at your app root.

    npm
    npm install @react-email/editor
    yarn
    yarn add @react-email/editor
    pnpm
    pnpm add @react-email/editor
    bun
    bun add @react-email/editor
    app/layout.tsx
    import '@react-email/editor/themes/default.css';

    The bundled theme covers the bubble menus, slash commands, and Inspector. If you only use a subset, you can import individual stylesheets instead.

  2. Mount the base editor

    The fastest path is the standalone EmailEditor component. It comes with the default extensions, bubble menus, slash commands, and theming wired up.

    app/components/Editor.tsx
    'use client';
    
    import { EmailEditor } from '@react-email/editor';
    
    const initialContent = `
      <h1>Welcome</h1>
      <p>Type "/" for a command, or select text to format it.</p>
    `;
    
    export function Editor() {
      return <EmailEditor content={initialContent} />;
    }

    content accepts an HTML string or a TipTap JSON document. See the EmailEditor API reference for the full prop list.

  3. Style the editor

    The editor exposes two layers of styling:

    1. Editor chrome (bubble menus, slash command palette, Inspector controls) — driven by CSS custom properties (--re-*) and data-re-* attribute selectors.
    2. Editor content (what the user is typing) — uses TipTap's .tiptap root class. Note that styling here only affects the in-editor preview; the exported HTML is styled by the theming system.

    Override the CSS variables on a wrapper class to apply your brand:

    app/styles/editor.css
    .brand-editor {
      --re-bg: #fafaf9;
      --re-border: #d6d3d1;
      --re-text: #1c1917;
      --re-text-muted: #78716c;
      --re-hover: rgba(28, 25, 23, 0.04);
      --re-active: rgba(28, 25, 23, 0.08);
      --re-radius: 0.5rem;
      --re-radius-sm: 0.25rem;
    }
    
    /* Target a specific bubble menu button */
    [data-re-bubble-menu-item][data-active] {
      background: #2563eb;
      color: #fff;
    }
    
    /* Style the user's content area inside the editor */
    .tiptap h1 {
      font-size: 1.75rem;
      letter-spacing: -0.025em;
    }
    TSX
    <div className="brand-editor">
      <EmailEditor content={initialContent} />
    </div>

    The default theme also responds to prefers-color-scheme: dark and to a .dark class on any ancestor — so it works with libraries like next-themes out of the box. See the Styling reference for the complete list of variables, data-re-* selectors, and content classes.

  4. Add the Inspector Sidebar

    The Inspector is a contextual sidebar that swaps panels based on the user's current selection — document defaults when nothing is focused, node properties when a block is selected, text formatting when text is selected.

    Pass Inspector.Root as a child of EmailEditor so it shares the editor's context:

    app/components/Editor.tsx
    'use client';
    
    import { EmailEditor } from '@react-email/editor';
    import { Inspector } from '@react-email/editor/ui';
    
    export function Editor() {
      return (
        <div className="flex" style={{ height: '600px' }}>
          <EmailEditor
            content="<h1>Hello</h1><p>Click any element to inspect it.</p>"
            className="flex-1 min-w-0 overflow-y-auto p-4"
          >
            <Inspector.Root className="w-72 shrink-0 border-l p-4 overflow-y-auto">
              <Inspector.Breadcrumb />
              <Inspector.Document />
              <Inspector.Node />
              <Inspector.Text />
            </Inspector.Root>
          </EmailEditor>
        </div>
      );
    }

    See the Inspector reference for every render-prop helper, the full list of reusable section components (Inspector.Size, Inspector.Padding, Inspector.Border, Inspector.Typography, Inspector.Background, Inspector.Link), and runnable examples.

  5. Add a custom extension

    Custom blocks use EmailNode, which extends TipTap's Node with the renderToReactEmail method so the same node renders correctly inside the editor and in the exported email.

  6. Send the result with Resend

    If you use the standalone EmailEditor component, you can access the email HTML and plain text through ref methods. In your handler, send the HTML to Resend using the Send Email or Send Batch Emails API.

    TSX
    import { EmailEditor, type EmailEditorRef } from '@react-email/editor';
    import { useRef, useState } from 'react';
    
    export function MyEditor() {
      const ref = useRef<EmailEditorRef>(null);
    
      const handleExport = async () => {
        if (!ref.current) return;
        const html = await ref.current.getEmailHTML();
        // Send email using Resend using a server-side API endpoint/action
        const response = await fetch('/api/send-email', {
          method: 'POST',
          body: JSON.stringify({ html }),
        });
      };
    
      return (
        <div>
          <EmailEditor ref={ref} content="<p>Edit me</p>" />
          <button onClick={handleExport}>Export HTML</button>
        </div>
      );
    }

    The ref exposes three export methods:

    MethodReturnsDescription
    getEmailHTML()Promise<string>Email-ready HTML
    getEmailText()Promise<string>Plain text version
    getEmail()Promise<{ html, text }>Both in a single call

    All three use composeReactEmail under the hood.

Suggest an edit

Propose a replacement for this page. The site team reviews it before applying any changes.

Export
Documentation menu