Blocks MailCraft

Drag-and-drop email editor as a Web Component. No dependencies, no framework, no build step.

Live demo · Documentation

npm install @seliseblocks/mailcraft

Use

import '@seliseblocks/mailcraft';   // registers <mailcraft-editor>
<mailcraft-editor id="editor"></mailcraft-editor>

Or mount it into a container from code, without writing the tag:

import { createEditor } from '@seliseblocks/mailcraft';

const editor = createEditor('#mail', { html, toolbar: false });
editor.exportHtml();
editor.destroy();

That is a working editor. It speaks HTML in both directions; there is no document format to store or migrate:

editor.loadTemplate({ name: 'Welcome', html });   // give it HTML
const html = editor.exportHtml();                 // get send-ready HTML back

Exported HTML is valid input to loadTemplate, so saving the export is saving the user's work.

Configure

variables="first_name,company" merge tags, exported as {{first_name}}
Condition / Loop blocks dynamic content, exported as {{#if …}} / {{#each …}} for your sending engine
locale="de" 31 languages, RTL automatic
theme="light" | "dark" host owns light/dark; hides the built-in toggle
ui-font="inherit" match your app's font
accent="#e11d48" your brand color, everywhere the editor is accented; also var(--brand) or inherit
toolbar="none" hide the editor's own top bar
footer="none" drop the "Powered by SELISE Blocks © 2026" strip, or pass your own line
toolbar="undo,redo,export" …or keep only these parts
.storageProvider + .storageLimits image uploads to your backend
.aiProvider async (prompt) => text, powers the AI draft panel
.messages override any UI string

Every attribute is also a property. TypeScript declarations ship with the package. Full reference and integration recipes: DOCS.md.

Paste into your AI agent

Everything an assistant needs to wire this up correctly:

PACKAGE   @seliseblocks/mailcraft: Web Component, zero runtime deps
IMPORT    import '@seliseblocks/mailcraft'   (side effect: registers the element)
          Safe to import under SSR; the element itself renders in a browser only.
TYPES     TypeScript declarations ship in the package (types/index.d.ts) for every
          named export; 'mailcraft-editor' is registered in HTMLElementTagNameMap.
ELEMENT   <mailcraft-editor id="editor"></mailcraft-editor>
MOUNT     createEditor(target, options) -> handle   (no tag needed)
          target  = CSS selector or Element; throws if it matches nothing
          options = html, name, variables, locale, dir, theme, uiFont, accent, toolbar,
                    footer, storageProvider, storageLimits, aiProvider, iconProvider,
                    messages, height, replace, onChange(doc), onExport(html)
                    name = template name for `html`; read only alongside it
          handle  = the METHODS below + .element + .destroy()
          The container supplies the height; the editor fills it.

DATA      HTML in, HTML out. There is no JSON document format in the public API.
          editor.loadTemplate({ name, html })   apply HTML  (undoable, deep-copied)
          editor.importHtml(html)               apply HTML  (same importer)
          editor.exportHtml() -> string         send-ready email HTML
          Persist by storing exportHtml() and passing it back to loadTemplate().
          exportHtml({ markers: false }) omits the data-mc* fidelity markers
          (pristine HTML; countdown/video/box/code/CSS blocks and flex/grid
          rows then reload lossily).
          Unclassifiable markup survives as a raw-HTML block. Nothing is dropped.
          Condition/Loop blocks export literal {{#if expr}}/{{#each expr}} tags
          (always balanced) for the host's templating engine; never evaluated
          by the editor, and re-import restores them as blocks.

EVENTS    'change'  detail = internal doc (do not persist this)
          'export'  detail = HTML string

ATTRS     variables="a,b,c" | locale="de" | theme="dark" | dir="rtl"
          ui-font="inherit" | accent="#e11d48" | accent="var(--brand)"
          toolbar="none" | toolbar="undo,redo,export" | footer="none"
PROPS     .variables .toolbar .footer .uiFont .accent .messages .aiProvider .iconProvider
          .storageProvider .storageLimits
METHODS   exportHtml() importHtml(html) loadTemplate(tpl) undo() redo()
          screenshotPng({format:'png'|'jpeg'|'webp', quality, scale}?)
          previewScreenshot() downloadScreenshot() copyScreenshot()

KEYS      Bound on window, so they survive toolbar="none":
          Esc (leave field / deselect + close), Ctrl/Cmd+Z, Shift+Ctrl/Cmd+Z,
          Ctrl/Cmd+E (export dialog), Ctrl/Cmd+K (link, while editing),
          Ctrl/Cmd+D (duplicate), Backspace|Delete (delete selection).

UPLOADS   A provider plus `maxBytes` are required, or every upload is refused;
          every image type is allowed unless `accept` narrows it.
          editor.storageLimits   = { maxBytes: 2*1024*1024,
                                     accept:['image/jpeg','image/png'] }  // accept optional
          editor.storageProvider = { list(q), upload(file, o), folders?(), remove?(a),
                                     limits? }
          provider.limits is merged per key under .storageLimits, and satisfies
          the requirement on its own. With no provider the library is empty and
          local to the draft; the package ships no files of its own.
          The editor never talks to a backend of its own; its only fetch is the
          screenshot capture inlining the template's own images.

TOOLBAR   Parts: logo status device undo redo theme ai code preview export
          Attribute = allow-list (keep these). Property = { part: false } (drop these).
          none|hidden|off|false = no bar at all; all = the default.
          Hidden undo/redo/export stay reachable: methods, plus Ctrl/Cmd+Z, +E.
          preview, code and ai are bar-only: hiding them removes the panel.

DO NOT    Do not use getContent()/setContent(): internal, shape may change.
          Do not expect a campaign/title option; there is none. <title> is "Email".

Develop

npm install && node build.js && npm test

examples/vanilla.html is a complete host page: open it directly, no server needed. It ships in the npm package too, so it is there after an install; the hosted copy is the same file.

MIT