@edmissioncrm/editor
v0.2.3
Published
Embeddable drag-and-drop email and document builder for React, a drop-in react-email-editor replacement.
Maintainers
Readme
@edmissioncrm/editor
An embeddable, drag-and-drop email and document builder for React, a drop-in <EmailEditor> that talks to
your own backend for uploads, AI and file storage.
- Drag-and-drop editor (17 blocks: heading, text, image, button, divider, spacer, social, video, GIF, HTML, menu, form, timer, table, carousel, file and saved templates)
- Email and PDF / document modes; export clean email-safe HTML and the design JSON
- Merge tags that stay intact, desktop and mobile preview, and inbox previews (Gmail, Apple Mail, Outlook, Yahoo)
- Undo / redo, saved versions with compare and restore, custom themes
- AI helper in every text field: improve writing, fix grammar, shorter / longer, change tone
- Image library: upload, Amazon S3 and Google Drive
- Video block that plays: upload a video file (its first frame becomes the thumbnail) or paste a YouTube / Vimeo link
- Form block with a picker for your published forms, shown live in the canvas and the preview
- Optional email size limit with a live size badge
- Opens existing
react-email-editordesigns and keeps its component API, so migrating is a one-line change
Install
npm install @edmissioncrm/editor
# peer deps: react >= 18, react-dom >= 18The editor's CSS is injected automatically; there is nothing else to import.
Quick start
import EmailEditor, { type EmailEditorRef } from "@edmissioncrm/editor";
import { useRef } from "react";
export function MyEditor() {
const ref = useRef<EmailEditorRef>(null);
const save = () =>
ref.current?.editor.exportHtml(({ design, html }) => {
// persist `design` (JSON) and send `html`
});
return (
// the editor fills its parent, so give the parent a height
<div style={{ height: "100vh" }}>
<button onClick={save}>Save</button>
<EmailEditor
ref={ref}
edmission={{ apiBaseUrl: "https://api.example.com" }} // your backend
options={{ displayMode: "email" }} // or "document" for PDF
onReady={() => console.log("editor ready")}
/>
</div>
);
}Moving from react-email-editor: change the import to @edmissioncrm/editor. Designs saved by
react-email-editor are converted automatically when you load them.
Ref API
ref.current.editor exposes a react-email-editor compatible handle:
loadDesign(design)/saveDesign(cb), the design JSONexportHtml(cb), email-ready HTML together with the designsetMergeTags(tags)(same asoptions.mergeTags)addEventListener(event, handler)/removeEventListener(event, handler), e.g."design:updated"
Configuration
PDF templates: pass options={{ displayMode: "email", inboxPreview: false }} (or displayMode: "document"). The preview then has no Gmail / Apple Mail / Outlook switcher, and a fixed-width page (an A4 letter) is shown complete at its normal size, never cut off and never with a horizontal scrollbar. Emails keep the inbox previews.
Your own blocks go in customTools. Each one shows up in the content palette, draws itself on the canvas, is edited through
its inspector fields, and exports through exportHtml. A field of type file (or image) gives the user an Upload button with a
progress bar (and an "Are you sure you want to cancel this upload?" question), a drop area and a link box. The value is the file URL;
its name, type and size are saved next to it as <key>_name, <key>_mime and <key>_size. Uploads go through your uploadPath.
const brochure = {
id: "brochure", label: "Brochure", values: { title: "Download our brochure", file: "" },
render: (v) => <div><strong>{v.title}</strong> {v.file_name || "No file yet"}</div>,
exportHtml: (v) => `<a href="${v.file}">${v.title}</a>`,
inspector: [{ key: "title", label: "Title", type: "text" }, { key: "file", label: "Brochure file", type: "file" }]
};
const tools = [brochure]; // define once, outside the component (or useMemo it)
<EmailEditor customTools={tools} />Merge tags go in options.mergeTags (the react-email-editor format or a flat map):
options={{ mergeTags: { first_name: { name: "First name", value: "{{first_name}}", sample: "Aditi" } } }}Backend connection is the edmission prop. Every option has a default:
| Option | Default | Description |
| --- | --- | --- |
| apiBaseUrl | page origin | Base URL of your API |
| orgId | first UUID in the page URL | Sent as the organization-id header |
| authToken | localStorage.token | Sent as Authorization: Bearer … (session cookies are always sent too) |
| uploadPath | /buzz-communication/upload/template-assets | Image upload endpoint |
| aiGeneratePath | /mail-templates/ai-generate | AI endpoint |
| s3ListEndpoint | ${apiBaseUrl}/buzz-communication/upload/template-assets | Files shown in the Amazon S3 tab |
| googleClientId | none | Shows the Google Drive tab (OAuth Web client id) |
| formsPath | /enquiry-forms/published-links | Lists your published forms and their links |
| formUrlBase | none | Public form link prefix (the form's link id is appended). Turns on the Form block's form picker |
| maxEmailBytes | none | Largest the email may get in bytes (text plus images and videos). Uploads that would pass it are refused and a size badge is shown |
Your backend holds every secret; the editor never does. Requests are sent with
credentials: "include" and the organization-id / Authorization headers, so allow those in CORS.
| Endpoint | Request | Response |
| --- | --- | --- |
| Upload POST {uploadPath} | multipart form, field file | { "file": { "url": "https://…" } } |
| AI POST {aiGeneratePath} | JSON { prompt, allowedVariables?, currentSubject?, currentHtmlContent? } | { "subject": "…", "htmlContent": "<html>…" } |
| List GET {s3ListEndpoint} | none | { "items": [{ "id", "key", "url", "name", "kind", "size" }] }, kind is image, gif, video, pdf or file, size is in bytes |
| Forms GET {formsPath} | none | [{ "id", "title", "urls": [{ "url_id", "label" }] }] |
When a request fails, the editor shows the message your backend returns, so send a readable one (for example "File too large").
Return only the signed-in organization's files from the list endpoint, and use public URLs for uploaded images so they load in the recipient's inbox.
Google Drive needs an OAuth Web application client with the Google Drive API and Google Picker API
enabled, the drive.file scope, and your app's origin under Authorized JavaScript origins. Picked files are
set to "anyone with the link" so they display in sent emails.
Branding: logoUrl / logoAlt replace the logo at the bottom of the side panel.
Typed component: EdmissionEmailEditor is also exported, for apps that prefer props
(initialDesign, onDesignChange, builder) and a ref with getDesign(), exportHtml() and loadDesign().
If you use Vite and see an "Invalid hook call", add resolve: { dedupe: ["react", "react-dom"] }.
License
MIT, see LICENSE. Licences of the bundled open-source packages are in THIRD_PARTY_LICENSES.md.
