@mark_stagepanda/email-studio-dropin
v0.1.5
Published
Drop-in React TypeScript email studio builder
Readme
@mark_stagepanda/email-studio-dropin
A drop-in, TypeScript-ready React email studio that can be embedded in Stagepanda or any React app.
Current package version: 0.1.2
What it provides
- Drag/drop email block editor UI
- Editable HTML view (paste/edit HTML and apply it to canvas)
- Image upload + on-canvas resize/rotate handles
- Right-side block settings panel
- Mobile/desktop preview modes
- Undo/redo history (20 activities) with version dropdown restore
- File-based starter templates from
template-library/folders - Typed
onStateChangecallback for persistence
Install
Option A: local package (recommended during development)
From this repo root:
cd packages/email-studio-dropin
npm install
npm run build
npm packThen in your target React app (for example, Stagepanda):
npm install /absolute/path/to/packages/email-studio-dropin/<generated-tarball>.tgzOption B: publish to npm (for third-party users)
cd packages/email-studio-dropin
npm publish --access publicThen consumers install with:
npm install @mark_stagepanda/email-studio-dropin
# or pin this release explicitly:
npm install @mark_stagepanda/[email protected]Usage
import React, { useState } from "react";
import {
EmailStudioDropin,
EmailStudioOutput,
EmailStudioState,
exportEmailHtmlFromState,
} from "@mark_stagepanda/email-studio-dropin";
export default function EmailBuilderPage() {
const [editorState, setEditorState] = useState<EmailStudioState | null>(null);
const [currentHtml, setCurrentHtml] = useState("");
const [output, setOutput] = useState<EmailStudioOutput | null>(null);
return (
<div style={{ height: "calc(100vh - 64px)" }}>
<EmailStudioDropin
height="100%"
initialHtml="<!doctype html><html><body>...</body></html>"
defaultTemplateId="library:invitations/event-invitation"
onStateChange={(state) => setEditorState(state)}
onHtmlChange={(html) => setCurrentHtml(html)}
onOutputChange={(next) => setOutput(next)}
bootstrap={{
companyName: "Stagepanda",
socialLinks: {
facebook: "https://facebook.com/stagepanda",
linkedin: "https://linkedin.com/company/stagepanda",
instagram: "https://instagram.com/stagepanda",
youtube: "https://youtube.com/@stagepanda",
stagepanda: "https://stagepanda.com",
},
}}
/>
</div>
);
}Exporting for email clients
The editor top bar includes built-in export controls with presets for Universal, Gmail, and Outlook.
You can also export programmatically:
const html = exportEmailHtmlFromState(editorState, { client: "outlook" });Programmatic export helper:
exportEmailHtmlFromState(state, { client?: "universal" | "gmail" | "outlook", width?: number })downloadEmailHtml(state, { client?: "universal" | "gmail" | "outlook", filename?: string, width?: number })importHtmlToBlocks(html)to map pasted HTML into editable design blocks
Props
initialState?: Partial<EmailStudioState>Seed initial editor data (name, blocks, preview, etc).initialHtml?: stringOptional email HTML string to import as editable blocks on load. IfinitialState.blocksis provided, it takes precedence overinitialHtml.defaultTemplateId?: stringSets the starter template wheninitialState.blocksis empty. Use a generated ID fromtemplate-library(for examplelibrary:invitations/event-invitation).onStateChange?: (state: EmailStudioState) => voidCalled whenever editor state changes.onHtmlChange?: (html: string) => voidCalled whenever editor state changes with the latest generated email HTML.onOutputChange?: (output: { json: EmailStudioState; html: string; subject: string; preheader: string }) => voidCalled whenever editor state changes with JSON, HTML, plus email subject and preheader.htmlClient?: "universal" | "gmail" | "outlook"Chooses the HTML generation preset used byonHtmlChange.className?: stringClass name on root container.style?: React.CSSPropertiesAdditional inline root styles.hideTopBar?: booleanHide the top action bar if embedding into an existing shell.height?: number | stringRoot editor height ("100vh"by default).fontHref?: stringOverride the loaded webfont URL.bootstrap?: { companyName?: string; socialLinks?: Partial<Record<"facebook" | "twitter" | "x" | "linkedin" | "instagram" | "youtube" | "stagepanda", string>> }Optional initializer. Company name is auto-applied to footer block content; social links are auto-applied to matching social icons.
Templates
- Template selection is opened from
Create campaign +and shown in a bottom-sheet selector. - The selector shows categories on the left and template thumbnails on the right.
- Selecting a template replaces the current canvas blocks.
- Folder-generated templates are exported from package:
TEMPLATE_PRESETSDEFAULT_TEMPLATE_IDcreateTemplateBlocks(templateId, bootstrap?)
File-Based Template Categories (Primary Folder)
You can manage templates from files using:
packages/email-studio-dropin/template-library/
Rules:
- Every subfolder is a category.
- Every
.htmlfile in that subfolder is a template. - Template name = filename without
.html.
Example:
template-library/
event-invitation/
Launch-Invite.html
reminders/
Last-Call.htmlAfter adding files, run:
npm run build --prefix packages/email-studio-dropinThe build auto-generates src/emailStudio/generatedTemplateLibrary.ts and those templates appear in the Create Campaign template selector by category.
Development hot reload:
npm startThis now runs watchers for template-library and drop-in TypeScript build, so edits/new HTML files are regenerated automatically during development.
Integration notes for any email project
- Persist
onStateChangeoutput in your backend (JSON). - Restore saved drafts via
initialState. - Wrap the component in a fixed-height container (
height: 100%or viewport-calculated) to avoid layout collapse. - Keep
reactandreact-domas host app dependencies (peer deps).
Stagepanda-specific integration checklist
- Create a route/page for the editor and mount
EmailStudioDropinin a full-height container. - Connect
onStateChangeto your draft autosave endpoint. - Optionally pass
hideTopBarif Stagepanda already has global actions. - Persist and rehydrate
initialStatefor editing existing templates.
Block Extension Workflow
When adding a new content block type, update these extension points:
src/emailStudio/constants.ts
- Add the block to
BLOCKS(left panel + insert menu). - Add default config in
DEFAULTS.
src/emailStudio/components/icons.tsx
- Add
BlockIconrendering for the newtype.
src/EmailStudioDropin.tsx
- Ensure
actions.addBlockmaps/normalizes the block type if needed. - Add any bootstrap-driven defaults.
src/emailStudio/components/Canvas.tsx
- Add render branch in
renderBlockContentfor canvas preview/editing.
src/emailStudio/components/RightPanel.tsx
- Add settings panel UI branch for the block.
- Keep settings updates isolated where possible (small subcomponents per block).
src/emailStudio/exportHtml.ts
- Add HTML export branch for the new block.
src/emailStudio/importHtml.ts(optional)
- Add reverse mapping if HTML import should produce this block.
- Regression coverage
- Add/update tests for:
- insert behavior
- settings behavior
- export output
- any bootstrap/default interactions
Distribution Verification
Before publishing, run:
npm run verify:dropinThis runs clean build and package dry-run checks for the drop-in.
