@lp-labs/react-email-builder
v1.0.0
Published
A standalone React email editor with typed blocks and table-based inline HTML export.
Readme
@lp-labs/react-email-builder
Package version: 1.0.0. Requires React ^18.2.0 || ^19.0.0
as its only runtime peer. Named ESM and CommonJS exports include declarations;
TypeScript React consumers also need their application's React types.
A standalone React email editor for a deliberately small, typed block model. Its HTML export is built from presentation tables and inline styles; the only runtime dependency is React.
Install
npm install @lp-labs/react-email-builder reactImport the editor stylesheet once:
import "@lp-labs/react-email-builder/style.css";Use
import { useState } from "react";
import { EmailBuilder, type EmailDocument } from "@lp-labs/react-email-builder";
const initial: EmailDocument = {
version: 1,
blocks: [
{ id: "welcome", type: "heading", level: 1, text: "Welcome" },
{ id: "body", type: "text", text: "Thanks for subscribing." },
{ id: "cta", type: "button", text: "Visit us", href: "https://example.com" }
]
};
export function EmailEditor() {
const [email, setEmail] = useState(initial);
return <EmailBuilder value={email} onChange={setEmail} />;
}EmailBuilderProps (all fields optional):
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| value | EmailDocument | absent | Controlled document when supplied. |
| defaultValue | EmailDocument | { version: 1, blocks: [] } | Initial document in uncontrolled mode. |
| onChange | (document: EmailDocument) => void | absent | Called on each edit; controlled parents must store and pass back changes. |
| className | string | absent | Added to the editor root for customization. |
Use either value or defaultValue. The editor provides palette insertion, selection, reordering, deletion, text/link/image/style inspection, undo/redo for up to 50 edit snapshots, desktop/tablet/mobile preview widths, JSON validation and serialization, and HTML export. The preview is a visual approximation and is not generated by embedding exported HTML.
Supported model and utilities
EmailDocumentis{ version: 1; blocks: EmailBlock[] }. SupportedEmailBlockvariants are text (text), heading (text,level1-3), image (src,alt), button (text,href), divider, and spacer (height0-1000 px). Blocks have unique, non-empty string IDs. Nesting, columns, arbitrary layout, and custom blocks are not supported.- Optional
stylefields are restricted to hexcolor/backgroundColor,padding(0-100 px),fontSize(8-48 px), andtextAlign(left,center,right). validateEmailDocument(unknown)returns path/message issues; empty means valid.parseEmailDocument(string)andserializeEmailDocument(EmailDocument)validate and throw anErroron malformed data.renderEmailHtml(EmailDocument)validates and exports a standalone HTML document.escapeHtml(string)is also exported.- URL checks trim outer whitespace and accept
http:,https:,mailto:,tel:, and non-scheme relative paths. After trimming, empty URLs, remaining control characters, protocol-relative paths, and all other schemes (includingjavascript:anddata:) are rejected. Text and attributes are escaped; the schema cannot carry arbitrary markup, JavaScript, event handlers, or free-form CSS.
Complete named exports
Runtime exports: EmailBuilder, validateEmailDocument, parseEmailDocument,
serializeEmailDocument, renderEmailHtml, escapeHtml, and isSafeUrl.
There is no default export. Type-only exports are EmailBuilderProps,
EmailAlign, EmailBlockStyle, EmailTextBlock, EmailHeadingBlock,
EmailImageBlock, EmailButtonBlock, EmailDividerBlock, EmailSpacerBlock,
EmailBlock, EmailDocument, and ValidationIssue.
Every block variant contains required id: string, its literal type, and
optional style: EmailBlockStyle. The additional required fields are:
| Exported variant | type | Additional fields |
| --- | --- | --- |
| EmailTextBlock | "text" | text: string |
| EmailHeadingBlock | "heading" | text: string, level: 1 \| 2 \| 3 |
| EmailImageBlock | "image" | src: string, alt: string |
| EmailButtonBlock | "button" | text: string, href: string |
| EmailDividerBlock | "divider" | None |
| EmailSpacerBlock | "spacer" | height: number |
EmailBlock is their union. EmailAlign = "left" | "center" | "right".
All EmailBlockStyle fields are optional: color?: string,
backgroundColor?: string, padding?: number, fontSize?: number, and
textAlign?: EmailAlign. Omitting style leaves renderer/browser defaults.
ValidationIssue = { path: string; message: string }.
Helper signatures and behavior:
validateEmailDocument(value: unknown): ValidationIssue[]reports supported field/value violations without throwing for ordinary malformed JSON values.parseEmailDocument(json: string): EmailDocumentthrows plainErrorfor malformed JSON or newline-separated validation issues.serializeEmailDocument(document: EmailDocument): stringvalidates and returns two-space-indented JSON; validation or native JSON serialization can throw.renderEmailHtml(document: EmailDocument): stringvalidates before rendering; invalid documents throwError. No async rendering or cancellation is involved.escapeHtml(value: string): stringescapes&,<,>, double and single quotes. It is an HTML text/attribute escape helper, not a general sanitizer.isSafeUrl(value: string): booleantrims for checking and implements the URL policy above; it does not normalize the returned document or assess trust, reachability, tracking, redirects, or email-client compatibility.
Standalone non-UI example:
import {
parseEmailDocument, renderEmailHtml, serializeEmailDocument, isSafeUrl
} from "@lp-labs/react-email-builder";
const email = parseEmailDocument(JSON.stringify({
version: 1,
blocks: [{ id: "hello", type: "text", text: "Hello <reader>" }]
}));
console.log(serializeEmailDocument(email), renderEmailHtml(email));
console.log(isSafeUrl("https://example.com")); // trueEmail rendering, accessibility, and limits
The renderer creates a fluid outer presentation table and a 600 px maximum-width inner table, uses table rows/cells for blocks, and emits inline styles rather than relying on an external stylesheet. It includes a basic viewport meta tag and semantic language/title metadata. This is not a claim of universal email-client rendering: clients vary in support for image sizing, CSS, links, fonts, and responsive behavior. Preview width modes do not emulate any particular mail client. Inspect and test exported messages in your target clients before use.
Controls use native labeled inputs, buttons, and selects. Preview headings, text, links, image alt text, separators, and spacer semantics are retained where appropriate. There is no drag/drop, rich-text formatting, asset upload, custom template engine, personalization/merge tags, email sending, or delivery/production guarantee. The JSON/HTML output fields are read-only; make edits with the palette and inspector.
Customize via className and the lp-email-* CSS classes, or render your own UI around the exported typed model.
Changes and helpers are synchronous. There is no autosave, subscription, worker,
or cancellation API; retain state/persist it in the parent and discard it on
teardown. Initial defaultValue is not a subsequent reset command. Undo history
and full HTML/JSON generation copy/scan the document; there is no virtualization
or block-count performance guarantee. External image URLs may load in the
preview; the library does not upload assets or send email. Import alone starts no
browser work. Use an application React renderer and DOM-capable browser for the
interactive editor; styles affect the editor, not exported HTML.
Project
License
MIT.
Support
If this library is useful to you, consider supporting its maintenance with a donation via PayPal.
Author
Created by Libin Prasanth. Connect on LinkedIn for updates and discussion.
