
Best Email Builder Tools in 2025: Features & Pricing Compared
Compare the best email builder tools — Unlayer, Beefree, Stripo, Topol.io, Postcards, and Chamaileon — by features and pricing, and see why Unlayer leads.

Embedding Unlayer into your React application opens up powerful drag-and-drop editor capabilities. However, it’s not always smooth sailing. Sometimes, developers run into frustrating bugs, unexpected behaviors, or compatibility quirks that aren’t always easy to diagnose.
Over time, our technical support team has worked closely with real customers to identify recurring roadblocks developers face during the integration process.
From rendering hiccups to AMP not working properly, these challenges vary, but the good news is they’re all fixable.
In this blog, we’ll dive deep into troubleshooting issues in React, specifically related to Unlayer.
If you’re just getting started, our complete guide to embeddable React email builders has got you covered. And if you are already in the middle of integration and trying to resolve a stubborn issue, this guide will walk you through the most common problems and offer clear, actionable solutions that you can implement promptly.
Setting up Unlayer in a React project can get messy if everything lives inside App.js or if the editor logic isn’t modular. This not only clutters your main app file but also makes scaling and reusing the editor component harder.
Developers often face issues when trying to integrate features like AMP, custom tools, or dynamic config, especially when the initialization isn’t properly scoped or abstracted.
Here’s a step-by-step guide on how to initialize the Unlayer React builder properly in your app.
npm install react-email-editor --save or yarn add react-email-editorInside your src folder, create a file named:
EmailComponent.jsUse the official Unlayer React component. Full docs are available here: React Component.
import React, { useRef } from 'react';
import { render } from 'react-dom';
import EmailEditor, { EditorRef, EmailEditorProps } from 'react-email-editor';
const App = (props) => {
const emailEditorRef = useRef<EditorRef>(null);
const exportHtml = () => {
const unlayer = emailEditorRef.current?.editor;
unlayer?.exportHtml((data) => {
const { design, html } = data;
console.log('exportHtml', html);
});
};
const onReady: EmailEditorProps['onReady'] = (unlayer) => {
// editor is ready
// you can load your template here;
// the design json can be obtained by calling
// unlayer.loadDesign(callback) or unlayer.exportHtml(callback)
// const templateJson = { DESIGN JSON GOES HERE };
// unlayer.loadDesign(templateJson);
};
return (
<div>
<div>
<button onClick={exportHtml}>Export HTML</button>
</div>
<EmailEditor ref={emailEditorRef} onReady={onReady} />
</div>
);
};
render(<App />, document.getElementById('app'));<EmailEditor
ref={emailEditorRef}
onReady={onReady}
options=
{{
projectId: 123456, // Replace with your actual Unlayer project ID
displayMode: “email”, // replace with "web", or "document", or "popup"
version: 'latest',
}}
/>// App.js
import React from 'react';
import EmailEditorComponent from './EmailComponent';
function App() {
return (
<div>
<EmailEditorComponent />
</div>
);
}
export default App;You’ve added an AMP/Carousel block to your email while designing it with Unlayer’s editor, expecting it to deliver a smooth, dynamic user experience. However, nothing shows up in the preview or the exported HTML.
This is a common frustration among developers while they’re working with Unlayer’s editor in React. The issue? AMP features like Carousel often appear to be broken simply because AMP mode hasn’t been correctly enabled in the configuration.
Without the right setup, the editor silently skips AMP rendering, leaving you puzzled and wasting valuable debugging time.
To unlock AMP functionality (including Carousel), you need to explicitly enable it during initialization. In React apps, this is done via the options prop. Here’s how to do it.
If you’re using vanilla JS, set the amp: true flag in the unlayer.init() call:
unlayer.init({
id: "editor-container",
projectId: 123456, // replace with project ID with yours
displayMode: "email", // replace with "web", or "document", or "popup"
version: "latest",
amp: true,
});For React users, pass the same config through the options prop:
<EmailEditor
ref={emailEditorRef}
onReady={onReady}
options=
{{
projectId: 123456, // Replace with your actual Unlayer project ID
displayMode: “email”, // replace with "web", or "document", or "popup"
version: 'latest',
amp: true,
}}
/>After that, don’t forget to toggle the AMP preview button inside the Unlayer editor UI. This lets you see Carousel and other AMP-powered blocks in action. Here’s a screenshot for reference.

If you also want to export the AMP version of your email template (for sending or further customization), make sure you include the AMP flag in the exportHtml() function:
unlayer.exportHtml(function (data) {
var json = data.design;
var html = data.html;
var ampHTML = data.amp.html; // extract the HTML with AMP
console.log("Design JSON:", json);
console.log("Design AMP HTML:", ampHTML);
console.log("Design HTML:", html);
}, { amp: true }); // enables AMP in the HTML outputYou may have an existing HTML email or landing page template you’d like to load into Unlayer for editing, only to discover that Unlayer’s standard editor accepts only JSON design objects.
If you need to work with existing HTML templates, switch to Unlayer’s Classic editor mode:
The Classic Editor is a WYSIWYG interface that supports raw HTML import and editing.
Note: It comes with a trade-off — the advanced drag-and-drop features of the editor are disabled.
Read More: Legacy Templates
You’ve set everything up, but a block isn’t rendering properly, or new features you saw in the docs aren’t available in your editor. This often happens when you’re using the wrong editor version.
Unlayer supports multiple version modes, and using the wrong one can lead to unstable or outdated behavior, especially in production environments. But the good news is that you can easily manage versions that best suit your needs.
Read More: Version Management
Unlayer allows you to choose which version of the editor to load. Picking the right one is key to ensuring stability and access to features you actually intend to use.
Here are your options:
latest:
Great for development and testing. It gives you access to the newest updates and experimental features, but may not be production-ready.
stable:
Recommended for production use. It’s the most tested and reliable version, minimizing the risk of regressions or bugs.
Specific version (e.g., 1.248.0):
Perfect for locking in a known good state. This ensures your editor doesn’t change unexpectedly due to upstream updates.
Here’s how you can control the editor version in your config:
If you’re using vanilla JS, set the version flag in the unlayer.init() call:
unlayer.init({
id: "editor-container",
projectId: 123456, // replace with project ID with yours
displayMode: "email", // replace with "web", or "document", or "popup"
version: "latest",
});For React users, pass the same config through the options prop:
<EmailEditor
ref={emailEditorRef}
onReady={onReady}
options=
{{
projectId: 123456, // Replace with your actual Unlayer project ID
displayMode: “email”, // replace with "web", or "document", or "popup"
version: 'latest',
}}
/>Use latest during development to take advantage of the newest features and debug early. Then switch to stable or lock in a specific version before going live, so your editor's behavior doesn’t change unexpectedly.
You’ve upgraded to a paid Unlayer plan expecting premium features and no watermark, but in production, the watermark still appears, and certain features are mysteriously unavailable.
Everything works fine on localhost, so it's easy to assume the issue lies in the code. In reality, this is a domain authorization issue that many users overlook when moving from dev to production.
Unlayer automatically whitelists localhost for testing, which is why everything behaves correctly during local development. But for your production environment, Unlayer requires you to explicitly authorize your production domain to unlock the full benefits of your paid plan.
Open your app in the browser where the Unlayer editor is embedded.
Right-click > Inspect and go to the Network tab.
Refresh the page and look for the session network call.
Under the Payload, find the domain value.
This is the exact domain Unlayer expects to be whitelisted.
Go to the Unlayer Console →
Settings > Deployment > Allowed Domains.
Paste the domain into the “Allowed Domains” field and save.
Refresh your app again. The correct plan features should now be active, and the watermark removed.
You’ve written code for a custom tool for Unlayer, bundled it, and added it to your React app, but it’s not appearing in the editor.
This is a common issue that usually stems from one of two things: either the tool isn’t properly bundled and hosted, or it hasn’t been correctly registered via customJS. Without proper setup, Unlayer won’t load your custom code, and the tool silently fails to appear.
To get your custom tool working in a React environment, you’ll need to:
Bundle your tool code using a module bundler like Webpack or Vite.
Host the output JavaScript file somewhere publicly accessible (your server, CDN, etc.).
Register it via the customJS property inside the options prop of the <EmailEditor /> component.
Here’s a code for reference:
<EmailEditor
ref={emailEditorRef}
onReady={onReady}
options=
{{
projectId: 123456, // Replace with your actual Unlayer project ID
displayMode: “email”, // replace with "web", or "document", or "popup"
version: 'latest',
customJS: “https://yourdomain.com/custom-tool.js”,
}}
/>If your custom tool includes React components or JSX, here’s how to render them properly:
Use the Viewer to attach your custom React component.
For exporters, ensure that any JSX is converted to a string using ReactDOMServer.renderToStaticMarkup() for proper rendering in both web and email outputs.
Here’s the code example for the above:
unlayer.registerTool({
……..,
renderer: {
Viewer: MyColorPicker, // this is your React component
exporters: {
web: function (values) {
return ReactDOMServer.renderToStaticMarkup(
<MyColorPicker/>
);
},
email: function (values) {
return ReactDOMServer.renderToStaticMarkup(
<MyColorPicker/>
);
},
},
head: {
css: function(values) {},
js: function(values) {}
}
}
});✅ Tool is bundled using Webpack/Vite
✅ Hosted JS file is reachable via HTTPS
✅ Registered via customJS in options
✅ JSX properly rendered using ReactDOMServer.renderToStaticMarkup()
Your custom tool works perfectly in the editor UI, but when you try exporting to HTML, PDF, or image formats, it renders as ‘Missing.’
This usually happens when:
The custom tool’s JavaScript file is not publicly accessible.
Exporters are improperly configured during tool registration.
To fix this issue, follow two key steps:
Make sure the JavaScript file for your custom tool is accessible via a secure, public URL. You can host it on:
AWS S3
GitHub Pages
Netlify
Any reliable CDN
Then, pass this URL in the customJS property when initializing the editor and using the export APIs.
Ensure your tool’s registration includes valid exporters for email, web, popup, and document displayModes.
Unlayer uses exporters to convert tools into exportable content in different formats, such as HTML, PDF, and image. If you skip this step, the server has no idea how to interpret your tool, leading to a “Missing” placeholder.
You’ve integrated the Unlayer editor into your React app, and everything seems to be working except the File Manager. This can be confusing, especially if you’re on a paid plan or expect the File Manager to be available by default.
This behavior is intentional. If you’re using a Custom Image Library, Unlayer automatically disables its built-in File Manager. That’s because both systems serve the same purpose and cannot coexist.
🔒 The Custom Image Library gives you full control over how users upload, browse, and manage their images, so the default File Manager is turned off to avoid conflicts.
Here’s what you need to know:
✅ If you want to use Unlayer’s built-in File Manager, do not configure a custom image library.
✅ If you need a custom image workflow (e.g., user-specific folders, secure uploads), go with the Custom Image Library — but understand that the File Manager UI will no longer be visible.
📘 Unlayer Image Library Documentation
💻 Live Example – Custom Image Library in Action
We have tried to unpack the most common roadblocks developers face and how to fix them through this blog.
Still, every integration is a little bit different. If you run into any unusual scenarios or bugs that we didn’t cover here, don’t hesitate to reach out to our technical support team or drop a comment below. We’ll make sure your concerns get to the right hands.
And remember, most of these issues boil down to configuration missteps. By following the setups outlined above, you’ll avoid common pitfalls and spend more time building great email and landing page experiences.
If you’re still troubleshooting issues in React with Unlayer and need help beyond what’s in this guide, we’re here for you.