Canvas and drag-and-drop
An iframe canvas with selection, drag targets, drop indicators, resizing and a component toolbar — the interaction layer that takes months to get right and that nobody buys your product for.
PageKit — the self-hosted GrapesJS site builder, sold as source. Get early access
Create a visual email editor for your SaaS, CMS, marketing platform or internal tools. Let users build responsive emails with drag-and-drop blocks while your team controls components, templates, storage, output and publishing.
Your users build emails visually. Your application controls the experience.
This is a real GrapesJS editor running on this page — not a video and not a screenshot. Drag a block in from the right, select anything to restyle it, switch to the phone width, then read the markup back out. The header and footer are locked, which is the same pattern the section further down explains.
What you can do here
A drag-and-drop email builder is a visual editor that lets users create email layouts from reusable components instead of manually writing HTML tables and email-specific CSS.
The difference is who has to know the rules. Without a builder, whoever writes the email needs to know that layout is done with nested tables, that most styling has to be inline, and that a stray margin can collapse the design in one client. With a builder, those rules are encoded once — by you — into blocks and components, and everyone else just arranges them.
Without a visual builder
Someone hand-writes table markup, then hand-checks it. Every new email starts from a copy of the last one.
<table role="presentation" cellpadding="0" cellspacing="0" width="600">
<tr>
<td style="padding:32px;font-family:Arial,sans-serif">
<h1 style="margin:0 0 12px;font-size:26px">Welcome</h1>
<p style="margin:0;font-size:15px;line-height:1.6">Your content…</p>
</td>
</tr>
</table>With a drag-and-drop builder
Someone arranges blocks. The table markup is generated from components you defined and reviewed once.
The builder is not there to make email easy. It is there to make the hard parts somebody else's decision — yours, made once, at the component level.
A web page runs in a browser you can broadly reason about. An email runs inside whatever client the recipient opened it in, and that client may rewrite your markup, strip your styles, or lay it out with a different engine entirely. A builder for email therefore has to constrain what users can produce, not just make production pleasant.
Where your email has to survive
Gmail
Web, Android and iOS apps behave differently from one another, and the web client processes your styles before rendering.
Outlook
Desktop, web and the newer Windows client are effectively different renderers. Desktop builds on Windows have historically been the strictest target.
Apple Mail
The most permissive of the common clients, which makes it a poor proxy: an email that looks right here can still break elsewhere.
Yahoo Mail
Another webmail client with its own sanitisation rules for styles and markup.
Mobile clients
Narrow viewports, aggressive text scaling, and dark-mode handling that varies by app and OS version.
Everything else
Corporate gateways, regional providers and older clients that never appear in a typical test matrix.
What that forces on the builder
Layout is tables, not flexbox or grid
Your columns, spacers and dividers must be table components. Users should never be able to drop a bare div into the canvas.
CSS support is partial and uneven
Restrict the Style Manager to properties you have actually tested, rather than exposing the browser's full set.
Styles are safest inline
Either author with a preset that inlines on export, or run an inliner in your own pipeline before the email leaves your backend.
Media queries are not universally honoured
Design so the single-column fallback is already acceptable, and treat responsive rules as an improvement rather than a requirement.
Images are often blocked by default
Require alt text on every image component, and never let a block depend on an image to be readable.
Dark mode may recolour your email
Some clients invert or shift colours automatically. Avoid designs that depend on a specific background staying exactly that colour.
Web fonts frequently do not load
Ship a real fallback stack on every text component, and check the email with the fallback rather than the intended face.
Scripts do not run, and interactivity is limited
Anything dynamic belongs behind a link. A builder that offers users interactive blocks is offering them something that will not work.
Commercial mail carries legal requirements
Sender identity and an unsubscribe mechanism are obligations, not design choices. Lock them into the template rather than trusting a checklist.
You are not building an editor that produces beautiful HTML. You are building an editor that cannot produce dangerous HTML.
Client behaviour changes with releases and rendering modes, so this page states no per-client support matrix. Validate against the clients and features your own audience actually uses. Last reviewed 2026-09-03.
GrapesJS is an open-source (BSD-3-Clause) web builder framework, currently 0.23.6, with roughly 26k+ stars on GitHub. It gives you the parts of an editor that are expensive to build and boring to differentiate on, and gets out of the way for the parts that are actually your product.
An iframe canvas with selection, drag targets, drop indicators, resizing and a component toolbar — the interaction layer that takes months to get right and that nobody buys your product for.
Define an email-safe component once, then expose it as a block users drag in. Components carry their own rules: what they accept, what can be edited, what can be styled.
A styling panel you can restrict to the properties email actually supports, and a traits panel for the settings that are not CSS — a link target, an alt text, a merge field.
A structure tree for nested tables that are hard to select by clicking, and an asset manager you can point at your own media library.
The whole editor state serialises to project JSON. Point the Storage Manager at your API and the editor stops caring where the data lives.
Every editor action is a named command you can call, override or add to — which is how presets, exporters and the 100+ plugins on GJS.Market extend it without a fork.
GrapesJS provides the editing layer. Your application provides the email product.
Your product: accounts, tenants, billing, the route the editor is mounted on.
Your shell around the editor: template picker, save state, send button, approvals.
The visual editing engine. It knows about components and canvases, and nothing about your users.
The editor subsystems you configure: which blocks exist, what they accept, what can be styled, what can be edited.
The serialised editor state. This is what you store so an email can be reopened later.
Your API: permissions, versioning, merge-tag resolution, validation, campaign records.
The markup you actually send. Generated on demand from the stored project.
The provider that delivers the mail and reports what happened to it.
The inbox, and whichever client happens to render it.
GrapesJS never talks to your database and never talks to your email provider. It hands you a document; everything after that is your architecture.
Three owners, no overlap. The most common architectural mistake on this page's topic is expecting the editor to do something in the right-hand columns.
Nothing in the right-hand columns is a GrapesJS gap — those rows were never the editor's job. Treating them as application work from the first sprint is what keeps the integration simple.
Three artefacts come out of the same canvas, and they are not interchangeable. One is the project you keep, two are the delivery formats you generate from it.
The editable representation of the email — components, styles, assets and pages, exactly as the editor holds them.
Best for
How you get it
editor.getProjectData()This is the one to store. Storing only rendered HTML means the next edit starts from parsed markup rather than from the document the user built.
The table-based markup you hand to a sending provider, with styles inlined so more clients keep them.
Best for
How you get it
editor.runCommand('gjs-get-inlined-html')The inlining command comes from the newsletter preset, not from the GrapesJS core. Without it you get the canvas markup and a stylesheet, which is not what you want to send.
A higher-level markup language for email that compiles down to responsive table HTML.
Best for
How you get it
editor.runCommand('mjml-code')MJML is a separate ecosystem: the editor plugin renders MJML components, and a compiler turns them into HTML. Both are third-party packages you add.
JSON is the project. HTML and MJML are the delivery. Store the first, generate the others.
From canvas to inbox
Do not hand users a blank canvas and a div. Ship a small set of components that already encode the table markup, the inline styles and the fallbacks, then let people arrange them. These ten cover most of what marketing and lifecycle email actually needs.
Logo, brand bar, preheader text. Usually locked so every email leaves with the same identity.
Headline, supporting line and one action. Sized so the message survives without the image loading.
A paragraph block with a real font fallback stack and line height that holds up at phone widths.
Fixed width, explicit dimensions and required alt text, so a blocked image still leaves a readable email.
A table-based button rather than a styled anchor — the difference is visible in stricter clients.
A table row that stacks on narrow viewports, with the stacked order already decided by you.
Image, name, price and link, driven by fields your backend fills in rather than by copy-paste.
A fixed row of icons and URLs, editable as settings rather than as markup.
A rule drawn with a table border rather than an <hr> element, which several clients restyle.
Sender identity, address and unsubscribe link. Usually locked, because these are obligations.
Let users design visually without requiring them to understand email HTML.
A component is the definition; a block is how a user gets one onto the canvas. The block library is the single most powerful lever you have over what people can build — and the easiest one to get wrong by being generous.
The loop your users repeat
Every element available, every style editable, no opinion about structure.
A short list of blocks you have designed, tested and can change centrally.
Three levels, and confusing them is what makes email builders hard to grow. Components are what things are, blocks are how they get added, templates are finished starting points.
Templates
A whole email a user can start from — newsletter, receipt, onboarding step. Stored as project JSON, cloned on use.
Blocks
The draggable units a template is assembled from: a hero, an article row, a CTA band, a footer.
Components
The editable primitives inside a block, with their own traits and rules — a product card that knows it has a price.
Build the components first, then the blocks that expose them, then the templates that arrange them. Teams that start at the template level end up with dozens of near-identical designs and no way to change any of them.
A newsletter template, expanded
Newsletter template
Each child is a block; each block is made of components. Change the article block once and every newsletter built from this template picks it up the next time it is opened.
Locking in GrapesJS is not a mode or a plan tier — it is a set of options on a component definition. A locked region still renders, still exports, and simply refuses to be dragged, deleted or restyled in the editor.
Company header
LockedPresent in every email, identical in every email. Nobody needs to move it.
Editable content
EditableText and images the sender is actually here to write.
Call to action
EditableEditable label and link, and a short list of colours — not the markup underneath.
Legal footer
LockedSender identity and unsubscribe link. An obligation, not a design decision.
The options that produce it
removable: false, draggable: falseeditable: truestylable: ['background-color', 'color']removable: false, copyable: falseWhat this buys you
These options govern the editor UI. They are not authorisation. A modified project posted straight at your save endpoint is bound only by what your server validates — so validate the structure, the required regions and the permitted fields on the backend, every time.
Personalised email is two separate problems: letting a user place a placeholder without knowing the syntax, and resolving that placeholder against real data at send time. The editor solves the first one. Only your application can solve the second.
Merge tags in the template
{{first_name}}{{company_name}}{{order.total}}{{unsubscribe_url}}As far as the editor is concerned these are ordinary text. They are stored exactly as written and survive save, reopen and export untouched.
How users insert them
Expose the available fields as a trait on your text component — a dropdown of “First name”, “Company”, “Order total” — and write the token into the content yourself. Users pick a field; they never learn a syntax, and they cannot invent a tag your renderer does not know.
Dynamic blocks
A block can be a placeholder for content that does not exist yet. The user drops in a product card and configures which products it should show; the values arrive when the email is rendered.
GrapesJS provides the editing experience. Your backend resolves dynamic data and merge tags when the email is rendered or sent.
Two widths cover almost all of the design review: the width the email is authored at, and a phone. Both are a device switch away in the editor, and neither is the same thing as testing.
The workflow that actually catches problems
An in-editor preview is your browser rendering the canvas markup. A mail client renders a transformed version of that markup through its own engine, and the two can differ. Preview is for design review; a seed list or a rendering service is for compatibility.
Most email disasters are not rendering bugs. They are a broken link, an unfilled merge tag or a missing unsubscribe. All three are cheap to catch on the server, and expensive to catch in an inbox.
Run before a template can move out of draft.
Cheap to automate, and the most common single failure.
Non-negotiable, so enforce it in code rather than in a checklist.
The Storage Manager is an interface, not a database. Implement it against your own API and the editor loads, autosaves and restores through endpoints you control — which is the point at which drafts, versions and approvals become possible at all.
The lifecycle your backend defines
// The editor hands you project JSON; your backend decides
// what a draft, a version and an approved template mean.
editor.Storage.add('email-api', {
async load({ templateId }) {
const res = await fetch(`/api/email-templates/${templateId}`);
return res.json(); // -> project JSON
},
async store(data, { templateId }) {
await fetch(`/api/email-templates/${templateId}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
// Authorization is enforced server-side. Never trust this call.
body: JSON.stringify({ project: data }),
});
},
});Autosave is throttled by the number of changes rather than by a timer, so a burst of edits still costs one request. Keep versions as immutable copies of the project JSON — restoring a revision then costs nothing but a load, and a bad edit stops being an incident.
Nothing about this architecture asks you to change how you send mail. The editor produces a document; your backend renders it and hands it to whatever provider you already pay for.
GrapesJS
Produces the project and, on request, the markup. Knows nothing about recipients.
HTML / MJML
Generated on demand from the stored project, with styles inlined or MJML compiled.
Your backend
Resolves merge tags, validates the output, records the campaign, calls the provider.
Your provider
Delivers the mail and reports bounces, complaints, opens and clicks back to you.
Examples of where your backend posts
Listed alphabetically as illustrations of the last hop. Any provider with an HTTP API or SMTP endpoint fits the same shape, and swapping one for another does not touch the editor.
GrapesJS is the editor, not your email delivery service.
Neither GrapesJS nor GJS.Market ships an integration with any provider named above, and no partnership is implied. Deliverability, authentication and suppression handling remain your provider's and your application's responsibility.
// Server side. GrapesJS is not in this file — and that is the point.
const template = await db.emailTemplates.find(templateId);
// 1. Your application resolves merge tags. The editor never does.
const html = renderMergeTags(template.html, {
first_name: user.firstName,
unsubscribe_url: unsubscribeUrlFor(user),
});
// 2. Your ESP delivers it. Swap the client, keep the editor.
await esp.send({ to: user.email, subject: template.subject, html });The same editing layer, packaged eight different ways. What changes between them is who edits, what they are allowed to change, and where the output goes.
Let customers design lifecycle and campaign email inside your product, against templates and blocks you control per plan.
SaaS builder architectureGive a marketing team a visual editor for a recurring format, with an article block that keeps every issue consistent.
Newsletter pluginsAuthor the emails that sit inside campaigns and workflows, sharing blocks with the landing pages they point at.
Landing page builderTightly controlled templates for receipts and notifications — mostly locked, with a small editable middle.
Template systemsLet content teams reuse the blocks they already write web content with, stored beside the rest of the content model.
Headless CMS editingOne editor, a branded block set per client, and templates that cannot be pulled out of shape between reviews.
White-label buildersGive internal teams a controlled editor instead of a shared HTML file and a request queue.
No-code for developersMake the editor look and behave like part of your product, down to the panels, the icons and the language.
White-label the editorNot a judgement, a scope check. Every row is something an email builder needs. The left column is work you would own; the right is what GrapesJS gives you before you write a component.
| Capability | From scratch | With GrapesJS |
|---|---|---|
| Visual canvas | Build it | Available |
| Drag and drop | Build it | Available |
| Component model | Build it | Available |
| Block library | Build it | Extensible |
| Style editing | Build it | Available |
| Layer tree | Build it | Available |
| Undo / redo | Build it | Available |
| Asset management | Build it | Available |
| Storage integration | Build it | Configurable |
| Custom components | Build it | Supported |
| Plugin architecture | Build it | Supported |
“Available” means the capability ships with the core and needs configuration, not construction. It does not mean it needs no work: every serious email builder defines its own components, restricts its own styles and writes its own storage adapter. Verified against grapesjs 0.23.6 on 2026-09-03.
Build your email product, not another editor engine.
Real listings from the GJS.Market catalogue, grouped by the stage of the build they belong to. Names, prices and images come straight from the marketplace, so what you see here is what is actually for sale today.
What turns a page editor into an email editor: table-based blocks, an email-shaped Style Manager, import and export.
Browse the categoryReady-made email sections, and a way to let users save their own.
Browse the categoryA library of starting points, so nobody begins an email from an empty canvas.
Browse the categoryFor the one part of an email that has to be hand-written — a tracking pixel, a provider-specific block, a snippet legal supplied.
Browse the categoryAn asset manager backed by storage that outlives the draft, because a broken image URL is a broken email.
Browse the categoryBack the asset manager with Cloudinary, so email images have a permanent, transformable URL.
An upload UI in front of the asset manager, with progress, retries and multiple sources.
Swap the default uploader for Filestack's, including its own picker and transformations.
Where the project JSON lives, and how often the editor is allowed to write it.
Browse the categoryPersist project JSON to Firebase without writing a storage adapter first.
Keep email projects in Directus alongside the rest of your content model.
Rate-limit saves so a burst of edits costs one request instead of dozens.
Export the finished document for handoff, review or an existing build pipeline.
Browse the categorySome capabilities on this page have no plugin behind them, and this section will not invent one: there is no preview or spam-check product, no CSS-inliner product, no merge-tag product and no ready-made email-template product in the catalogue. Those are described here as patterns to implement, or as work our team can do with you.
Four builds, assembled only from listings that exist today. Start at the row that matches what you are shipping and add the next layer when it earns its place.
A first internal editor: drag blocks, save a template, export HTML.
MJML authoring, a designed block set, templates and a per-component code escape hatch.
Customer-facing: MJML output, template records, remote storage and throttled autosave.
See SaaS architectureA marketing team's editor: blocks, templates and hosted assets that outlive the draft.
Prices come from the live catalogue at build time. Free listings are marked; paid ones link straight to their product page.
The shape of the work, in the order it makes sense to do it. Each step links to the guide that covers it properly — this page is a map, not a tutorial.
Mount GrapesJS on a container, set the two devices email needs, and turn off the default local storage before anything else.
GrapesJS setup guide →Write the table-based components your emails are made of, restrict what can be styled, and expose each as a block.
Email components in depth →Give users somewhere to start. Store templates as project JSON and clone on use rather than editing the original.
Template systems →Implement a storage adapter against your API so drafts, autosave and versions are yours to define.
Storage plugins →Generate the markup on send, resolve merge tags server-side, and hand the result to your provider.
Get help with the build →import grapesjs from 'grapesjs';
import newsletter from 'grapesjs-preset-newsletter';
const editor = grapesjs.init({
container: '#email-editor',
height: '100%',
// Email is authored at a fixed width; give users one extra viewport
// to check, not a full responsive breakpoint set.
deviceManager: {
devices: [
{ id: 'desktop', name: 'Desktop', width: '' },
{ id: 'mobile', name: 'Mobile', width: '375px' },
],
},
// Curated blocks only: the preset's table sections, plus your own.
plugins: [
(ed) => newsletter(ed, { inlineCss: true, showBlocksOnLoad: true }),
],
// Point the editor at your API rather than the browser's localStorage.
storageManager: {
type: 'remote',
autosave: true,
stepsBeforeSave: 10,
},
});// An email-safe button: users edit the label and the link,
// never the table markup that makes it render in Outlook.
editor.DomComponents.addType('email-button', {
isComponent: (el) => el.dataset?.type === 'email-button',
model: {
defaults: {
draggable: '[data-gjs-type="cell"], td',
// Users may recolour it; they may not restyle it into a div.
stylable: ['background-color', 'color', 'border-radius'],
traits: [
{ name: 'href', label: 'Link' },
{ name: 'label', label: 'Button text' },
],
components: `
<table role="presentation" cellpadding="0" cellspacing="0">
<tr><td><a href="#">Read the update</a></td></tr>
</table>`,
},
},
});Both samples run against grapesjs 0.23.6. `grapesjs-preset-newsletter` and `grapesjs-mjml` are separate BSD-3-Clause packages — the core ships no email preset — so install whichever matches the output format you chose above.
What separates a working demo from an email builder you can hand to customers. Everything here is your work, not the editor's.
What users can and cannot do.
Nothing a user makes should be losable.
Validated on the server, every time.
The parts that are not about design.
An email builder accepts user-authored markup and sends it to third parties. That combination deserves more care than a typical CRUD feature.
Risk
Locked regions, restricted styles and hidden blocks live in the browser. A crafted request can post any project it likes.
What to do
Validate the saved project on the server: required regions present, component types on an allowlist, fields within bounds.
Risk
A code-embed component is a feature request that arrives on every email builder, and it is an injection surface pointed at your customers' inboxes.
What to do
Sanitise the stored markup and re-sanitise at render. Restrict who may use a code component, and log every use of it.
Risk
Uploads reached from an editor end up on public URLs that live as long as the email does.
What to do
Validate type and size on the server, re-encode images, and serve assets from a domain that carries no session cookies.
Risk
Rendering and sending are the expensive, irreversible operations. They are usually protected less carefully than the editor route.
What to do
Authorise per template and per recipient list, rate-limit sends, and require a second check before anything goes to a full audience.
Risk
A permissive template renderer will happily interpolate a field the sender was never supposed to see.
What to do
Resolve against an explicit allowlist of fields for that template, and fail the render on an unknown tag rather than emitting it.
The editor is a large dependency living inside your application. These are the levers that matter, in the order they usually pay off.
Load the editor lazily
Import it only on the route that edits an email, and only in the browser. Nothing about the editor belongs in a server render or in your main bundle.
Lazy-load optional plugins
A code viewer, an image editor or an MJML compiler can arrive when the user opens that panel rather than at init.
Keep the block library short
Every block is markup parsed at startup and a card rendered in the palette. A curated set is faster as well as safer.
Debounce autosave
Throttle by change count rather than by keystroke, so a burst of typing produces one request instead of thirty.
Keep project data lean
Reference assets by URL. Base64 images inside project JSON make every load, save and version copy heavier.
Watch custom components
Component logic that runs on every change is the usual cause of a canvas that feels sluggish on long emails.
No figures are quoted here on purpose: the numbers depend on your block count, your component logic and your users' hardware. Measure the editor route in your own application before and after each change.
Now that the architecture is clear, the trade-off is easy to state. A hosted builder is faster to put in front of a customer; a builder you own is a feature of your product rather than a dependency of it.
An embeddable editor operated by a vendor, integrated through their API and their UI.
What you get
Fastest route to a working editor, with the roadmap and the pricing owned by someone else.
Compare with a hosted vendorAn open-source editing layer you configure, extend and deploy as part of your own application.
What you get
More work up front, and an editor that grows with your product instead of around it.
Start buildingHosted platforms differ substantially from one another, which is why the left column says “depends on the provider” rather than making a blanket claim. Check the specific vendor's terms on data location, retention, customisation limits and what happens to your templates if you leave.
GrapesJS renders into a plain DOM element, so integrating it is a question of which lifecycle hook calls init and which one calls destroy. Your framework powers your application; GrapesJS powers the visual email editing layer.
If you would rather have this working than research it, our team builds GrapesJS-based email editors as a service — including the parts of this page that have no plugin behind them.
Give your users a visual way to create emails without forcing your development team to build an email editor from scratch.
Tell us what the editor has to do and get a scoped plan for the integration.
Start BuildingPresets, blocks, templates, storage and asset plugins from the marketplace.
Explore Email PluginsComponents, templates, storage and publishing, delivered against your stack.
Talk to a GrapesJS expertYour users build the emails. Your product owns the experience.