Embed the React Email editor
Add the open-source React Email visual editor to your own app: install it, mount the base editor, theme it, add the Inspector sidebar, and send the exported HTML with POST /emails.
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, and it exports email-ready HTML you can hand straight to MailBlastr. This guide covers embedding it in a React app, theming it, customizing the Inspector, and sending the result.
Prerequisites
- A React 18+ application (Vite, Next.js, or Remix, for example).
- A bundler with package exports support (the default for the frameworks above).
- 1Install the editor
Add @react-email/editor and import the bundled theme stylesheet once at your app root.
- 2Mount the base editor
The standalone EmailEditor component comes with the default extensions, menus, and theming wired up.
- 3Style the editor
Override the --re-* CSS variables on a wrapper class to match your brand; the default theme also follows prefers-color-scheme and a .dark ancestor class.
- 4Add the Inspector sidebar
Pass Inspector.Root as a child of EmailEditor so it shares the editor context and swaps panels based on the selection.
- 5Send the result with MailBlastr
Read the exported HTML via the editor ref, POST it to your own server, and forward it to MailBlastr.
Install
npm install @react-email/editorimport '@react-email/editor/themes/default.css';Mount the base editor
'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. For full control over which extensions load, drop down to the lower-level setup with EditorProvider.
Theme it to your brand
The editor exposes two styling layers: the chrome (menus, palette, Inspector) driven by --re-* custom properties and data-re-* selectors, and the content area styled via TipTap’s .tiptap class. Override the variables on a wrapper class:
.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;
}
[data-re-bubble-menu-item][data-active] {
background: #2563eb;
color: #fff;
}
.tiptap h1 {
font-size: 1.75rem;
letter-spacing: -0.025em;
}Add the Inspector sidebar
The Inspector is a contextual sidebar that swaps panels based on the current selection — document defaults, node properties, or text formatting. Pass Inspector.Root as a child of EmailEditor so it shares the editor context:
'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>
);
}Send the exported HTML with MailBlastr
The standalone editor exposes its output through ref methods. Read the HTML in a handler, POST it to your server, and forward it to MailBlastr with POST /emails — never call MailBlastr from the browser, where your key would be exposed.
| Method | Returns | Description |
|---|---|---|
getEmailHTML() | Promise<string> | Email-ready HTML |
getEmailText() | Promise<string> | Plain-text version |
getEmail() | Promise<{ html, text }> | Both in one call |
import { EmailEditor, type EmailEditorRef } from '@react-email/editor';
import { useRef } from 'react';
export function MyEditor() {
const ref = useRef<EmailEditorRef>(null);
const handleExport = async () => {
if (!ref.current) return;
const html = await ref.current.getEmailHTML();
// Send via YOUR server-side endpoint — the key stays on the server.
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 & send</button>
</div>
);
}import { MailBlastr } from 'mailblastr';
const mb = new MailBlastr(process.env.MAILBLASTR_API_KEY);
export async function POST(req: Request) {
const { html } = await req.json();
const { data, error } = await mb.emails.send({
from: 'Acme <hello@yourdomain.com>',
to: ['customer@example.com'],
subject: 'Your newsletter',
html,
});
if (error) return Response.json(error, { status: 422 });
return Response.json(data);
}