npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@edmissioncrm/editor

v0.2.3

Published

Embeddable drag-and-drop email and document builder for React, a drop-in react-email-editor replacement.

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-editor designs and keeps its component API, so migrating is a one-line change

Install

npm install @edmissioncrm/editor
# peer deps: react >= 18, react-dom >= 18

The 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 JSON
  • exportHtml(cb), email-ready HTML together with the design
  • setMergeTags(tags) (same as options.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.