Skip to main content

Dynamic Custom Block Example

This example demonstrates how to create custom blocks that integrate with dynamic data, merge tags, and external APIs. The key concept is understanding when to use mergetagsData (for testing/preview) versus liquid syntax (for production with caching support).

Overview​

Dynamic custom blocks can:

  • Fetch data from APIs
  • Use merge tags for personalization
  • Display product information
  • Integrate with external services
  • Support caching for better performance

Important: Testing vs Production Mode​

The rendering process follows this flow:

  1. JSON → MJML: Convert Easy Email Pro JSON to MJML (can be cached)
  2. MJML → HTML: Compile MJML to HTML (can be cached)
  3. HTML + Dynamic Data → Final HTML: Inject dynamic data using template engine

Key Point: Dynamic data injection happens at the HTML stage, not during MJML generation. This allows caching of the MJML and HTML skeleton.

  • Testing Mode: Use mergetagsData to render actual data for preview in the editor
  • Production Mode: Use liquid syntax (e.g., {{item.title}}) or <ForEach> component, which will be processed by the template engine at the HTML stage

Example: Product Block with Dynamic Data​

import {
createCustomBlock,
t,
components,
mergeBlock,
ElementType,
} from "easy-email-pro-core";
import { ElementCategory } from "easy-email-pro-core";
import { get } from "lodash";
import React from "react";

const { ForEach } = components;

export interface ProductItem {
id: string;
title: string;
image: string;
price: string;
comparePrice: string;
}

export const DynamicCustomBlock = createCustomBlock<DynamicCustomElement>({
get name() {
return t("Dynamic Products");
},
type: CustomBlockType.DYNAMIC_CUSTOM_BLOCK,
defaultData: {
attributes: {
"background-color": "#ffffff",
"padding-top": "20px",
"padding-bottom": "20px",
},
data: {
productIds: [],
},
},
create: (payload) => {
// ... create block data
},
category: ElementCategory.WRAPPER,
render(params) {
const { node, mergetagsData, mode } = params;
const dataSourceKey = "products";

// In testing mode, use mergetagsData for preview
const products = get(mergetagsData, dataSourceKey, []) as Array<ProductItem>;

const RenderList = ({ item }: { item: ProductItem }) => {
return (
<mj-column width="50%">
<mj-image src={item.image} />
<mj-text>{item.title}</mj-text>
<mj-text>{item.price}</mj-text>
</mj-column>
);
};

return (
<mj-wrapper {...node.attributes}>
<mj-section>
<mj-group>
{mode === "testing" ? (
// Testing mode: Use mergetagsData for preview
products.map((item, index) => (
<RenderList item={item} key={index} />
))
) : (
// Production mode: Use liquid syntax for template engine
<ForEach dataSource={dataSourceKey} itemName="item">
<RenderList
item={{
id: "{{item.id}}",
title: "{{item.title}}",
image: "{{item.image}}",
price: "{{item.price}}",
comparePrice: "{{item.comparePrice}}",
}}
/>
</ForEach>
)}
</mj-group>
</mj-section>
</mj-wrapper>
);
},
});

Server-Side Rendering with Caching​

When rendering on the server, you can cache the MJML and HTML skeleton, then inject dynamic data at the HTML stage:

import { EditorCore, PluginManager } from "easy-email-pro-core";
import mjml from "mjml";

// Step 1: Convert JSON to MJML (can be cached)
const mjmlSkeleton = EditorCore.toMJML({
element: pageData,
mode: "production", // Use production mode to generate liquid syntax
});

// Step 2: Compile MJML to HTML (can be cached)
const htmlSkeleton = mjml(mjmlSkeleton).html;

// Step 3: Inject dynamic data at HTML stage
const dynamicData = await getDynamicData(pageData);
const finalHtml = PluginManager.renderWithData(htmlSkeleton, dynamicData);

Why this approach?

  • The MJML skeleton (with liquid syntax) can be cached and reused
  • The HTML skeleton can also be cached
  • Dynamic data is only injected at the final HTML stage
  • This significantly improves performance for high-volume email sending

Merge Tags Integration​

For editor preview, configure merge tags:

const mergetags = [
{
label: "Products",
value: "",
children: [
{
label: "Product Name",
value: "products.0.title",
},
{
label: "Product Price",
value: "products.0.price",
},
],
},
];

const mergetagsData = {
products: [
{
id: "1",
title: "Product 1",
price: "$10.00",
image: "https://example.com/product1.jpg",
},
// ... more products
],
};

const config = Retro.useCreateConfig({
mergetags,
mergetagsData, // Used for testing mode preview
// ... other config
});

Key Features​

  • API Integration: Fetch data from external sources
  • Merge Tags: Use dynamic variables in templates
  • Caching Support: MJML and HTML can be cached, data injected at HTML stage
  • Testing Mode: Preview with actual data using mergetagsData
  • Production Mode: Use liquid syntax for template engine processing

Use Cases​

  • Product showcases
  • Order confirmations
  • Personalized recommendations
  • Dynamic content blocks