For email developers

Your email design system, as code

Pull your Better Email Design System into plain Liquid and JSON files. Build modules in your own editor, review in pull requests, validate in CI, push back with one command. Marketers keep shipping campaigns from the same system in the visual editor.

Bring your stack

MaizzleReact EmailPlain HTML+ your editor, your git, your CI
~/acme-design-system on main
$

The standoff

You already have a workflow you like

Every email platform asks you to give it up and move into their web editor. This one doesn't.

What you refuse to give up

  • Git, branches, and pull requests
  • Your editor, your keybindings, your dotfiles
  • Hot reload on save
  • CI that blocks bad changes
  • Diffs you can actually read

What your org actually needs

  • Modules marketers can safely fill in
  • Safe rollouts that can’t break in-flight campaigns
  • Review, approvals, and localization
  • One-click export to the ESP
  • A visual editor that never breaks the brand

Until now, connecting the two meant pasting HTML into a web editor, then pasting again after every change. Design Systems as Code splits the job instead: code owns structure, cloud owns content. You keep your loop. They keep theirs.

The format

It's just files

A Design System is a directory. The directory is the source of truth.

acme-design-system/
acme-design-system/
├── design-system.json # name, content zones, metadata
├── base.liquid # the Template Base
├── settings.json # global settings schema
├── modules.order # order is diffable too
└── modules/
├── hero/
│ ├── module.liquid
│ ├── settings.json # the inputs a marketer gets
│ └── module.json
├── product_grid/
└── footer/
modules/hero/settings.json
[
{
"key": "hero_headline",
"name": "Headline",
"inputs": [{ "type": "text" }]
},
{
"key": "hero_cta",
"name": "Call to action",
"inputs": [{ "type": "text" }, { "type": "url" }],
"only_show_if": "hero_style != 'minimal'"
}
]

The file no framework has

Maizzle ends at HTML. settings.json is where a coded module becomes something a marketer can safely fill in: typed inputs, conditional visibility, repeatables, feed bindings. You declare them in code, next to the markup they control.

Plain Liquid and JSON

Grep it, diff it, vendor it, refactor it with sedif that's your thing. No binary blobs, no lock-in.

JSON Schemas, published

Every file has a $schema. Autocomplete and inline validation in your IDE, no compile step, and agents can read the spec.

Byte-identical round-trips

pull then push with no edits changes nothing, byte for byte. The serializer is property-tested against production design systems.

Rather read a real one? Meridian is a complete starter Design System in this format: 12 production-grade modules, MIT-licensed.

The loop

Pull, hack, check, push

The CLI works the way you'd write it yourself. The live preview renders locally with the same code as production: one engine, zero drift, no “looks different in the app” surprises.

~/acme-design-system
$ better ds list
ID NAME LIVE CANDIDATE LATEST
acme ACME 12 — 12
 
$ better pull --design-system acme
Pulled ACME live Version 12.
 
$ ls
base.liquid design-system.json modules/ modules.order settings.json

Your Design System, exploded into plain files. pull then push with no edits changes nothing, byte for byte.

The dev experience

Save the file, watch it render

better dev serves a live preview with an editable inputs panel: the same settings UI your marketers use, wired to the files in your editor.

  • The marketer’s inputs panel, against your local files

    Exercise settings.json while you write it: edit values in the panel, see the render change. What you ship is exactly what they get.

  • Rendered locally by the production code

    Not a lookalike, not a proxy: better dev runs the same render pipeline in-process. Works offline, no login required.

  • Save a file, every preview reloads

    Module switcher, Desktop and Mobile viewports, hidden modules marked. Deep-link a teammate straight to the module you're working on.

modules/hero/module.liquid — acme-design-system
> better email
Better Email: Start Local Preview
Better Email: Stop Local Preview
Better preview: running

The Better Email Design Systems extension for VS Code starts better dev and opens the preview beside your code, with schema-aware autocomplete, inline better check diagnostics, and a module tree view.

Install from the VS Code Marketplace or Open VSX for Cursor and friends.

app.better.email/design-systems/local-dev?server=4100
Previewing: ACME
PreviewRendered
DesktopMobile
Template BaseHeroProduct gridFooter
Shop new arrivals
BlockGlobal
Summer drop is live
Shop new arrivals
https://acme.com/new
Show legal footer

Release safety

Your refactor can't break 400 in-flight campaigns

better push writes to exactly one place: the Candidate channel. Going Live stays a human decision. Push stages, publish gates.

your repo

Modules, settings schemas, base. PRs, review, CI.

better push

Candidate

Staged revision. Previewable and testable in-app. Never auto-Live.

publish

Live

Usage guard re-runs at publish. Full provenance on every version.

float / pin

Campaigns

Floating campaigns follow the latest. Pinned campaigns stay put.

The platform knows what your diff touches

Git can tell you what changed. Better tells you who it affects. Remove a module that live campaigns still use, and the push says so before anything moves:

error Cannot stage this version because it removes
module "Hero", which is used in 3 campaigns.

Push is polite

A push negotiates the same editing lock as every other editor. It never clobbers a colleague's open session or a Candidate it didn't create:

locked This Design System is being edited in the
app by Alex — try again shortly.

In CI

Reviews with receipts

One GitHub Action covers the whole loop. On pull requests, better check validates the files and a sticky comment shows the module diff against Live. On merge to main, the design system is pushed as a Version, staged as a Candidate, and never auto-published. CI ships Versions. People publish.

.github/workflows/design-system.yml
name: design-system
on: [pull_request]
 
permissions:
pull-requests: write
 
jobs:
design-system:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 22 }
- uses: betteremail/design-system-action@v1
with:
api-key: ${{ secrets.BETTER_API_KEY }}
b

better-email bot

commented on pull request #142

Design System diff vs Live Version 12

ModuleChangeFlags
Hero~ editedcontent, settings
Testimonial+ added
Legacy banner- removed

Template Base unchanged · Module order changed

better check passedValidation failures fail the build. The diff never does; it informs review.

betteremail/design-system-action · CI recipes in the docs

On-ramps

Keep your build

Nobody rewrites forty modules to try a new tool.

Maizzle

$ better-maizzle build && better push

Keep your Maizzle and Tailwind build. Annotate a component with a better: front-matter block and the build emits modules, validated by the same serializer push uses. Starter repo.

@better-email/maizzle

React Email

$ email build && better push

Export a settings schema next to your components. Your JSX stays the source; the build output becomes modules your marketers can use. Starter repo.

@better-email/react-email

Starting fresh

$ npm create better-email

A scaffold with the full layout, JSON Schemas wired for autocomplete, and an AGENTS.md for your coding agent. Learn from Meridian, the open starter Design System.

create-better-email

Both worlds

Marketers never see your terminal

Flip a Design System to managed-in-code and the deal is enforced: structure lives in your repo, content stays in the cloud.

app.better.email — ACME

Managed in codeModules and the Template Base are edited in your team's repository. Campaign editing is unaffected.

What marketers keep (everything)

  • Building and editing campaigns, visually
  • Approvals, comments, and review flows
  • Localization across every market
  • Pre-flight checks and client testing
  • One-click export to the ESP

The in-app module editor goes read-only for everyone, so there is no two-way merge problem and no “who overwrote my change” conversation. Reversible anytime, per Design System, from settings.

Agents

Your coding agent speaks email now

Plain files, published schemas, and a fast check loop are exactly what coding agents are good at. The same push guardrails apply to them, too.

AGENTS.md

AGENTS.md in every scaffold

The file format, the check loop, and the rules of the road, written for coding agents. better ai-files install adds them to any existing repo, Claude Code skill included.

better-email-mcp

MCP server

Claude Code, Cursor, or any MCP client can pull, diff, check, and stage a Candidate from inside your session. Same API as the CLI.

npx skill add better-email

Installable agent skill

Teach your agent the module format and the release model once. It scaffolds new modules that pass check on the first try.

FAQ

Questions

Is the local preview really identical to production?

Yes. better dev renders locally with the same render code that powers the app and your exports, not a reimplementation of it. One engine, zero drift. It even works offline, with no login.

What is the file format?

Plain Liquid and JSON: a design-system.json, the Template Base as base.liquid, and one directory per module holding module.liquid, settings.json, and module.json. Every JSON file has a published JSON Schema for IDE autocomplete. pull then push with no edits changes nothing, byte for byte.

Can marketers keep working while we manage the system in code?

Yes. Campaign editing, approvals, localization, and ESP export are untouched. Only module and Template Base editing moves to your repo. The in-app module editor becomes read-only with a banner pointing at it, and the flag is per Design System and reversible anytime.

What happens if someone is editing in the app when I push?

The push negotiates the same editing lock as every other editor. If a colleague holds it, the push fails and tells you who has it. It never clobbers their session, and it never overwrites a Candidate it didn't create without explicit acknowledgment.

Does it work with Maizzle or React Email?

Yes. Adapters keep your existing build: annotate components with a settings schema, then maizzle build && better push (or the React Email equivalent) emits modules. You migrate a design system without rewriting it.

Do we have to adopt all of it at once?

No. The CLI works without changing who owns what: pull your Design System into a repo, preview and push from code, and the in-app editor stays fully editable. Flipping a Design System to managed-in-code is a separate, reversible step for when the repo should become the single source of structure.

How do I get started?

Design Systems as Code is included with Better Email. Install @better-email/cli, run better login (it signs you in through the browser), then better pull. Not on Better Email yet? Request a demo.

Best of both worlds. No compromises.

Your workflow in git. Your organization shipping from the same design system. Pull yours down now, or see it on your own modules in a 30-minute demo.

Request a demo
get started
$ npm install -g @better-email/cli
$ better login # sign in through the browser
$ better ds pull acme # your Design System, as files
$ better ds dev # live preview, offline
$ better ds push # ship a Revision

Bring your stack

MaizzleReact EmailPlain HTML+ your editor, your git, your CI