Embed an Email Template Builder in Your CRM
Your users already live inside your CRM. When they want to design a sales email, an onboarding sequence template, or a customer broadcast, the last thing they want is to leave the dashboard, log into a separate tool, copy markup back and forth, and hope it survives Outlook. The cleanest answer is to put the editor where the work already happens — embed a drag-and-drop email template builder directly inside your CRM's UI. BuilderJS is built for exactly this. It is a framework-agnostic, vanilla-JavaScript drag-and-drop builder for HTML email and web/landing pages that you self-host and mount inside your own application — React, Vue, Svelte, Angular, Next.js, Laravel, or a plain server-rendered page. It is the editor surface only, and that boundary is the point: BuilderJS builds and exports the markup, while your CRM keeps doing what it already does — owning contacts, lists, segments, sequences, sending, and deliverability. There is no SaaS account between your users and their work, no per-seat SDK bill that scales with your customer base, and nothing phones home. You buy the source once under a CodeCanyon one-time license, host the bundle from your own origin, and embed it in as many of your own CRM screens as your tier allows. Designs round-trip as plain JSON through getData() and load(), so your CRM owns persistence end to end. This page explains where BuilderJS fits in a CRM, the honest division of labor, and the exact mount-and-persist pattern your team can ship.
Mount it
import { useEffect, useRef } from 'react';
// Self-host builder.js + builder.css and load them globally (e.g. in index.html),
// which attaches window.Builder. There is NO npm package and NO import.
export default function CrmTemplateEditor({ templateId, onSaved }) {
const mounted = useRef(false);
const instance = useRef(null);
useEffect(() => {
if (mounted.current || !window.Builder) return; // React 18 Strict-Mode guard
mounted.current = true;
instance.current = new window.Builder({
mainContainer: '#bjs-canvas', // REQUIRED — CSS selector string
widgetsContainer: '#bjs-widgets', // optional — drop to slim the chrome
settingsContainer: '#bjs-settings', // optional
});
// Restore a saved CRM template. load() takes 4 positional args, in this order:
// load(themeJson, themeTemplatesJson, themeConfigData, mediaUrl)
// Your server emits the saved design + theme bundle as these globals
// (THEME_JSON is the user's saved snapshot from a prior getData()).
instance.current.load(
window.THEME_JSON,
window.THEME_TEMPLATES,
window.THEME_CONFIG_DATA,
window.MEDIA_URL
);
// No destroy()/cleanup — React discards the DOM on unmount and the editor with it.
}, []);
// Persist into YOUR CRM's database — BuilderJS does not store or send anything.
async function handleSave() {
const json = instance.current.getData(); // serializable design tree
await fetch(`/api/crm/templates/${templateId}`, { // your own backend route
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ design: json }),
});
onSaved?.();
}
return (
<div className="crm-template-editor">
<button onClick={handleSave}>Save template</button>
<div id="bjs-widgets" />
<div id="bjs-canvas" />
<div id="bjs-settings" />
</div>
);
}
Why a CRM is the right place to embed the editor — and where the line is
A CRM is one of the most natural homes for an embedded email editor because the audience is already there. Sales reps drafting outreach, success teams writing onboarding templates, and marketers building broadcasts all work inside the CRM all day; sending them out to a separate design tool breaks their flow and creates copy-paste errors. Embedding BuilderJS in your template-management screen means a user clicks 'New template', a drag-and-drop canvas opens inside your own dashboard, and they design without ever leaving. That said, BuilderJS is deliberately the editor component, not a marketing platform — and being clear about that line is what keeps the integration honest and clean. BuilderJS does not send email, manage contact lists or segments, run sequences and automations, score leads, track opens and clicks, or handle deliverability and suppression. Those are exactly the things your CRM already does well and should keep owning. BuilderJS produces the template markup; your CRM merges in contact fields, picks the audience, schedules the send, and routes it through your ESP or SMTP. The result is a tidy contract: the editor builds the design, the CRM owns everything around it. You are adding a best-in-class design surface to your product, not bolting on a second platform that competes with your own.
Email templates that survive real inboxes — and pages from the same editor
CRM email goes to real inboxes, which means Gmail, Outlook, and Yahoo, each with their own quirks. BuilderJS handles that for you through its EmailExportPipeline: it converts modern flexbox and grid layouts into nested, role=presentation tables, adds MSO conditional comments and ghost tables for Windows Outlook, generates @media (max-width:600px) rules so columns stack on mobile, makes images fluid (max-width:100%; height:auto), rasterizes SVG to PNG for clients that won't render vectors, and runs a CSS inliner that pushes every safe declaration onto its element — because Gmail, Outlook.com, and Yahoo routinely strip head and external CSS. The library ships 30+ templates and 200+ samples that were Litmus-tested across 22 email clients, so the output your CRM users export is built for inboxes that actually exist, not a clean-room browser. And because many CRMs also need landing pages, signup confirmations, or hosted forms, the same editor produces standalone, modern-CSS page HTML from the same design — one embedded tool covers both your transactional/marketing email templates and your page-building needs, instead of two separate integrations. To be precise about the handoff: BuilderJS builds and exports the HTML; your CRM brings the sending for email and the hosting for pages.
JSON-first persistence: your CRM database owns every template
Designs in BuilderJS are plain JSON, which fits a CRM's data model naturally. Call builder.getData() and you get a serializable snapshot of the full design tree — every element, its property values, and the nesting between them — and you POST that JSON to your own backend to store it however your CRM already stores records: a JSON or text column on a templates table, scoped to the account, the workspace, or the individual rep. To re-open a template for editing, call builder.load(themeJson, themeTemplatesJson, themeConfigData, mediaUrl) — exactly four positional arguments, in that order — passing the saved snapshot back as the first one, and the editor rebuilds the exact state losslessly. There is no proprietary store you get evicted from, no usage meter counting templates, and no export wizard gating clean code behind a paid tier. For a multi-tenant CRM this matters: BuilderJS is multi-tenant capable via closure-captured configuration, so each tenant's editor instance is isolated, but your CRM owns tenant isolation, seats, and billing — which you already do. Because the data is just JSON, you can version templates in git, diff them in review, and migrate a property across thousands of saved designs with a script. The package also includes reference PHP backends (MySQL / SQLite / S3 for save, upload, and auth) as working examples you copy and own, and can port to Node, Go, or Python to match your CRM's stack.
Mount it inside your CRM dashboard
BuilderJS is delivered as a compiled browser bundle, not an npm module — loading builder.js attaches a global window.Builder, so there is no import { Builder } from anything and no package to install. You self-host builder.js and builder.css from your own origin and add them with a stylesheet link and a script tag. The constructor takes CSS selector strings (not DOM nodes, not refs): mainContainer is required and resolves the canvas; widgetsContainer and settingsContainer are optional sidebars, so a selector that resolves to nothing simply leaves that panel unmounted — handy when your CRM has limited horizontal space and you want a leaner chrome. BuilderJS is a client-side DOM-mount editor and is never server-rendered, so mount it after the DOM exists: inside useEffect in React, onMounted in Vue, or ngAfterViewInit in Angular. There is no destroy(), teardown(), or dispose() method — on a real unmount your framework discards the container DOM node and the editor goes with it, so the only thing to guard is constructing it exactly once (use a ref flag to survive React 18 Strict Mode's intentional double-invoke in development). Because the engine is framework-agnostic, the same bundle drops into whatever your CRM front end is built with, unchanged. The snippet below shows the exact, copy-paste mount-and-save pattern for a React-based CRM screen.
Own the editor outright — no per-seat SaaS bill as your CRM grows
Embeddable builder SDKs sold as SaaS bill you every month for as long as your CRM exists, and that cost tends to scale with your success — per-session metering, per-seat charges, white-label add-ons. For a CRM that may serve thousands of accounts, that recurring line item compounds fast. BuilderJS is the opposite model: a one-time CodeCanyon license (item 27146783, Regular or Extended) paid once, with the full source delivered for you to self-host and embed in your own product. There is no SaaS layer, no monthly fee, no per-seat charge, no usage metering, and no phone-home telemetry; your only ongoing cost is the server you already run for your CRM. The honest boundary on ownership: the code is yours to keep and run perpetually, but author support and version updates are time-boxed (roughly 6–12 months under CodeCanyon's standard window), after which you self-maintain the source you own — we never call support or updates 'lifetime.' If you charge your CRM's end users (as most do), the Extended tier is the one to choose. You can try the full editor and the getData()/load() round-trip on the live demo at builder.emotsy.com before you commit, then read /one-time-license/ for the ownership model and /embed/react/ for the framework-specific mount guide.
Frequently asked questions
-
Does BuilderJS send the emails my CRM users design, or manage contacts?
No. BuilderJS is the embeddable editor only. It builds and exports the template markup — Outlook-safe HTML for email or modern-CSS HTML for pages — and your CRM keeps doing everything else: contacts, lists, segments, merge fields, sequences, scheduling, sending through your ESP or SMTP, and deliverability. The clean split is intentional: BuilderJS owns the design, your CRM owns the audience and the send.
-
How do I store a template a user designs, scoped to their CRM account?
Call builder.getData() to get a JSON snapshot of the design and POST it to your own backend — store it on a templates table as a JSON or text column, scoped to the account, workspace, or user exactly as your CRM scopes other records. To re-open it, call builder.load(themeJson, themeTemplatesJson, themeConfigData, mediaUrl) with those four positional arguments, passing the saved snapshot as the first one, and the editor restores the exact state losslessly. Your CRM database owns persistence end to end.
-
Can I embed it in a multi-tenant CRM, and is there a per-seat fee?
Yes. BuilderJS is multi-tenant capable via closure-captured configuration, so each tenant's editor instance is isolated, and there is no per-seat charge, usage meter, or revenue share. The honest caveat: your CRM owns tenant isolation, user seats, and billing — which it already does. If your end users are charged, choose the Extended CodeCanyon tier, and review the license terms for your distribution model before launch.
-
Which front-end frameworks does it work with — my CRM is built on [React/Vue/Angular]?
All of them. BuilderJS is a framework-agnostic vanilla-JS bundle that attaches a global window.Builder when its script loads; there is no npm package and no module import. You render three container divs, then call new window.Builder({ mainContainer, widgetsContainer, settingsContainer }) with CSS selector strings inside your framework's mount hook (useEffect for React, onMounted for Vue, ngAfterViewInit for Angular). The same bundle drops into any of them unchanged.
-
Will the email templates render correctly in Outlook and Gmail?
That is what the EmailExportPipeline is for. On export it converts flexbox and grid to nested tables, adds MSO conditional comments and ghost tables for Windows Outlook, generates @media mobile-stacking, makes images fluid, rasterizes SVG to PNG, and inlines CSS — because Gmail, Outlook.com, and Yahoo strip head and external styles. The shipped templates and samples are Litmus-tested across 22 email clients. BuilderJS produces the inbox-safe markup; your CRM hands it to your sending path.