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

@softsync-ai/email-editor

v0.1.5

Published

React-only visual email editor with browser-local design storage.

Readme

@softsync-ai/email-editor

React-only visual email editor for JSON email designs, including block palette, responsive canvas, block inspector, design settings, merge tags, preview mode, undo/redo, browser-local design library, and HTML/MJML/JSON export.

Run the demo

npm install
npm run sample

The sample site is pinned to http://localhost:4455. Vite runs with --strictPort, so it fails clearly if port 4455 is already in use instead of silently moving to another port. To share the running sample site over an ngrok tunnel, use:

npm run ngrok

That command starts the sample site on port 4455 if it is not already running, starts ngrok against that exact port, prints the public HTTPS URL, and shuts down both processes on Ctrl-C.

The editor is responsive and can be opened directly from a phone using the ngrok URL. On narrow screens the block palette scrolls horizontally, the email canvas fits the available width, and the inspector is available below the canvas. The Mobile control previews the exported email at 375px.

Embed it

import { EmailEditor, createBrowserFileSystemProvider } from "@softsync-ai/email-editor";
import "@softsync-ai/email-editor/style.css";

const templates = createBrowserFileSystemProvider();

<EmailEditor
  templates={templates}
  mergeTags={[
    { label: "First name", value: "{{first_name}}", sample: "Alex" },
    { label: "Company", value: "{{company}}", sample: "SoftSync" },
  ]}
  theme={{
    primary: "#2457d6",
    surface: "#ffffff",
    radius: "8px",
  }}
/>

Embed the email signature editor

EmailSignatureEditor is the provider-neutral signature module from the same package. It uses the same theme contract as EmailEditor, supports explicit light/dark/auto UI themes, and does not persist or send the entered details.

import {
  EmailSignatureEditor,
  createDefaultEmailSignature,
} from "@softsync-ai/email-editor";
import "@softsync-ai/email-editor/style.css";

<EmailSignatureEditor
  initialValue={createDefaultEmailSignature({ company: "DoneAI", color: "#8060ee" })}
  branding={{ name: "DoneAI" }}
  uiTheme="dark"
  theme={{
    primary: "#8060ee",
    dark: { surface: "#202030", panel: "#303040" },
  }}
  onOpenEmailEditor={() => setEmailEditorOpen(true)}
/>

Use emailEditorHref instead of onOpenEmailEditor when the host has a route. The formatted and HTML copy actions are built in. For persistence or backend integration, use onChange and keep the signature data in the host application. signatureToHtml(value) is also exported for non-React integrations.

The editor uses the browser File System Access API for persistence. The first Open or Save action asks the user to select a folder, then stores designs as JSON files in that folder. No localStorage or Origin Private File System (OPFS) fallback is used. Embedding applications can also call createBrowserFileSystemProvider().pickDirectory() from their own file-store control before rendering the editor.

File System Access is supported by Chromium-based browsers. Browsers without the API show a clear save/open error; a backend adapter can be supplied through the same EmailTemplateProvider interface when broader browser support is needed.

The provider is intentionally backend-shaped. A server adapter only needs the same load, create, and save methods; the component and exported JSON do not change.

Rendering is a separate seam. Supply render.toMjml, render.toHtml, or render.compileMjml when the host wants a backend renderer; otherwise the client bundle produces local HTML/MJML. Custom blocks can declare field schemas, templates, async data sources, and stylesheets without taking a dependency on a server.

Design and Templates

The sample and an editor without initial content start with Your workspace is ready. Explicit initialTemplate / initialContent take precedence. Use initialContent={createDefaultTemplateContent()} for an empty initial design; the New design action still creates a blank draft.

White-label editor branding

<EmailEditor
  branding={{
    name: "Your Brand",
    logo: <img src="/your-wordmark.svg" alt="Your Brand" />,
    footer: false,
  }}
/>

The logo can be an image, inline SVG or React component. Without a logo, the brand name is displayed. branding={false} removes SoftSync branding from both the header and footer; the neutral New button remains. This controls editor chrome, not email content: the starter's editable logo images should be replaced separately.

All dropdowns use the shared, theme-local Select component (keyboard navigation, type-to-jump, Escape, and touch). The rich-text toolbar wraps at narrow widths. Use Show/Hide design tools and Show/Hide properties to expand or collapse either panel. On narrow screens they become animated, scrollable accordions above the canvas; selecting a block opens its properties. Reduced-motion is respected.

The Design tab includes 1-, 2-, 3-column, 2:1 and 1:2 layout presets. Empty columns have touch-friendly Add text buttons; drag other blocks into them. Use the parent section button on a selected child to change its layout. Reducing the column count moves remaining content into the final column rather than deleting it.

Importing existing HTML

Import HTML reads a local UTF-8 HTML file (up to 5 MB) and converts it into the normal editable JSON block format. Headings, rich text, images, buttons, dividers, tables and common one-/two-/three-column email layouts can be edited immediately. Variable tokens remain intact. Conversion is undoable.

Existing source-mode documents have a Convert to editable blocks button. The browser-only importHtml(html) export returns { content, warnings } for hosts. Inline styles and basic stylesheet rules are mapped; complex nesting is flattened, unsupported CSS is reported, and active content/unsafe URLs are removed. Always review the result: arbitrary HTML cannot be converted losslessly into this block model. Relative assets need public HTTPS URLs.

For exact source retention, hosts can still provide sourceHtml in TemplateContent. That mode keeps original HTML/CSS, provides sandboxed preview and source editing, and supports HTML/JSON export, not MJML. It intentionally does not sanitise exported source; use trusted files. Converting to blocks removes source mode.

The left panel has Design (editable blocks) and Templates tabs. The gallery contains 30 starters across onboarding, campaigns, meetings and events, purchases, sales follow-up, and retention. Search or filter by category, preview at desktop or mobile width, then choose Use template. Applying replaces the current design in one undoable action; every section, heading, button, and table remains editable. Each application generates fresh block IDs. The 30 independently authored layouts include personal sales notes, itemised receipts, event tickets, editorial newsletters, product launches and retail purchase journeys. Preview dialogs include a suggested subject; applying a template sets its preheader, but subject handling belongs to the host.

Every starter includes a replaceable Image block containing the actual SoftSync wordmark, rasterised from the partner brand kit (dark and reversed versions). Select it on the canvas and change its Image URL to your own hosted logo. The bundled PNG is embedded for portable local previews; many mail clients block data images, so replace it with a publicly accessible HTTPS image before sending. Photography is loaded from Unsplash, and product screenshots from softsync.ai; these remote images need an internet connection. Their source URLs are in src/starterAssets.ts. All are ordinary editable image blocks, not baked into HTML. Check imagery rights and replace product photography with your own approved assets.

All people, quotes, metrics, prices, dates, addresses, policies and programme terms are illustrative. Replace them with verified business details; links deliberately use example.com. Receipt/order amounts must be reconciled with real line items.

Starters include example links and variable names. Replace links, review variable names against your workspace fields, and add your own preferences/unsubscribe destination before sending a campaign. Onboarding starters are individual emails; sequence scheduling belongs to the host application.

Variables and apps/client integration

Headings and paragraphs support typing / at a word boundary to search for a variable, arrow keys + Enter to insert, Escape to dismiss, and clicking an option on touch devices. The toolbar picker inserts at the last text selection. Variables are non-editable badges in the text editor; saved text and HTML/MJML exports retain literal {{apiName}} tokens. Sample values are preview-only.

This matches apps/client/src/plugins/interactions/state/email.parser.ts: the client stores {{apiName}}, decodes it to @{apiName} while editing, and uses field-variable-badge spans for display. Imported client badge HTML and @{apiName} tokens normalize to the stored format here.

// JsonField[] is structurally compatible; the editor imports no client SDK.
<EmailEditor
  ref={editorRef}
  availableFields={table.fields}
  initialContent={savedDesign}
  templates={templateProvider}
/>

// Save the design JSON for future visual editing, alongside the email body.
const design = editorRef.current!.getContent();
const body = await editorRef.current!.toHtml(); // contains {{apiName}}
// apps/client can pass body through its existing decode/resolve/send pipeline.

availableFields takes precedence over mergeTags. Fields need only apiName; optional displayName or label, group, and sample customize presentation. The exported fieldsToMergeTags(fields) helper is available when a host prefers the existing mergeTags prop. Updating the supplied field list updates the pickers. Unknown tokens remain intact so editing cannot silently discard workspace data. Map starter field names to your real API names when they differ. Record lookup, field formatting, subject editing, and sending remain the host's responsibility. This prepares the package for client embedding; it does not install an editor mode into apps/client yet.

Imperative export

const editorRef = useRef<EmailEditorHandle>(null);

await editorRef.current?.toHtml();
await editorRef.current?.toMjml();
editorRef.current?.getContent();

Build the installable library with npm run build:lib. react and react-dom are peer dependencies, so the package does not bring a second React runtime into host apps.

One package, multiple hosts

Install the published package (after release), or install the tarball produced by npm pack. No source copying, router, auth, theme context, Redux, QueryClient, Tailwind, or UI-library provider is required. React and React DOM are peers.

"use client";
import { EmailEditor, type EmailTemplateProvider } from "@softsync-ai/email-editor";
import "@softsync-ai/email-editor/style.css";

export function HostEmailEditor({ mode, storage }: {
  mode: "light" | "dark";
  storage: EmailTemplateProvider;
}) {
  return <EmailEditor
    templates={storage}
    branding={{ name: "DoneAI", logo: <img src="/wordmark.svg" alt="DoneAI" />, footer: false }}
    uiTheme={mode}
    showThemeToggle={false}
    theme={{
      primary: "var(--brand-accent, #7357df)",
      primaryForeground: "#fff",
      surface: "var(--app-background, #fafafa)",
      panel: "var(--app-panel, #fff)",
      text: "var(--app-text, #222)",
      radius: "8px",
      dark: { surface: "#161622", panel: "#202030", text: "#fafafa" },
    }}
    starterTemplates={[]}
    availableFields={[{ apiName: "first_name", label: "First name" }]}
  />;
}

For SoftSync client, pass its wordmark, theme values and an adapter using the existing authenticated API. For SoftSync landing, pass its wordmark/theme and omit templates to use browser files. Host contexts are read by a thin host wrapper, never by this package. Keep adapter identity stable (module scope or useMemo). No API credentials or environment variables belong in this package.

starterTemplates replaces the example gallery; an empty array starts blank. Omitting it retains the 30 SoftSync examples and welcome design for backwards compatibility. Branding changes editor chrome only, never customer email content. Pass initialContent for an existing document; use the ref's setContent or a React key to switch documents (initial props are not controlled values).

Theme props map to scoped --ee-* CSS variables; omitted tokens use CSS defaults, so variables can also be inherited from a host wrapper. style takes final precedence. Dark overrides are merged only in dark mode. Editor colours never recolour exported email designs. Styles do not target host body/html elements; this is CSS scoping, not a Shadow DOM boundary against arbitrary host !important rules. Keep aggressive host resets scoped. Layout responds to the editor container, including a narrow embed inside a wide desktop page. Each instance owns its state.

When the host owns persistence and provides its own Apply/Save flow, set showStorageControls={false} to remove the browser-file Open and Save actions while retaining import/export and editing. This is the mode used by the client template dialog.

The bundle has a client boundary for React Server Component hosts; import CSS from the host's permitted global CSS entry point. The editor can render without browser globals on the server, but editing/file access requires hydration in a browser. Use a modern browser supporting container queries and ResizeObserver.

Package verification and release

npm test
npm run test:package
npm pack
# After reviewing the tarball, licensing and npm scope access:
npm publish --access public

prepack always builds JS, CSS and declarations; only dist and package metadata/ README are shipped. Sample builds go to demo-dist and cannot overwrite the package. test:package installs the tarball in a clean temporary React 18 consumer, checks TypeScript imports and CSS resolution, then renders without any host providers. The normal suite runs with React 19. No publish is performed by tests.

Branded starter templates

Keep all 30 starter designs and replace their editable logo images with your own:

import { EmailEditor, createBrandedStarterTemplates } from '@softsync-ai/email-editor';

const starterTemplates = createBrandedStarterTemplates({
  name: 'Your brand',
  logo: 'https://your-domain.com/logo.png',
  darkBackgroundLogo: 'https://your-domain.com/logo-white.png',
  width: 132,
});

<EmailEditor starterTemplates={starterTemplates} branding={{ name: 'Your brand' }} />;

Use public image URLs for email recipients. Each logo remains an ordinary editable image block. Omitting starterTemplates keeps the bundled gallery; passing [] hides its designs. Theme colors must be complete CSS colors: for host HSL channel variables, pass hsl(var(--card)), not var(--card).