Use Case

    Rich Text Email Composer

    Add a polished compose window with Scribe: inline formatting via editor.bold(), clean body output via getHTML(), signature editing with setHTML(), and paste-safe content from Word and Outlook — all under 50KB.

    getHTML()
    Clean email body
    setHTML()
    Load signatures
    XSS-safe
    Paste sanitized

    Everything an email composer needs

    Inline formatting for messages

    Bold, italic, links, lists, and blockquotes via the direct API — editor.bold(), editor.italic(), editor.link(url). Everything an email body needs without a heavyweight editor.

    Clean HTML via getHTML()

    Call editor.getHTML() to get clean, semantic HTML for the email body. No editor wrapper cruft, no proprietary classes — just the markup your mail transport needs.

    Signature editing

    Run a second Scribe instance for the saved signature. Load it with setHTML(), persist with getHTML(), and append it to the body before sending.

    Paste-safe & XSS-safe

    Users paste replies from Word, Outlook, and Google Docs. Scribe strips proprietary markup on paste and sanitizes HTML, so stored email bodies stay clean and safe.

    Compose Window with Clean HTML Output

    Wire inline formatting buttons to the direct API, then send the body via a single getHTML() call.

    import { Scribe } from 'scribejs-editor';
    import 'scribejs-editor/dist/scribe.css';
    
    // A compose window: a fixed toolbar reads well for email bodies
    const editor = Scribe.init('#email-body', {
      toolbar:     'fixed',
      autofocus:   true,
      placeholder: 'Write your message...',
    });
    
    // Apply inline formatting from your own toolbar buttons
    document.querySelector('#bold').addEventListener('click',   () => editor.bold());
    document.querySelector('#italic').addEventListener('click', () => editor.italic());
    document.querySelector('#link').addEventListener('click',   () => {
      const url = prompt('Link URL:');
      if (url) editor.link(url);
    });
    
    // Send: grab clean HTML for the email body
    document.querySelector('#send-btn').addEventListener('click', async () => {
      const bodyHtml = editor.getHTML();   // sanitized, clean semantic HTML
      await fetch('/api/email/send', {
        method:  'POST',
        headers: { 'Content-Type': 'application/json' },
        body:    JSON.stringify({
          to:      document.querySelector('#to').value,
          subject: document.querySelector('#subject').value,
          html:    bodyHtml,
        }),
      });
    });

    Signature Editing

    Run a second Scribe instance for the saved signature. Load it with setHTML() and persist with getHTML().

    import { Scribe } from 'scribejs-editor';
    
    // Separate editor instance for the user's saved signature
    const signature = Scribe.init('#signature-editor', {
      toolbar:     'floating',
      placeholder: 'Add your email signature...',
    });
    
    // Load an existing signature from the server
    const saved = await fetch('/api/settings/signature').then(r => r.json());
    if (saved.html) signature.setHTML(saved.html);
    
    // Persist signature HTML on save
    document.querySelector('#save-sig').addEventListener('click', async () => {
      const html = signature.getHTML();
      await fetch('/api/settings/signature', {
        method:  'PUT',
        headers: { 'Content-Type': 'application/json' },
        body:    JSON.stringify({ html }),
      });
    });
    
    // When composing, append the signature to the body HTML before sending
    function composeWithSignature(bodyHtml, signatureHtml) {
      return `${bodyHtml}<br><br>--<br>${signatureHtml}`;
    }

    React Email Composer Component

    A complete React component that grabs sanitized HTML on send and appends the signature.

    import { useRef, useEffect, useState } from 'react';
    import { Scribe } from 'scribejs-editor';
    
    export function EmailComposer({ onSend, signatureHtml }) {
      const containerRef = useRef(null);
      const editorRef    = useRef(null);
      const [sending, setSending] = useState(false);
    
      useEffect(() => {
        editorRef.current = Scribe.init(containerRef.current, {
          toolbar:     'fixed',
          autofocus:   true,
          placeholder: 'Write your message...',
        });
        return () => editorRef.current?.destroy();
      }, []);
    
      const send = async (to, subject) => {
        const editor = editorRef.current;
        if (!editor || editor.isEmpty()) return;
        setSending(true);
        // getHTML() returns sanitized HTML, safe to store and resend
        const body = editor.getHTML();
        const html = signatureHtml
          ? `${body}<br><br>--<br>${signatureHtml}`
          : body;
        try {
          await onSend({ to, subject, html });
          editor.setHTML('');   // clear the compose window after sending
        } finally {
          setSending(false);
        }
      };
    
      return (
        <div className="email-composer">
          <div ref={containerRef} className="prose max-w-none" />
          <button disabled={sending} onClick={() => send(/* to, subject */)}>
            {sending ? 'Sending...' : 'Send'}
          </button>
        </div>
      );
    }

    Everything an email writer expects

    Inline bold, italic, underline, strikethrough
    Links with inline URL editing via link(url)
    Ordered & unordered lists
    Blockquote for quoting replies
    Clean email body HTML via getHTML()
    setHTML() to load drafts & signatures
    Second instance for signature editing
    Floating or fixed toolbar modes
    Paste-safe from Word, Outlook, Google Docs
    Built-in XSS sanitization
    Zero dependencies, under 50KB
    React, Vue 3, Svelte & Vanilla JS

    Ship your email composer today.

    Inline formatting, clean HTML bodies, signature editing, safe paste. Everything in 50KB — free under MIT.

    Email composer — common questions

    How do I build a rich text email composer in JavaScript?

    Initialize Scribe Editor with toolbar: 'fixed' on your compose window, expose inline formatting with the direct API (editor.bold(), editor.italic(), editor.link(url)), and on send call editor.getHTML() to get clean HTML for the email body. Scribe is under 50KB gzipped with zero dependencies and works with React, Vue 3, Svelte, and Vanilla JS.

    How do I get clean HTML for an email body from Scribe?

    Call editor.getHTML(). It returns sanitized, semantic HTML without editor wrapper markup or proprietary class names. Send that string as the html field to your mail transport. Because Scribe sanitizes on input and paste, the HTML you store and resend stays clean.

    Can I edit an email signature with Scribe Editor?

    Yes. Run a second Scribe instance bound to a signature field. Load the saved signature with editor.setHTML(html) and persist edits with editor.getHTML(). When composing, append the signature HTML to the body before sending. A floating toolbar works well for the smaller signature surface.

    Does Scribe handle pasting replies from Word or Outlook safely?

    Yes. Scribe has built-in paste sanitization that strips proprietary Word, Outlook, and Google Docs markup and converts content to clean semantic HTML. Combined with built-in XSS sanitization, this keeps pasted reply content safe and consistent in your stored email bodies.