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

email-builder-standalone

v4.1.2

Published

A React email builder component with drag and drop functionality

Readme

email-builder-online

Powerful, modern email builder with drag-and-drop blocks, live preview, and HTML export. Built with React and Material UI, distributed as a React component and as a Web Component so it can be embedded in other frameworks (Nuxt 3, Next.js, SvelteKit, etc.). Compatible with React 18 and 19.

🚀 Live Demo

Features

  • Drag-and-drop blocks: Text (Rich Text Editor), Image, Button, Columns, Divider, Spacer, Social Media, Container
  • Editor and Preview tabs with responsive screen sizes (Desktop/Mobile)
  • Undo/Redo and keyboard shortcuts
  • HTML export and copy-to-clipboard helpers
  • CSS size guard for email client compatibility
  • Dark mode support
  • AI features for text (rewrite, grammar, continue, tone) and image generation
  • Image gallery integration with custom image provider support
  • Internationalization (i18n) with English, Spanish, and Italian support
  • Works as React component or Web Component - embed in any framework (Nuxt 3, Next.js, SvelteKit, Vue, etc.)
  • Built with TypeScript for better developer experience
  • Responsive design that works on mobile and desktop

Installation

Install the package along with React:

npm i email-builder-online react react-dom
# or
yarn add email-builder-online react react-dom
# or
pnpm add email-builder-online react react-dom

Note: All other dependencies (Material UI, i18n, drag-and-drop, Tiptap, etc.) are bundled with the package — no extra installs needed.

Requirements: This package targets Node 24+ and pnpm (declared in engines). Node 24 + pnpm are required when building or self-hosting the builder from source.

Peer dependencies (React / React-DOM)

react and react-dom must be the same major/minor version (e.g. both 18.x or both 19.x). Mixing versions (e.g. react@18 with react-dom@19) can cause runtime errors such as "Cannot read properties of undefined (reading 'S')".

If your project uses Vite, add this to your vite.config to avoid multiple instances of React (recommended on Windows and in monorepos):

export default defineConfig({
  resolve: {
    dedupe: ['react', 'react-dom', 'react-dom/client'],
  },
  // ...
});

Add the stylesheet (required):

// React / Vite / Nuxt / Next
import 'email-builder-online/style.css';

AI Features

The builder supports AI-powered features for both text and image blocks, controlled by a single enableAI prop.

Text AI

When enableAI is true, the rich text editor (NotionText) shows AI actions in the bubble menu toolbar and slash menu:

  • Rewrite, grammar check, continue writing
  • Tone adjustments: shorter, descriptive, detailed, friendly, professional

Text AI uses the onAIRequest callback to process requests. You provide the backend integration:

<EmailBuilder
  enableAI={true}
  onAIRequest={async ({ text, content, action, blockId }) => {
    const response = await fetch('/api/ai', {
      method: 'POST',
      body: JSON.stringify({ text, content, action }),
      headers: { 'Content-Type': 'application/json' },
    });
    const data = await response.json();
    return data.processedContent;
  }}
/>

Image AI Generation

When enableAI is true, image blocks and background image inputs show a "Generate with AI" button that opens a dialog for prompt-based image generation.

Events

The image AI generation uses a custom event system:

request-ai-image - Fired when the user submits a prompt to generate an image

window.addEventListener('request-ai-image', (event: CustomEvent) => {
  const prompt = event.detail; // string: the user's prompt

  // Call your AI image generation API
  const imageUrl = await generateImage(prompt);

  // Respond with the generated image
  window.dispatchEvent(
    new CustomEvent('generated-image', {
      detail: { url: imageUrl, success: true },
    })
  );
});

store-ai-image - Fired when the user confirms and inserts the generated image

window.addEventListener('store-ai-image', (event: CustomEvent) => {
  const imageUrl = event.detail; // string: URL of the image to store
  // Persist the image to your storage if needed
});

generated-image - Dispatch this event to return the generated image to the builder

// Success
window.dispatchEvent(
  new CustomEvent('generated-image', {
    detail: { url: 'https://example.com/generated.png', success: true },
  })
);

// Error
window.dispatchEvent(
  new CustomEvent('generated-image', {
    detail: { url: null, success: false, error: { code: 500, message: 'Generation failed' } },
  })
);

Template AI Generation (demo)

The dev playground wires onAIGenerateTemplate to the bundled @eb/backend workspace package so you can try the end-to-end flow (prompt → SSE NDJSON stream → live preview → Apply).

Run both packages in parallel from the repo root (two terminals):

# Terminal 1 — AI backend on http://localhost:3100
cp packages/backend/.env.example packages/backend/.env
# edit OPENAI_API_KEY inside packages/backend/.env
pnpm dev:backend

# Terminal 2 — React 19 playground on http://localhost:2501
pnpm dev

The consumer code in playground/react-19/src/App.tsx is the minimal reference: a single fetch to http://localhost:3100/api/generate that returns the SSE response body as a ReadableStream<string> to the builder's dialog. The backend URL is hardcoded there — change the AI_BACKEND_URL constant if your backend runs elsewhere.

Custom Image Provider

You can integrate your own image selector/gallery by passing a React component through the customImageProvider prop. This component will be rendered at the top of the image input panel, allowing users to select images from your custom source.

Events

The custom image provider can listen to and dispatch the following events:

Listening to Events

email-builder-image-panel-opened - Fired when an Image block is selected/opened in the editor

window.addEventListener('email-builder-image-panel-opened', (event: CustomEvent) => {
  const { blockId, currentImageUrl, alt } = event.detail;
  // You can use this to highlight the current image in your gallery
  // or load additional data based on the current selection
});

Event detail properties:

  • blockId (string): The ID of the selected image block
  • currentImageUrl (string | null): The URL of the currently selected image, or null if no image is set
  • alt (string | null): The alt text of the current image, or null if not set

Dispatching Events

email-builder-set-image - Dispatch this event to set an image in the currently selected block

window.dispatchEvent(
  new CustomEvent('email-builder-set-image', {
    detail: imageUrl, // string: URL of the image to set
  })
);

Complete Example

import React, { useEffect, useState } from 'react';
import { EmailBuilder } from 'email-builder-online';

function MyImageGallery() {
  const [currentImageUrl, setCurrentImageUrl] = useState<string | null>(null);

  useEffect(() => {
    // Listen for when the image panel opens
    const handlePanelOpened = (event: CustomEvent) => {
      const { currentImageUrl } = event.detail;
      setCurrentImageUrl(currentImageUrl);
    };

    window.addEventListener('email-builder-image-panel-opened', handlePanelOpened);

    return () => {
      window.removeEventListener('email-builder-image-panel-opened', handlePanelOpened);
    };
  }, []);

  const handleImageSelect = (imageUrl: string) => {
    // Dispatch event to set the image
    window.dispatchEvent(
      new CustomEvent('email-builder-set-image', {
        detail: imageUrl,
      })
    );
  };

  return (
    <div>
      <h4>My Custom Gallery</h4>
      {currentImageUrl && <p style={{ fontSize: '12px', color: '#666' }}>Current: {currentImageUrl}</p>}
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '8px' }}>
        <img
          src="https://example.com/image1.jpg"
          onClick={() => handleImageSelect('https://example.com/image1.jpg')}
          style={{
            cursor: 'pointer',
            width: '100%',
            border: currentImageUrl === 'https://example.com/image1.jpg' ? '2px solid blue' : 'none',
          }}
        />
        {/* More images... */}
      </div>
    </div>
  );
}

export default function Page() {
  return (
    <EmailBuilder
      customImageProvider={<MyImageGallery />}
      // ... other props
    />
  );
}

Live Example: Check out the working implementation in packages/email-builder-online/src/components/SampleImageGallery.tsx and see it in action in the demo app.

Custom Merge Tags

You can provide your own merge tags that will appear in the text editor's merge tag menu. Pass an array of tags or a complete group configuration:

Simple Array of Tags

import { EmailBuilder, MergeTag } from 'email-builder-online';

const myMergeTags: MergeTag[] = [
  {
    label: 'First Name',
    value: '[first_name]',
  },
  {
    label: 'Last Name',
    value: '[last_name]',
  },
  {
    label: 'Company',
    value: '[company]',
  },
  {
    type: 'divider', // Add a divider
  },
  {
    label: 'Custom Link',
    value: '{custom_link}Click here{/custom_link}',
  },
];

export default function Page() {
  return (
    <EmailBuilder
      mergeTags={myMergeTags}
      // ... other props
    />
  );
}

Complete Group with Custom Icons

import { EmailBuilder, MergeTagGroup } from 'email-builder-online';
import { User, Building, Mail } from 'lucide-react'; // or any icon library

const myMergeTagGroup: MergeTagGroup = {
  label: 'My Custom Tags',
  icon: <Mail />,
  children: [
    {
      label: 'First Name',
      value: '[first_name]',
      icon: <User />,
    },
    {
      label: 'Company Name',
      value: '[company]',
      icon: <Building />,
    },
    {
      type: 'divider',
    },
    {
      label: 'Verification Link',
      value: '{verify}Verify Account{/verify}',
      icon: <Mail />,
    },
  ],
};

export default function Page() {
  return (
    <EmailBuilder
      mergeTags={myMergeTagGroup}
      // ... other props
    />
  );
}

Programmatic Image Loading

You can load images programmatically using the ref:

import { useRef } from 'react';
import { EmailBuilder, EmailBuilderRef } from 'email-builder-online';

export default function Page() {
  const emailBuilderRef = useRef<EmailBuilderRef>(null);

  const handleSelectFromMyGallery = (imageUrl: string, blockId: string) => {
    // Set image URL for a specific block
    emailBuilderRef.current?.setImageUrl(blockId, imageUrl);
  };

  return (
    <EmailBuilder
      ref={emailBuilderRef}
      customImageProvider={<MyCustomGallery onSelect={handleSelectFromMyGallery} />}
    />
  );
}

Using with Custom Image Provider Events

You can combine the ref approach with the event system for a more robust solution:

import { useRef, useEffect, useState } from 'react';
import { EmailBuilder, EmailBuilderRef } from 'email-builder-online';

function MyCustomGallery() {
  const [currentBlockId, setCurrentBlockId] = useState<string | null>(null);
  const [currentImageUrl, setCurrentImageUrl] = useState<string | null>(null);

  useEffect(() => {
    const handlePanelOpened = (event: CustomEvent) => {
      const { blockId, currentImageUrl } = event.detail;
      setCurrentBlockId(blockId);
      setCurrentImageUrl(currentImageUrl);
    };

    window.addEventListener('email-builder-image-panel-opened', handlePanelOpened);
    return () => window.removeEventListener('email-builder-image-panel-opened', handlePanelOpened);
  }, []);

  const handleImageSelect = (imageUrl: string) => {
    // Use the event system to set the image
    window.dispatchEvent(
      new CustomEvent('email-builder-set-image', {
        detail: imageUrl,
      })
    );
  };

  return (
    <div>
      <h4>Select Image</h4>
      {currentImageUrl && <p>Current: {currentImageUrl}</p>}
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '8px' }}>
        {/* Your image gallery */}
      </div>
    </div>
  );
}

export default function Page() {
  const emailBuilderRef = useRef<EmailBuilderRef>(null);

  // Alternative: Use ref method directly
  const handleSelectFromGallery = (imageUrl: string, blockId: string) => {
    emailBuilderRef.current?.setImageUrl(blockId, imageUrl);
  };

  return <EmailBuilder ref={emailBuilderRef} customImageProvider={<MyCustomGallery />} />;
}

Available Blocks

The email builder includes the following drag-and-drop blocks:

  • Notion Text - Rich text editor with Notion-like editing experience (Tiptap-based)
  • Image - Image block with link support
  • Button - Call-to-action button with customizable styling
  • Columns - Multi-column layout container
  • Divider - Horizontal divider/separator line
  • Spacer - Vertical spacing block
  • Social Media - Social media icons with links
  • Container - Container block for grouping content

Backward Compatibility

The email builder automatically migrates legacy blocks when loading a document so templates created before the NotionText block was introduced still hydrate cleanly. The following retired block types are converted to NotionText on load:

  • CustomEditorNotionText (type rename, compatible data)
  • HtmlNotionText (props.contents wrapped into props.html)
  • HeadingNotionText (props.text wrapped into <hN>…</hN> in props.html)

Other previously-supported types (Wysiwyg, Text, Avatar) were fully retired and are no longer migrated — old documents containing them will silently drop those blocks.

Usage

There are three ways to use the builder:

1) As a React component with Ref (recommended for programmatic control)

Use a ref to control saving and access the document programmatically:

import React, { useRef, useState, useEffect } from 'react';
import { EmailBuilder, EmailBuilderRef, TEditorConfiguration, MergeTag } from 'email-builder-online';
import 'email-builder-online/style.css';

// Custom Image Gallery Component
function MyImageGallery() {
  const images = ['https://example.com/image1.jpg', 'https://example.com/image2.jpg', 'https://example.com/image3.jpg'];

  const handleImageSelect = (imageUrl: string) => {
    window.dispatchEvent(
      new CustomEvent('email-builder-set-image', {
        detail: imageUrl,
      })
    );
  };

  return (
    <div>
      <h4>My Custom Gallery</h4>
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '8px' }}>
        {images.map((img, idx) => (
          <img
            key={idx}
            src={img}
            onClick={() => handleImageSelect(img)}
            style={{ cursor: 'pointer', width: '100%', borderRadius: '4px' }}
          />
        ))}
      </div>
    </div>
  );
}

// Custom Merge Tags
const customMergeTags: MergeTag[] = [
  {
    label: 'First Name',
    value: '[first_name]',
  },
  {
    label: 'Last Name',
    value: '[last_name]',
  },
  {
    label: 'Company',
    value: '[company]',
  },
  {
    type: 'divider',
  },
  {
    label: 'Verification Link',
    value: '{verify}Verify your account{/verify}',
  },
];

export default function Page() {
  const emailBuilderRef = useRef<EmailBuilderRef>(null);
  const [document, setDocument] = useState<TEditorConfiguration | null>(null);

  // Load saved document on mount
  useEffect(() => {
    const saved = localStorage.getItem('draft');
    if (saved) {
      setDocument(JSON.parse(saved));
    }
  }, []);

  const handleSave = () => {
    if (emailBuilderRef.current) {
      const document = emailBuilderRef.current.save();
      // Send to your backend
      fetch('/api/save', {
        method: 'POST',
        body: JSON.stringify(document),
        headers: { 'Content-Type': 'application/json' },
      });
    }
  };

  const handleExportHtml = () => {
    if (emailBuilderRef.current) {
      const html = emailBuilderRef.current.getHtml();
      // Download HTML file or send to backend
      const blob = new Blob([html], { type: 'text/html' });
      const url = URL.createObjectURL(blob);
      const a = document.createElement('a');
      a.href = url;
      a.download = 'email.html';
      a.click();
      URL.revokeObjectURL(url);
    }
  };

  const handleSendEmail = async () => {
    if (emailBuilderRef.current) {
      const html = emailBuilderRef.current.getHtml();
      // Send email via your backend
      await fetch('/api/send-email', {
        method: 'POST',
        body: JSON.stringify({ html }),
        headers: { 'Content-Type': 'application/json' },
      });
    }
  };

  return (
    <div style={{ height: '100vh' }}>
      <button onClick={handleSave}>Save</button>
      <button onClick={handleExportHtml}>Export HTML</button>
      <button onClick={handleSendEmail}>Send Email</button>
      <EmailBuilder
        ref={emailBuilderRef}
        data={document}
        onAutoSave={(doc) => localStorage.setItem('draft', JSON.stringify(doc))}
        customImageProvider={<MyImageGallery />}
        mergeTags={customMergeTags}
        primaryColor="#0d9488"
        secondaryColor="#0ea5a6"
        locale="en"
        height="calc(100vh - 80px)"
      />
    </div>
  );
}

Ref Methods:

The EmailBuilderRef exposes the following methods:

  • getDocument(): TEditorConfiguration - Returns the current editor document
  • setDocument(document: TEditorConfiguration): void - Replaces the current document
  • save(): TEditorConfiguration - Flushes pending changes, calls onSave (if provided) and returns the document
  • getHtml(): string - Returns the rendered HTML for the current document
  • setImageUrl(blockId: string, url: string): void - Sets the URL of an image block with the given ID and updates the document

2) As a React component (basic usage)

import React from 'react';
import { EmailBuilder } from 'email-builder-online';
import 'email-builder-online/style.css';

export default function Page() {
  return (
    <div style={{ height: '100vh' }}>
      <EmailBuilder
        primaryColor="#0d9488"
        secondaryColor="#0ea5a6"
        darkMode={false}
        stickyHeader
        locale="en"
        height="calc(100vh - 80px)"
      />
    </div>
  );
}

3) As a Web Component (works in Nuxt 3, Vue, Svelte, plain HTML)

For Web Component usage, the component auto-registers in browser environments. Here's how to use it in different frameworks:

Nuxt 3 Example

  1. Register the email builder component:
// plugins/email-builder.client.ts
import { registerEmailBuilder } from 'email-builder-online';
import 'email-builder-online/style.css';

export default defineNuxtPlugin(() => {
  // Register the web component
  registerEmailBuilder('email-builder');
});

Vanilla JavaScript Example

For plain HTML (no bundler, no React in the host page), use the standalone build. It bundles React + ReactDOM, renders inside a Shadow DOM, and auto-registers the <email-builder> element on load (no manual registerEmailBuilder call needed).

The standalone files (dist/standalone.js, dist/standalone.mjs) are produced by pnpm build:standalone (or pnpm build:all). The regular dist/index.* build externalizes React and is meant for bundler-based apps, not plain <script> usage.

<!DOCTYPE html>
<html>
  <head>
    <link rel="stylesheet" href="path/to/email-builder-online/dist/style.css" />
    <!-- IIFE build (no module support needed); exposes window.EmailBuilder -->
    <script src="path/to/email-builder-online/dist/standalone.js"></script>
  </head>
  <body>
    <!-- Auto-registered on load -->
    <email-builder></email-builder>
  </body>
</html>

Or with the ES module variant:

<link rel="stylesheet" href="path/to/email-builder-online/dist/style.css" />
<script type="module" src="path/to/email-builder-online/dist/standalone.mjs"></script>
<email-builder></email-builder>

Then use it anywhere in your templates (wrap in <ClientOnly> for Nuxt):

<template>
  <ClientOnly>
    <email-builder
      primary-color="#0d9488"
      secondary-color="#0ea5a6"
      dark-mode="false"
      sticky-header="true"
      locale="en"
      height="calc(100vh - 80px)"
    />
  </ClientOnly>
</template>

For Vue/Nuxt, you can silence unknown element warnings by marking the tag as a custom element:

// nuxt.config.ts
export default defineNuxtConfig({
  vue: {
    compilerOptions: {
      isCustomElement: (tag) => tag === 'email-builder',
    },
  },
  css: ['email-builder-online/style.css'],
});

Notes on SSR and dependencies:

  • The Web Component wrapper is registered only on the client. Use a client-only plugin in SSR frameworks.
  • React and ReactDOM are peer dependencies for the package-based usage (React component and the registerEmailBuilder Web Component). Install them in your app or use a bundler that provides them. React 18 and 19 are supported. (The self-contained standalone build bundles its own React.)
  • i18n dependencies (i18next, react-i18next, i18next-browser-languagedetector) are bundled with the package — you do not need to install them separately.

Props / Attributes

React Props (camelCase) and Web Component attributes (dash-case) map 1:1:

| React Prop | Web Component Attribute | Type | Default | Description | | ------------------- | ----------------------- | ---------------------------------------- | ------- | ---------------------------------------------------------------------------------------- | | ref | - | React.Ref | - | Ref to access component methods (getDocument, setDocument, save, getHtml, setImageUrl) | | onSave | - | (document: TEditorConfiguration) => void | - | Callback when ref.save() is called | | onAutoSave | - | (document: TEditorConfiguration) => void | - | Callback for auto-save (2s after changes) | | data | data | TEditorConfiguration | string | - | Document to load (reactive - updates when changed) | | initialDocument | - | TEditorConfiguration | string | - | Initial document to load on mount (one-time only) | | customImageProvider | - | React.ReactNode | - | Custom image selector component to integrate your own image gallery | | mergeTags | - | MergeTag[] | MergeTagGroup | - | Custom merge tags to show in the text editor | | primaryColor | primary-color | string | #058705 | Primary theme color | | secondaryColor | secondary-color | string | #079707 | Secondary theme color | | darkMode | dark-mode | boolean | false | Enable dark mode | | height | height | string | - | Container height (e.g. "calc(100vh - 80px)") | | stickyHeader | sticky-header | boolean | true | Sticky header behavior | | sticky | sticky | boolean | false | Sticky content behavior | | galleryImages | gallery-images | boolean | false | Enable image gallery | | locale | locale | string | - | UI language (en, es, it, en-US, es-419, it-IT). Falls back to dataLocale if not provided | | dataLocale | data-locale | string | - | Alternative locale prop (used as fallback if locale is not provided) | | htmlTab | html-tab | boolean | true | Show HTML tab | | jsonTab | json-tab | boolean | true | Show JSON tab | | imagePlaceholder | image-placeholder | string | - | Default placeholder for images | | imageUrlInput | image-url-input | boolean | true | Show the URL field in the Image block picker's Upload tab. When false, only the dropzone is rendered (subject to imageUploadInput). When both imageUrlInput and imageUploadInput are false, the Upload tab is hidden. | | imageUploadInput | image-upload-input | boolean | true | Show the drag & drop / file upload zone in the Image block picker's Upload tab. When false, only the URL field is rendered (subject to imageUrlInput). When both imageUrlInput and imageUploadInput are false, the Upload tab is hidden. | | backgroundUrlInput | background-url-input | boolean | true | Show the URL field in the Background image picker's Upload tab (Container, ColumnsContainer, EmailLayout). Same hide-on-both-false behavior as imageUrlInput. | | backgroundUploadInput | background-upload-input | boolean | true | Show the drag & drop / file upload zone in the Background image picker's Upload tab. Same hide-on-both-false behavior as imageUploadInput. | | enableAI | enable-ai | boolean | false | Enable AI features for text and image generation | | onAIRequest | - | (request: AIFeatureRequest) => Promise<string> | - | Callback for AI text processing requests | | onAIGenerateTemplate | - | (request: AIGenerateTemplateRequest, options: { signal: AbortSignal }) => Promise<AIGenerateTemplateResponse> | - | Callback for AI template generation (prompt → template). When undefined, the "Generate with AI" entry point is hidden | | unsplashEnabled | - | boolean | false | Show the built-in Unsplash picker tab (requires the backend proxy) | | unsplashBackendUrl | - | string | - | Override the backend URL used for Unsplash proxy calls | | portalContainer | - | HTMLElement | - | Container element for MUI portals (menus, dialogs); useful when mounting inside Shadow DOM | | showVersion | show-version | boolean | - | Show version indicator in the editor | | componentTree | component-tree | boolean | true | Show the component tree panel |

Internationalization (i18n)

The builder supports multiple languages. Pass the locale prop with one of the supported values:

  • en or en-US - English (default)
  • es or es-419 - Spanish
  • it or it-IT - Italian
<EmailBuilder locale="es" />

TypeScript

Types are shipped. You can import them as:

import type { EmailBuilderProps, AIFeatureRequest, MergeTag, MergeTagGroup, EmailBuilderRef } from 'email-builder-online';

The AIFeatureRequest interface:

interface AIFeatureRequest {
  text: string;      // Selected or relevant text
  content: string;   // Full block content
  action: string;    // AI action (rewrite, grammar_check, continue_writing, shorter, descriptive, detailed, friendly, professional)
  blockId?: string;  // Block ID where the request originated
}

License

Free to use. © Laravel42. All rights reserved.

Links

Changelog

See Git history for details. Please open issues or PRs for bugs and improvements.