SendLit logoSendLit Docs
Email Blocks

EmailEditor

WYSIWYG editor for an email's content.

EmailEditor edits an Email's block content (text, separator, image, link by default) and reports changes via onChange; pass blocks to customize which block types are available.

If all you need is the editor and renderer — no filters, triggers, or the other blocks in this package — install @sendlit/email-editor directly instead. @sendlit/email-blocks re-exports it in full (EmailEditor, Email, EmailBlock, EmailMeta, EmailStyle, BlockComponent, renderEmailToHtml, defaultEmail) purely so the components below can share its types without you needing a second dependency.

Usage

Simple editor

import { useState } from "react";
import { EmailEditor, defaultEmail, type Email } from "@sendlit/email-blocks";

function Example() {
    const [email, setEmail] = useState<Email>(defaultEmail);

    return <EmailEditor email={email} onChange={setEmail} />;
}

Custom image uploader

Use blocks to replace the default image block settings with a wrapper that opens your own uploader from the existing image upload button.

import { useRef, useState, type ChangeEvent } from "react";
import { EmailEditor, type Email } from "@sendlit/email-blocks";
import { defaultEmail } from "@sendlit/email-editor";
import {
    ImageBlock,
    Link,
    Separator,
    Text,
    type UploaderProps,
} from "@sendlit/email-editor/blocks";

function LocalImageUploader({ children, onChange }: UploaderProps) {
    const inputRef = useRef<HTMLInputElement>(null);

    const handleFileChange = (event: ChangeEvent<HTMLInputElement>) => {
        const file = event.currentTarget.files?.[0];
        if (!file) {
            return;
        }

        const reader = new FileReader();
        reader.onload = () => {
            if (typeof reader.result !== "string") {
                return;
            }

            onChange({
                src: reader.result,
                alt: file.name,
            });
        };
        reader.readAsDataURL(file);
        event.currentTarget.value = "";
    };

    return (
        <span className="inline-flex" onClick={() => inputRef.current?.click()}>
            {children}
            <input
                ref={inputRef}
                type="file"
                accept="image/*"
                className="sr-only"
                tabIndex={-1}
                onClick={(event) => event.stopPropagation()}
                onChange={handleFileChange}
            />
        </span>
    );
}

const blocks = [
    Text,
    Separator,
    Link,
    ImageBlock.configure({ uploader: LocalImageUploader }),
];

function Example() {
    const [email, setEmail] = useState<Email>(defaultEmail);

    return <EmailEditor email={email} onChange={setEmail} blocks={blocks} />;
}

Pass the same Email value to EmailPreview or renderEmailToHtml (also re-exported) to see the rendered result or generate the HTML you actually send.

Generic add-on blocks

@sendlit/email-editor does not know about SendLit template purposes or compliance. Host applications can register add-on blocks with:

  • defaultSettings
  • insertable, deletable, duplicable, and movable capabilities
  • placement: "any" | "first" | "last"
  • an optional renderContext supplied by the host

Mutation handlers enforce these capabilities as well as hiding unavailable controls. The render context is passed to preview and HTML rendering but is never serialized into the Email document.

SendLit itself uses those generic hooks for the optional @sendlit/email-blocks/footer add-on. Marketing hosts register it as a locked final block and provide the real address and unsubscribe URL:

import { EmailEditor } from "@sendlit/email-editor";
import { createFooterBlock } from "@sendlit/email-blocks/footer";

const footer = createFooterBlock({ labels });

<EmailEditor
    email={email}
    onChange={setEmail}
    blocks={[Text, Separator, Link, ImageBlock, footer]}
    renderContext={{
        footer: {
            mailingAddress: "123 Main Street",
            unsubscribeUrl: "#unsubscribe-preview",
        },
    }}
/>;

The independently hosted editor remains generic: omit this add-on and context for ordinary or transactional designs.

On this page