# Easy Email Easy Email is the most developer-friendly email editor built on top of MJML (Mailjet Markup Language). It provides a drag-and-drop interface for creating responsive email templates that render consistently across email clients. The editor transforms visual compositions into MJML markup, which can then be compiled to HTML for sending. The library is organized as a monorepo with three core packages: `easy-email-core` for block definitions and JSON-to-MJML conversion, `easy-email-editor` for the visual editing experience with React components, and `easy-email-extensions` for UI layouts and additional functionality. Built with React 18 and TypeScript, it leverages react-final-form for state management and provides extensive customization through custom blocks, merge tags, and theming support. ## Installation ```bash npm install --save easy-email-core easy-email-editor easy-email-extensions react-final-form ``` ## Basic Email Editor Setup The EmailEditorProvider is the root component that wraps the entire editor and manages form state, while EmailEditor renders the visual editing canvas. ```tsx import React from 'react'; import { BlockManager, BasicType, AdvancedType } from 'easy-email-core'; import { EmailEditor, EmailEditorProvider } from 'easy-email-editor'; import { StandardLayout } from 'easy-email-extensions'; import 'easy-email-editor/lib/style.css'; import 'easy-email-extensions/lib/style.css'; import '@arco-themes/react-easy-email-theme/css/arco.css'; const initialValues = { subject: 'Welcome to Easy-email', subTitle: 'Nice to meet you!', content: BlockManager.getBlockByType(BasicType.PAGE).create({}), }; export default function App() { const onSubmit = (values) => { console.log('Email template:', values); }; return ( {({ values }, { submit }) => ( )} ); } ``` ## EmailEditorProvider Configuration The EmailEditorProvider accepts various props to customize the editor behavior including image upload handlers, merge tags for dynamic content, and interactive styling. ```tsx import React from 'react'; import { EmailEditor, EmailEditorProvider, IEmailTemplate } from 'easy-email-editor'; import { StandardLayout } from 'easy-email-extensions'; import { BlockManager, BasicType } from 'easy-email-core'; const App = () => { const initialValues: IEmailTemplate = { subject: 'Newsletter', subTitle: 'Monthly update', content: BlockManager.getBlockByType(BasicType.PAGE).create({}), }; const onUploadImage = async (blob: Blob): Promise => { const formData = new FormData(); formData.append('file', blob); const response = await fetch('/api/upload', { method: 'POST', body: formData }); const { url } = await response.json(); return url; }; return ( `{{${tag}}}`} interactiveStyle={{ hoverColor: '#3b82f6', selectedColor: '#1d4ed8', }} onSubmit={(values) => console.log(values)} > {({ values }, { submit }) => ( )} ); }; ``` ## JsonToMjml - Converting Block Data to MJML The JsonToMjml function converts the editor's JSON block data structure into valid MJML markup that can be compiled to HTML using mjml-browser. ```tsx import { JsonToMjml, BasicType, BlockManager } from 'easy-email-core'; import mjml from 'mjml-browser'; // Create a simple email template const pageBlock = BlockManager.getBlockByType(BasicType.PAGE).create({ children: [ BlockManager.getBlockByType(BasicType.SECTION).create({ children: [ BlockManager.getBlockByType(BasicType.COLUMN).create({ children: [ BlockManager.getBlockByType(BasicType.TEXT).create({ data: { value: { content: 'Hello World!' } }, }), BlockManager.getBlockByType(BasicType.BUTTON).create({ data: { value: { content: 'Click Me' } }, attributes: { 'background-color': '#3b82f6', href: 'https://example.com' }, }), ], }), ], }), ], }); // Convert to MJML const mjmlString = JsonToMjml({ data: pageBlock, mode: 'production', context: pageBlock, beautify: true, }); console.log(mjmlString); // Output: ... // Convert MJML to HTML const { html, errors } = mjml(mjmlString, {}); console.log(html); // Output: ... ``` ## MjmlToJson - Importing MJML Templates The MjmlToJson function parses MJML markup and converts it back into the editor's JSON block data format, enabling import of existing templates. ```tsx import { MjmlToJson } from 'easy-email-extensions'; import { IPage } from 'easy-email-core'; const mjmlTemplate = ` Welcome to our newsletter! Get Started `; // Convert MJML to block data const blockData: IPage = MjmlToJson(mjmlTemplate); // Use in EmailEditorProvider const initialValues = { subject: 'Imported Template', subTitle: '', content: blockData, }; ``` ## BlockManager - Managing Block Types BlockManager is a singleton that registers and retrieves block definitions. It supports both basic MJML blocks and advanced blocks with enhanced editing capabilities. ```tsx import { BlockManager, BasicType, AdvancedType, IBlockData } from 'easy-email-core'; // Get a block definition by type const textBlock = BlockManager.getBlockByType(BasicType.TEXT); const buttonBlock = BlockManager.getBlockByType(AdvancedType.BUTTON); // Create block instances with custom attributes const customText = textBlock.create({ data: { value: { content: 'Custom styled text' }, }, attributes: { 'font-size': '18px', 'font-weight': 'bold', color: '#1f2937', padding: '20px 25px', 'line-height': '1.5', }, }); const customButton = buttonBlock.create({ data: { value: { content: 'Subscribe Now' }, }, attributes: { 'background-color': '#10b981', color: '#ffffff', 'border-radius': '8px', padding: '15px 30px', href: 'https://example.com/subscribe', target: '_blank', }, }); // Get all registered blocks const allBlocks = BlockManager.getBlocks(); console.log('Available blocks:', allBlocks.map(b => b.type)); // Output: ['page', 'section', 'column', 'text', 'image', 'button', ...] ``` ## useBlock Hook - Block Operations The useBlock hook provides methods for manipulating blocks in the editor including add, move, copy, and remove operations with undo/redo support. ```tsx import React from 'react'; import { useBlock, useFocusIdx } from 'easy-email-editor'; import { AdvancedType } from 'easy-email-core'; const BlockToolbar = () => { const { values, focusBlock, addBlock, copyBlock, removeBlock, moveBlock, undo, redo, undoable, redoable, } = useBlock(); const { focusIdx } = useFocusIdx(); const handleAddText = () => { addBlock({ type: AdvancedType.TEXT, parentIdx: focusIdx, positionIndex: 0, payload: { data: { value: { content: 'New text block' } }, }, }); }; const handleAddImage = () => { addBlock({ type: AdvancedType.IMAGE, parentIdx: focusIdx, payload: { attributes: { src: 'https://via.placeholder.com/600x300', alt: 'Placeholder image', padding: '10px 0', }, }, }); }; return (

Current block: {focusBlock?.type}

); }; ``` ## Creating Custom Blocks Custom blocks allow you to create reusable email components by composing basic blocks. Register custom blocks with BlockManager to use them in the editor. ```tsx import { IBlockData, BasicType, components, createCustomBlock, getPreviewClassName, BlockManager, } from 'easy-email-core'; import { merge } from 'lodash'; import React from 'react'; const { Column, Section, Text, Button, Image } = components; // Define custom block type const CUSTOM_BLOCK_TYPE = 'custom_product_card'; // Define block data interface interface IProductCard extends IBlockData< { 'background-color': string; 'button-color': string }, { title: string; price: string; imageUrl: string; buttonText: string } > {} // Create custom block const ProductCard = createCustomBlock({ name: 'Product Card', type: CUSTOM_BLOCK_TYPE, validParentType: [BasicType.PAGE, BasicType.WRAPPER], create: (payload) => { const defaultData: IProductCard = { type: CUSTOM_BLOCK_TYPE, data: { value: { title: 'Product Name', price: '$99.99', imageUrl: 'https://via.placeholder.com/300x200', buttonText: 'Buy Now', }, }, attributes: { 'background-color': '#ffffff', 'button-color': '#3b82f6', }, children: [], }; return merge(defaultData, payload); }, render: (data, idx, mode, context, dataSource) => { const { title, price, imageUrl, buttonText } = data.data.value; const attributes = data.attributes; const className = mode === 'testing' ? getPreviewClassName(idx, data.type) : ''; return (
{title} {title} {price}
); }, }); // Register the custom block BlockManager.registerBlocks({ [CUSTOM_BLOCK_TYPE]: ProductCard }); ``` ## StandardLayout and SimpleLayout The extension package provides two layout components: StandardLayout for a compact sidebar experience and SimpleLayout for a more spacious editing interface with collapsible panels. ```tsx import React from 'react'; import { EmailEditor, EmailEditorProvider } from 'easy-email-editor'; import { StandardLayout, SimpleLayout, ExtensionProps } from 'easy-email-extensions'; import { AdvancedType, BlockManager, BasicType } from 'easy-email-core'; // Custom block categories for the sidebar const categories: ExtensionProps['categories'] = [ { label: 'Content', active: true, blocks: [ { type: AdvancedType.TEXT }, { type: AdvancedType.IMAGE, payload: { attributes: { padding: '0px' } } }, { type: AdvancedType.BUTTON }, { type: AdvancedType.DIVIDER }, { type: AdvancedType.SPACER }, { type: AdvancedType.SOCIAL }, { type: AdvancedType.HERO }, ], }, { label: 'Layout', active: true, displayType: 'column', blocks: [ { title: '2 columns', payload: [['50%', '50%'], ['33%', '67%'], ['67%', '33%']] }, { title: '3 columns', payload: [['33.33%', '33.33%', '33.33%']] }, { title: '4 columns', payload: [['25%', '25%', '25%', '25%']] }, ], }, ]; const initialValues = { subject: 'Newsletter', subTitle: '', content: BlockManager.getBlockByType(BasicType.PAGE).create({}), }; // Using StandardLayout (compact mode) const StandardEditor = () => ( {({ values }) => ( )} ); // Using SimpleLayout (expanded mode with collapsible layer panel) const SimpleEditor = () => ( {({ values }) => ( )} ); ``` ## Exporting Email Templates Export email templates as MJML markup, compiled HTML, or JSON data for storage and delivery. ```tsx import { JsonToMjml, IPage } from 'easy-email-core'; import { IEmailTemplate } from 'easy-email-editor'; import mjml from 'mjml-browser'; import { saveAs } from 'file-saver'; const exportTemplate = (values: IEmailTemplate) => { // Export as MJML const mjmlString = JsonToMjml({ data: values.content, mode: 'production', context: values.content, beautify: true, }); saveAs(new Blob([mjmlString], { type: 'text/mjml' }), 'template.mjml'); // Export as HTML const { html, errors } = mjml(mjmlString, { validationLevel: 'soft', minify: false, }); if (errors.length > 0) { console.warn('MJML compilation warnings:', errors); } saveAs(new Blob([html], { type: 'text/html' }), 'template.html'); // Export as JSON (for re-importing into the editor) const jsonData = JSON.stringify({ subject: values.subject, subTitle: values.subTitle, content: values.content, }, null, 2); saveAs(new Blob([jsonData], { type: 'application/json' }), 'template.json'); return { mjml: mjmlString, html, json: jsonData }; }; // Usage with dynamic merge tags const exportWithMergeTags = (values: IEmailTemplate, mergeTags: Record) => { const mjmlString = JsonToMjml({ data: values.content, mode: 'production', context: values.content, dataSource: mergeTags, beautify: true, }); return mjml(mjmlString, {}).html; }; ``` ## Working with Merge Tags Merge tags enable dynamic content insertion in email templates. Configure merge tags in the provider and use them in text blocks. ```tsx import React from 'react'; import { EmailEditor, EmailEditorProvider } from 'easy-email-editor'; import { StandardLayout } from 'easy-email-extensions'; import { BlockManager, BasicType, JsonToMjml } from 'easy-email-core'; import { Liquid } from 'liquidjs'; const engine = new Liquid(); const App = () => { const mergeTags = { user: { firstName: 'John', lastName: 'Doe', email: 'john@example.com', }, company: { name: 'Acme Inc', website: 'https://acme.com', }, order: { id: '12345', total: '$99.99', items: [ { name: 'Product A', qty: 2 }, { name: 'Product B', qty: 1 }, ], }, }; const initialValues = { subject: 'Hello {{user.firstName}}!', subTitle: '', content: BlockManager.getBlockByType(BasicType.PAGE).create({ children: [ BlockManager.getBlockByType(BasicType.SECTION).create({ children: [ BlockManager.getBlockByType(BasicType.COLUMN).create({ children: [ BlockManager.getBlockByType(BasicType.TEXT).create({ data: { value: { content: 'Hello {{user.firstName}} {{user.lastName}}!' } }, }), ], }), ], }), ], }), }; // Preview with merge tag replacement using Liquid template engine const onBeforePreview = async (html: string, tags: Record) => { return engine.parseAndRenderSync(html, tags); }; return ( `{{${tag}}}`} enabledMergeTagsBadge={true} previewInjectData={mergeTags} onBeforePreview={onBeforePreview} > {({ values }) => ( )} ); }; ``` ## Summary Easy Email provides a comprehensive solution for building email template editors in React applications. The core package handles block definitions and MJML conversion, the editor package manages the visual editing experience with drag-and-drop functionality, and the extensions package offers ready-to-use layouts and UI components. The library supports both basic MJML blocks (text, image, button, section, column) and advanced blocks with enhanced editing capabilities. Integration patterns typically involve wrapping the EmailEditorProvider around your application, configuring block categories and merge tags, and using either StandardLayout or SimpleLayout for the UI. For production use, templates are exported via JsonToMjml and compiled to HTML using mjml-browser. Custom blocks extend the editor's capabilities by composing basic blocks into reusable components. The library integrates well with form libraries through react-final-form, supports undo/redo operations, and provides hooks like useBlock and useFocusIdx for programmatic block manipulation.