Email Fast Get a sandbox key
Features

Templates that compile once and can't be injected

MJML for layout, Handlebars for data, typed variable schemas at the door, and a lint that makes unescaped output a build error instead of an incident.

Author once, render safely forever

Write MJML email templates with Handlebars variables and stop hand-assembling table HTML. Templates are versioned, every variable is typed, and the compile-time lint rejects any unescaped output — so template HTML-injection is structurally impossible rather than merely discouraged.

How it works

  1. Author in MJML for layout, Handlebars for data. MJML compiles at author time — never per-send — so sending renders merges of prebuilt HTML, and a broken layout surfaces when you save, not mid-campaign.
  2. Declare a typed schema per template: string, email, number, boolean, with required, maxLen, and enum constraints. A send whose data doesn't match is refused at admission.
  3. Every edit creates a version instead of silently rewriting what came before.
  4. The lint runs at compile time and rejects every unescaped output. There is no "just this once" escape hatch — that absence is what makes the guarantee structural.

The editor also warns at author time when compiled HTML approaches Gmail's 102KB clipping threshold, while shortening it is still cheap.

The evidence

A typed variable schema
{
  "name":  { "type": "string", "required": true, "maxLen": 80 },
  "email": { "type": "email",  "required": true },
  "seats": { "type": "number" },
  "plan":  { "type": "string", "enum": ["free", "starter", "growth"] }
}

Four fields, four types, hard limits. A payload of { "plan": "<script>" } doesn't render harmlessly — it never gets in: rejected at admission, before a message exists.

Honest limits

Strictness you'll occasionally curse

There is no unescaped output, period — if a workflow needs raw HTML injected through a variable, the lint wins that argument by design. MJML's compiled output is table-heavy HTML because that's what renders consistently across mail clients; view-source is not where it shines. And the 102KB warning can only warn: when a message is too big, the fix is a shorter email, and nothing else.

Where to go next

Templates plug into the send API by id and power newsletters; the full authoring reference is in the docs. Deciding between platforms? The comparison hub is the honest tour.

Questions, answered plainly

How is template injection prevented?

Structurally. The compile-time lint rejects every unescaped output — a template containing one will not compile, so there is no path where user data becomes markup. It's a property of the system, not a review checklist.

When does MJML compile?

At author time, never per-send. Sending renders data merges of prebuilt HTML, so a layout mistake is caught when you save the template, not discovered halfway through a campaign.

What can I constrain in a variable schema?

Types (string, email, number, boolean) plus required, maxLen, and enum per variable. A send whose data doesn't match the schema is refused at admission, before a message exists.

What's the Gmail 102KB warning about?

Gmail clips messages whose HTML exceeds roughly 102KB, hiding the rest behind a "view entire message" link — which usually hides your unsubscribe footer too. The editor warns at author time, while you can still shorten it.

See it for yourself

Sandbox keys run the real pipeline dry — real validation, real events, a hosted inbox, no email sent. Early access is onboarding now.