react-email-studio
v7.0.0
Published
Visual React email editor: JSON design in/out; HTML for preview.
Downloads
2,023
Maintainers
Readme
react-email-studio
Visual email editor for React — drag-and-drop layouts and blocks, rich text, images, buttons, tables, and more. Designs save as JSON (email_document); use jsonToHtml() to generate HTML for preview and your ESP (SendGrid, SES, Mailgun, …).
Release notes
Latest: 7.0.0 (npm) — unified options.palette, blockPresets / widgetPresets / customWidgets, controlled options + onOptionsChange.
7.0.0 — major release: palette + presets + custom sidebar widgets; breaking: removed ref.setOptions() / ref.getOptions() (use React state + mergeEditorOptions). Aligned with [email protected] and [email protected].
6.2.0 — runtime editor options; image padding preserved on HTML/JSON round-trip.
6.1.0 — standalone react-studio-color-picker dependency; transparent page/content backgrounds; empty canvas on load; floating section drag handle; static settings cards.
6.0.0 — 14 UI locales; premium chrome; canvas floating actions; rich-text/HTML toggles on formatting toolbar; merge-tag auto-linking; context menu removed.
See 5.2.x for Widgets palette, demo footer rows, and Preview toolbar chip. See 5.1.0 for email-client-safe export, Outlook VML backgrounds, and inspector action icons.
See 5.0.0 for major export pipeline changes (MJML compile removed; studio limited to universally supported HTML).
See 3.11.0 for dark mode export, file block, table-based social icons, merge tags, column width ratios.
See 3.10.x for HTML import round-trip of structured blocks (data-res-block markers).
Version history and migration hints: CHANGELOG.md (also included in the published npm tarball under node_modules/react-email-studio/CHANGELOG.md).
Features
- Multi-column layout rows and email-safe content blocks: heading, text, rich HTML, image, file (download link), button, link, divider, spacer, social (inline table + linked icons), menu, table, nested layouts
- Dark mode (inbox) — optional
color-schememeta tags and dark-theme CSS in exported HTML (Settings → Dark mode (inbox); on by default) - Page & content backgrounds (solid, gradient, image), responsive preview
- HTML import / export — structured blocks round-trip via
data-res-blockmarkers; rich HTML is sanitized on export (no scripts, iframes, or forms) - Uploads —
onUpload+options.features.upload.enabledfor server-hosted images and file icons (publichttps://URLs only) - Widgets — drag-in row sections (marketing, LMS, footer) built from supported blocks only
- Undo / redo, device preview modal
- Internationalized UI strings (
en,fr,de,es) - Template placeholders (e.g.
{{first_name}}) in text / HTML blocks - Tree-shakeable ESM + CJS, TypeScript types
Blocks that do not render reliably in major clients (live/static countdowns, video embeds) are not in the palette; legacy designs that contain them skip those blocks on load. Social uses a presentation table row with linked icon cells (no inline SVG).
Installation
npm install react-email-studioThis also installs react-studio-color-picker (inspector color fields). To use the picker on its own:
npm install react-studio-color-picker lucide-reactpnpm add react-email-studio
yarn add react-email-studioPeer dependencies
Install these in your application (versions should satisfy peerDependencies in this package):
react, react-dom, lucide-react, and TipTap v3 (@tiptap/react, @tiptap/core, @tiptap/starter-kit, @tiptap/extension-link, @tiptap/extension-placeholder, @tiptap/extension-text-align, @tiptap/extension-text-style, @tiptap/extension-underline).
Quick start
import { useRef } from "react";
import {
ReactEmailEditor,
type ReactEmailEditorRef,
jsonToHtml,
htmlToJson,
} from "react-email-studio";
export function App() {
const editorRef = useRef<ReactEmailEditorRef>(null);
return (
<>
<button
type="button"
onClick={() =>
editorRef.current?.exportJson((json) => {
// Persist JSON (API / database)
fetch("/api/save-email", { method: "POST", body: json });
}, true)
}
>
Save design
</button>
<ReactEmailEditor
ref={editorRef}
hideTemplates
onReady={(api) => {
/* api.loadJson(existingDesign); */
}}
onUpload={async (file) => {
const url = await uploadToYourStorage(file); // must return a public URL
return url;
}}
options={{
locale: "en", // en | es | fr | de | pt | it | nl | pl | tr | ar | hi | zh | ja | ko
features: { upload: { enabled: true } },
palette: ["logo", "hero", "image", "button", "heading"],
blockPresets: {
button: { label: "Shop now", bgColor: "#111827", href: "https://shop.example" },
heading: { content: "Welcome", fontSize: 32, align: "center" },
},
widgetPresets: {
hero: { headline: "Welcome back", buttonLabel: "Get started" },
logo: { src: "https://cdn.example/logo.png", alt: "Acme" },
},
}}
/>
</>
);
}
// Generate HTML for sending (server or client; includes dark-mode CSS when enabled in settings)
const html = jsonToHtml(savedDesignJson, {
customCSS: "/* optional extra styles */",
});
// Import existing HTML into the editor (fragment or full document)
const designJson = htmlToJson("<p>Hello from HTML</p>", true);
editorRef.current?.loadJson(designJson);Implement onUpload so image uploads return public HTTPS URLs your recipients can load.
Package exports
| Export | Description |
|--------|-------------|
| ReactEmailEditor | Main editor component (forwardRef). |
| ReactEmailEditorProps | Component props type. |
| ReactEmailEditorRef | Imperative API: loadJson, exportJson. |
| jsonToHtml | (design, opts?) => string — full HTML email document. |
| htmlToJson | (html, pretty?) => string — HTML fragment/page → email_document JSON. |
| htmlToEmailDesignTemplate | (html) => EmailDocument \| null — same conversion as object. |
| canonicalizeEmailDocument | (input) => EmailDocument \| null — normalize import/export JSON. |
| extractHtmlForDesign | (html) => string — pull body/fragment from a full HTML document. |
| ReactEmailEditor.jsonToHtml / .htmlToJson | Static helpers on the component. |
| utf8ToBase64, base64ToUtf8 | Encoding helpers. |
| EmailPreviewModal | Standalone responsive preview modal. |
| emailPreviewDevices | Device presets for preview. |
| EmailPreviewModalProps, MobilePreviewVariant | TypeScript types. |
| ReactEmailEditorOptions, JsonToHtmlOptions, EmailHtmlOptions | Editor options (palette, features, …) and HTML generation options. |
| mergeEditorOptions | Deep-merge helper for onOptionsChange handlers. |
| PaletteInput, PaletteGroupInput | Types for options.palette / top-level palette prop. |
| BlockPresetProps, BlockPropKey | Types for options.blockPresets (per-block default props). |
| WidgetPresetProps, WidgetPresetKey | Types for options.widgetPresets (widget row default copy). |
| resolveWidgetPreset, resolveFooterWidgetPreset | Resolve preset props for built-in widget rows. |
| CustomWidgetDef, CustomWidgetRowTemplate | Types for options.customWidgets. |
| makeCustomWidgetRow, resolveWidgetRowInsert, listCustomWidgets | Build and insert custom widget rows from palette config. |
| filterWidgetGroups, resolveWidgetGroupTitle | Filter/group custom widgets in the sidebar. |
| WidgetRowKey, WIDGET_ROW_KEYS, isBuiltInWidgetRowKey | Widget row keys for options.palette / options.widgets. |
| react-email-studio/convert | Same HTML ↔ JSON helpers as [email protected] (no React UI). |
| EmailDocument, EmailDocumentSettings, … | JSON schema types for stored designs. |
For a single-file release tutorial, see RELEASE.md. For extended framework setup (Next.js client/SSR), props tables, and troubleshooting, see TUTORIAL.md.
Documentation in this package
| File | Audience |
|------|----------|
| RELEASE.md | Single-file release guide — install, core API, palette, troubleshooting. |
| USER_README.md | User guide — editor UI, workflows, options overview, troubleshooting. |
| TUTORIAL.md | Integration tutorial — peers, save/load, Next.js/Vite, APIs, TypeScript. |
| README.md (this file) | npm landing page — install, quick start, exports. |
npm publish steps and optional demo hosting live in the monorepo DEPLOYMENT.md in the source repository.
License
MIT
