# MJML editors and API

> Build responsive templates and campaigns with MJML, an alpha open to the Emailit team. The Visual and Code editors, stored documents, validation, Temple in MJML, live collaboration and the MJML API.

[MJML](https://mjml.io) is a markup language for responsive email. You write sections, columns and components such as `<mj-text>` and `<mj-button>`, and MJML compiles them to HTML that renders consistently across mail clients. In Emailit, MJML can be the source of a template or campaign: you write it in the dashboard editors or send it through the API, and Emailit validates it and compiles the HTML.

> **MJML is in alpha:** MJML is in alpha and open only to the Emailit team while we test it. Customer workspaces don't see the MJML editors yet, and API requests that create or change MJML templates, or call the MJML endpoints, return `403` with `error: "mjml_alpha"`. This page describes how MJML works so you can plan for it. Templates and campaigns made with MJML still send for everyone.

## Overview

- **Templates**: `editor: "mjml"` with the MJML in `source`. See [Create a template](/docs/api-reference/templates/create/).
- **Campaigns**: `content_type: "mjml"` with the MJML in `content`. See [Campaigns](#campaigns).
- **Automations**: the **Send email** step sends an MJML template like any other template. See [Automations](#automations).
- **MJML endpoints**: [Validate MJML](/docs/api-reference/mjml/validate/), [Render MJML](/docs/api-reference/mjml/render/) and [Retrieve the MJML reference](/docs/api-reference/mjml/reference/).

### Who can use MJML

During the alpha, MJML is available to Emailit platform admins only. A workspace Admin role isn't enough.

| Where | Emailit team | Everyone else |
| --- | --- | --- |
| Dashboard | MJML editors, MJML import, **Edit with AI** and live collaboration | No MJML editors. An MJML template or campaign shows a note, can't be opened in an editor, and still sends. |
| API | MJML templates, MJML campaigns and the MJML endpoints | `403` with `error: "mjml_alpha"`. A campaign's `content_type: "mjml"` stays a plain label, as before the alpha. See [Campaigns](#campaigns). |
| API keys | None. API keys belong to a workspace, not to a person. | `403` with `error: "mjml_alpha"` |
| MCP server | The MJML tools and the MJML parameters of the template and campaign tools | Not listed |

Everyone can still rename, publish, export, send and delete MJML templates and campaigns. Duplicating an MJML template creates a new MJML template, so it needs MJML access.

### MJML version

Emailit compiles all MJML with **MJML 5.4.1**, both on the server and in the editors' live preview. Validation checks tags, attributes and attribute values against that version. [Retrieve the MJML reference](/docs/api-reference/mjml/reference/) returns the version and every component and attribute it supports.

### Editors

The dashboard has two MJML editors. Both are versioned and both are `0.x` alphas.

| Editor | ID | Version | Description |
| --- | --- | --- | --- |
| MJML Visual Editor | `mjml-visual` | 0.2.0 (alpha) | Drag and drop on the rendered email, covering every MJML component and attribute |
| MJML Code Editor | `mjml-code` | 0.2.0 (alpha) | MJML with autocomplete, inline validation and a live desktop and mobile preview |

Both editors save a template with `editor: "mjml"`. The stored document records which editor, and which version of it, saved it last. Teammates can edit the same template or campaign at the same time. See [Editing together](#editing-together).

## Source formats

Wherever Emailit accepts MJML (a template's `source`, a campaign's `content` and the `source` field of the MJML endpoints), you can send any of these:

| Format | Example |
| --- | --- |
| MJML markup | A string starting with `<mjml>`. An XML declaration or leading comments are allowed. |
| MJML JSON | MJML's own JSON format, as an object or a JSON string: `{ "tagName": "mjml", "attributes": {}, "children": [ … ] }`. Ending tags such as `mj-text` carry their HTML in `content`. |
| Emailit MJML document | The envelope Emailit stores (below), as an object or a JSON string |

Anything else is rejected with `document.unrecognized`. Sources larger than 2 MB are rejected with `document.too-large`.

### The stored document

Emailit stores MJML in a versioned envelope. It's the template's `source` and the campaign's `content`:

```json
{
  "kind": "emailit/mjml",
  "schema_version": 1,
  "mjml_version": "5.4.1",
  "editor": "api",
  "editor_version": null,
  "format": "markup",
  "content": "<mjml>\n  <mj-body>\n    …\n  </mj-body>\n</mjml>"
}
```

| Field | Description |
| --- | --- |
| `kind` | Always `emailit/mjml`. |
| `schema_version` | Version of the envelope shape. Currently `1`. |
| `mjml_version` | The MJML release the content targets. Emailit sets it to the version it compiled with. |
| `editor` | What last wrote the document: `mjml-visual`, `mjml-code`, `ai` (**Edit with AI**) or `api` (the API, the MCP tools and file imports). |
| `editor_version` | Version of that editor, or `null`. |
| `format` | `markup`: `content` is MJML markup, kept as written, comments and formatting included. `json`: `content` is MJML JSON. |
| `content` | The MJML. |

What you send decides the format: markup is stored as `markup` and MJML JSON as `json`. Both are recorded as `editor: "api"`. An envelope you send keeps its `editor` and `editor_version`.

API responses return `source` as this envelope, serialized to a JSON string, and you can send it back unchanged. Responses also include an `mjml` object with the envelope's versions:

```json
"mjml": {
  "mjml_version": "5.4.1",
  "schema_version": 1,
  "editor": "api",
  "editor_version": null,
  "format": "markup"
}
```

The dashboard opens a document in the editor that saved it last. Documents written through the API open in the Code Editor when `format` is `markup` and in the Visual Editor when it's `json`.

## Compile and save

For MJML templates and campaigns, Emailit owns the HTML:

- On create and update, Emailit validates the MJML and compiles it. The compiled HTML is stored as the template's `html`, and any `html` you send is ignored.
- Sending uses the stored HTML. Temple tags stay in it and are rendered for each recipient at send time.
- Updating other fields only, such as `name` or `subject`, doesn't recompile.
- `text` isn't generated from the MJML. Send `text` yourself if you want a plain-text part.
- Switching an existing template to `editor: "mjml"` without sending `source` compiles the template's stored `source`, which must then be MJML.

The HTML is compiled when you save, so an existing template's HTML changes only when it's saved again.

```bash
curl https://api.emailit.com/v2/templates \
  -H "Authorization: Bearer your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Welcome",
    "alias": "welcome",
    "subject": "Welcome, {{first_name|\"there\"}}",
    "editor": "mjml",
    "source": "<mjml><mj-head><mj-title>Welcome</mj-title><mj-preview>Your account is ready</mj-preview></mj-head><mj-body><mj-section><mj-column><mj-text>Hi {{first_name|\"there\"}}, welcome aboard.</mj-text><mj-button href=\"{{activation_url}}\">Activate account</mj-button></mj-column></mj-section></mj-body></mjml>"
  }'
```

Then send it like any other template with [Send an email](/docs/api-reference/emails/send/): `"template": "welcome"` and a `variables` object.

## Validation

Every save runs the same checks as [Validate MJML](/docs/api-reference/mjml/validate/):

- XML syntax: unclosed or mismatched tags and broken attributes (`xml.*`)
- MJML structure, attributes and attribute values for MJML 5.4.1 (`mjml.*`)
- Temple syntax and balanced `{{#if}}` blocks (`temple.*`)
- The source format and versions (`document.*`), and the compiler itself (`compiler.*`)

Each finding is a diagnostic with a severity:

| Severity | Effect |
| --- | --- |
| `error` | The MJML is rejected. Templates and campaigns aren't saved. |
| `warning` | Saved. Probably a mistake: no `<mj-title>`, text outside a component, a conditional block that crosses components, or HTML over Gmail's 102 KB clipping limit. |
| `info` | Saved. A suggestion or a note: no `<mj-preview>`, an image without `alt`, or a document written for an older MJML version. |

A diagnostic has these fields. Fields that don't apply are left out.

| Field | Description |
| --- | --- |
| `severity` | `error`, `warning` or `info` |
| `code` | A stable machine-readable code, for example `mjml.invalid-child` |
| `message` | A readable explanation, often with a fix ("Did you mean color?") |
| `line`, `column` | 1-based position in the markup. Markup sources only. |
| `tag` | The element the diagnostic is about |
| `attribute` | The attribute, when there is one |
| `path` | Child-index path from the `<mjml>` root. `[0, 1]` is the second child of the first child. |

### Error response

A template or campaign save with error diagnostics returns `422`. For this source:

```xml
<mjml>
  <mj-body>
    <mj-section>
      <mj-column>
        <mj-text colour="#333333">Hi {{first_name|"there"}}</mj-text>
        <mj-button href="{{cta_url}}">{{#if trial}}Start trial</mj-button>
      </mj-column>
    </mj-section>
  </mj-body>
</mjml>
```

the response is:

```json
{
  "message": "The MJML is not valid.",
  "errors": {
    "source": [
      "Line 5: <mj-text> has no attribute colour. Did you mean color?",
      "Line 6: {{#if trial}} is never closed with {{/if}}."
    ]
  },
  "diagnostics": [
    {
      "severity": "error",
      "code": "mjml.unknown-attribute",
      "message": "<mj-text> has no attribute colour. Did you mean color?",
      "line": 5,
      "column": 18,
      "tag": "mj-text",
      "attribute": "colour",
      "path": [0, 0, 0, 0]
    },
    {
      "severity": "warning",
      "code": "mjml.missing-title",
      "message": "Add an <mj-title> to <mj-head>; clients and screen readers use it.",
      "line": 1,
      "column": 2,
      "tag": "mjml",
      "path": []
    },
    {
      "severity": "info",
      "code": "mjml.missing-preview",
      "message": "Add an <mj-preview> to control the inbox preview text.",
      "line": 1,
      "column": 2,
      "tag": "mjml",
      "path": []
    },
    {
      "severity": "error",
      "code": "temple.unclosed-if",
      "message": "{{#if trial}} is never closed with {{/if}}.",
      "line": 6,
      "column": 39,
      "tag": "mj-button",
      "path": [0, 0, 0, 1]
    }
  ]
}
```

- `errors.source` (`errors.content` for campaigns) lists up to five error messages, with their line when it's known.
- `diagnostics` lists every diagnostic, including warnings and info.
- A missing or empty source returns `422` with `"message": "Validation failed"`, `"errors": { "source": ["The source field is required for MJML."] }` and an empty `diagnostics` array.

[Validate MJML](/docs/api-reference/mjml/validate/) runs the same checks without saving and returns `200` with `valid: false` instead of `422`.

### Diagnostic codes

**XML** (markup sources)

| Code | Severity | Meaning |
| --- | --- | --- |
| `xml.unclosed-tag` | error | An element is never closed. |
| `xml.unexpected-closing-tag` | error | A closing tag matches no open element. |
| `xml.malformed-closing-tag` | error | A closing tag can't be read. |
| `xml.unterminated-tag` | error | An opening tag has no closing `>`. |
| `xml.unterminated-attribute` | error | An attribute value has no closing quote. |
| `xml.missing-attribute-value` | error | `name=` has no value. |
| `xml.invalid-attribute` | error | An unexpected character inside a tag. |
| `xml.duplicate-attribute` | error | The same attribute twice on one element. The first one is used. |
| `xml.unterminated-comment` | error | A comment has no `-->`. |
| `xml.unterminated-cdata` | error | A CDATA section has no `]]>`. |
| `xml.unexpected-character` | error | A bare `<` outside an ending tag. |
| `xml.multiple-roots` | error | More than one root element. |
| `xml.text-outside-root` | error | Text outside `<mjml>`. |
| `mjml.missing-root` | error | The document is empty. |
| `xml.unquoted-attribute` | warning | An attribute value without quotes. |
| `xml.stray-text` | warning | Text between elements, outside any content component. MJML ignores it. |
| `xml.unexpected-declaration` | warning | A declaration after `<mjml>` starts. |

**MJML**

| Code | Severity | Meaning |
| --- | --- | --- |
| `mjml.unknown-tag` | error | Not an MJML 5.4.1 element, with a "did you mean" suggestion when one is close. |
| `mjml.unknown-attribute` | error | The element has no such attribute. A warning on `<mjml>` itself. |
| `mjml.invalid-attribute-value` | error | The wrong kind of value: not a color, a unit or an allowed value. |
| `mjml.invalid-child` | error | The element isn't allowed inside its parent. |
| `mjml.invalid-root` | error | The root element isn't `<mjml>`. |
| `mjml.missing-body` | error | No `<mj-body>`. |
| `mjml.duplicate-body` | error | More than one `<mj-body>`. |
| `mjml.include-not-supported` | error | `<mj-include>` isn't supported. |
| `mjml.missing-attribute` | error or warning | A required attribute is missing. An error for `<mj-font>` `name` and `href`, `<mj-class>` `name`, `<mj-selector>` `path` and `<mj-html-attribute>` `name`. A warning for an image `src` and `<mj-breakpoint>` `width`. |
| `mjml.missing-title` | warning | No `<mj-title>` in `<mj-head>`. |
| `mjml.empty-title` | warning | `<mj-title>` is empty. |
| `mjml.duplicate-head` | warning | More than one `<mj-head>`. |
| `mjml.ignored-content` | warning | Text inside an element that doesn't take content. |
| `mjml.ignored-children` | warning | Child elements inside an element that only takes content. |
| `mjml.column-widths` | warning | Column widths in a section or group add up to more than 100%. |
| `mjml.unknown-social-network` | warning | An `<mj-social-element>` name with no built-in icon and no `src`. |
| `mjml.script` | warning | `<script>` in content. Mail clients strip it. |
| `mjml.missing-preview` | info | No `<mj-preview>`. |
| `mjml.missing-alt` | info | An `<mj-image>` without `alt`. |
| `mjml.button-without-link` | info | An `<mj-button>` without `href`. |

**Temple**

| Code | Severity | Meaning |
| --- | --- | --- |
| `temple.unclosed-if` | error | `{{#if}}` without `{{/if}}`. |
| `temple.endif-without-if` | error | `{{/if}}` without `{{#if}}`. |
| `temple.else-without-if` | error | `{{else}}` outside a block. |
| `temple.duplicate-else` | error | Two `{{else}}` in one block. |
| `temple.unclosed-expression` | error | `{{` without `}}`. |
| `temple.empty-expression` | error | `{{ }}`. |
| `temple.empty-condition` | error | `{{#if}}` without a variable. |
| `temple.malformed-else` | error | `{{else}}` written with spaces or arguments. |
| `temple.unsupported-block` | error | A block other than `{{#if}}`, such as `{{#each}}`. |
| `temple.unsupported-syntax` | error | Triple braces `{{{…}}}`, partials `{{> …}}` or comments `{{! …}}`. |
| `temple.invalid-variable` | warning | A variable that isn't a valid path. |
| `temple.invalid-condition` | warning | A condition that isn't a variable path. Comparisons aren't supported. |
| `temple.block-crosses-components` | warning | A block that opens in one component and closes in another. |

**Document and compiler**

| Code | Severity | Meaning |
| --- | --- | --- |
| `document.empty` | error | The source is empty. |
| `document.unrecognized` | error | Not MJML markup, MJML JSON or an Emailit MJML document. |
| `document.invalid-json` | error | The source looks like JSON but doesn't parse. |
| `document.invalid-node` | error | MJML JSON with a malformed node. |
| `document.too-large` | error | The source is larger than 2 MB. |
| `document.unsupported-schema` | error | The envelope's `schema_version` is newer than Emailit reads. |
| `document.unsupported-mjml-version` | error | The document's MJML version can't be compiled. See [Versions and upgrades](#versions-and-upgrades). |
| `document.assumed-mjml-version` | info | The envelope has no `mjml_version`, so the current version is assumed. |
| `document.mjml-upgraded` | info | Written for another MJML version and compiled with 5.4.1. |
| `compiler.failed` | error | MJML couldn't render the document. |
| `compiler.gmail-clipping` | warning | The HTML is larger than 102 KB, so Gmail clips it. |

## Temple in MJML

[Temple](/docs/templates/temple/) tags pass through MJML compilation unchanged. Emailit renders them for each recipient at send time, on the compiled HTML.

### Variables in content and attributes

Variables work in content and in any attribute:

```xml
<mj-text>Hi {{first_name|"there"}},</mj-text>
<mj-button href="{{activation_url}}">Activate your account</mj-button>
<mj-image src="{{logo_url}}" alt="{{company|'Acme'}}" />
<mj-section background-color="{{brand_color|'#ffffff'}}">
```

- Inside an attribute, write defaults with single quotes: `href="{{url|'https://example.com'}}"`.
- Attribute values that contain Temple aren't type-checked, because the value is only known at send time. Make sure the variable holds a value that's valid for the attribute, such as a color for `background-color`.
- Values are inserted as they are, without HTML escaping.

### Conditional blocks

Inside one component, put the block in its content:

```xml
<mj-text>{{#if plan}}You are on the {{plan}} plan.{{else}}You are on the free plan.{{/if}}</mj-text>
```

To show or hide whole components, put the block tags in `<mj-raw>` siblings:

```xml
<mj-raw>{{#if vip}}</mj-raw>
<mj-section background-color="#fef3c7">
  <mj-column>
    <mj-text>Your VIP perks are ready.</mj-text>
  </mj-column>
</mj-section>
<mj-raw>{{/if}}</mj-raw>
```

Bare block tags between components are converted to `<mj-raw>` when the markup is parsed, so this is the same thing:

```xml
{{#if vip}}
<mj-section background-color="#fef3c7">
  …
</mj-section>
{{/if}}
```

Other text between components is ignored by MJML and reported as `xml.stray-text`.

- Blocks must balance across the whole document. An unclosed or extra tag is an error.
- Open and close each block inside one component's content, or among the `<mj-raw>` siblings of one parent. A block that opens in one component and closes in another gets a `temple.block-crosses-components` warning, because hiding it would cut through the HTML structure.
- Blocks can be nested.

### Not supported

- `<mj-include>` is rejected with `mjml.include-not-supported`. Paste the included MJML into the document.
- Tags and attributes that MJML 5.4.1 doesn't define are errors.
- Temple has no loops, helpers, partials, comments, triple braces or comparisons. See [Temple](/docs/templates/temple/).

### Variables by channel

The same MJML template can be sent from several places, and each one provides different variables:

| Sent from | Variables |
| --- | --- |
| API [Send an email](/docs/api-reference/emails/send/) with `template` | The `variables` object you pass |
| Automation **Send email** step | Contact automations: the contact's fields at the top level (`{{first_name}}`, `{{email}}`), custom fields as `{{cf.<key>}}` or `{{custom_fields.<key>}}`, plus `{{contact.*}}`, `{{payload.*}}` and `{{meta.*}}` |
| MJML campaigns | `{{first_name}}`, `{{last_name}}`, `{{email}}`, `{{unsubscribe_url}}`, `{{cf.<key>}}`, and the same fields under `{{contact.*}}` |

The editors insert custom fields as `{{cf.<key>}}`, which works in MJML campaigns and in automations. For API sends, pass the variables yourself.

To preview a recipient's version, call [Render MJML](/docs/api-reference/mjml/render/) with `variables`.

## Campaigns

A campaign with `content_type: "mjml"` stores its MJML in `content`, in any of the [source formats](#source-formats), and Emailit compiles its `html`. As with templates, any `html` you send is ignored. Invalid MJML returns `422` with `errors.content` and `diagnostics`. Sending an empty `content` clears both the content and the HTML. Campaign responses include the same `mjml` object as templates.

MJML campaigns render the subject, HTML and text with **Temple** for each recipient, on test sends too. These variables are available:

| Variable | Value |
| --- | --- |
| `{{first_name}}` | Contact first name |
| `{{last_name}}` | Contact last name |
| `{{email}}` | Contact email address |
| `{{unsubscribe_url}}` | Unsubscribe link for this contact and campaign |
| `{{cf.<key>}}` | Contact custom field, for example `{{cf.company}}` |
| `{{contact.first_name}}`, `{{contact.cf.<key>}}`, … | The same fields under `contact` |

Empty contact fields count as missing, so defaults apply: `{{first_name|"there"}}` renders `there` for a contact without a first name. Keep a `{{unsubscribe_url}}` link in the footer of marketing email.

Classic campaigns (HTML, text and the other editors) keep the fixed merge tags:

| | Classic campaigns | MJML campaigns |
| --- | --- | --- |
| Engine | Fixed merge tags | Temple |
| `{{#if}}` … `{{else}}` … `{{/if}}` | Not processed | Supported |
| Defaults such as `{{first_name\|"there"}}` | Not processed | Supported. Empty fields count as missing. |
| Letter case | `{{FIRST_NAME}}` works | Paths are case-sensitive |
| Unknown tags | Left in the message as written | Rendered as empty |

In the dashboard, starting a campaign from an MJML template copies the template's MJML document into the campaign.

### Without MJML access

During the alpha, Emailit compiles campaign MJML only for the Emailit team. For everyone else, including API keys, `content_type: "mjml"` stays the plain label it was before the alpha: `content` is stored as you send it, you send the compiled HTML in `html`, and sends use the classic merge tags. Starting a campaign from an MJML template copies the template's HTML into an HTML campaign.

## Automations

The **Send email** step references a template by ID (`tem_…`). MJML templates work like any other: the step sends the template's compiled HTML and renders Temple with the automation's variables. See [Automation emails](/docs/templates/temple/#automation-emails).

In the step settings, **Design a new email** creates an MJML template from a starter design, selects it for the step and opens it in the Visual Editor. **Edit email** opens the selected MJML template. Edits change the template itself, so every step and API call that uses the template gets them. Without MJML access, the step shows a link to the template instead.

## Editing together

Everyone who opens the same saved MJML template or campaign edits one shared draft in real time, in either editor:

- **Presence**: the header shows who else is editing and what they're doing. In the Visual Editor you see their selections, their cursors and a short highlight in their color where they change something. **Layers** shows who has a component selected. Select someone's avatar to jump to their selection.
- **Edits merge**: changes to different components, attributes or parts of a text combine instead of overwriting each other. While someone types into a text on the canvas, that text is locked for others.
- **Code Editor**: your changes merge into the shared draft as you type. Changes from others appear in your code once you pause typing, so your cursor doesn't jump. If your code has a syntax error, they wait until you fix it.
- **Undo and redo** only undo your own changes.
- **Saving**: there's one **Save** for everyone. The header shows unsaved changes for the whole draft, and who saved last. Sending always uses the saved version.
- **The draft is kept**: closing the editor or losing the connection doesn't lose changes. They stay in the shared draft and sync when you're back online. Reopening the editor restores unsaved changes, and you can discard them to go back to the saved version.
- **Saved elsewhere**: when the template or campaign is saved outside the editor (the API, MCP or **Edit with AI**) while it's open, a draft without unsaved changes switches to the saved version. A draft with unsaved changes keeps them and offers **Load saved version** or **Keep this draft**.
- **Deleted**: if the template or campaign is deleted, or is no longer MJML, while you edit, the editor tells you and lets you copy the MJML.

Editing together needs saved, valid MJML. A template or campaign whose MJML doesn't parse, or one that isn't saved as MJML yet, opens without it: each person edits alone and the last save wins. The editor says so in a banner. In a suspended workspace the editors are read-only.

## Versions and upgrades

Emailit compiles with one MJML version at a time, currently 5.4.1. Each stored document records the `mjml_version` it targets, and Emailit checks it whenever the document is compiled:

| Document `mjml_version` | Result |
| --- | --- |
| 5.4.1 | Compiled as is |
| Missing | 5.4.1 is assumed (`document.assumed-mjml-version`, info) |
| Another 5.x release | Compiled with 5.4.1 (`document.mjml-upgraded`, info) |
| 4.x | Migrated to MJML 5, then compiled with 5.4.1 (`document.mjml-upgraded`, info). MJML 4 and 5 share the same components and attributes; the HTML output differs slightly. |
| 3.x or older | Rejected with `document.unsupported-mjml-version` |
| A newer major version | Rejected with `document.unsupported-mjml-version` |

A document whose `schema_version` is newer than Emailit reads is rejected with `document.unsupported-schema`. Saving stores the document with the current `mjml_version` and `schema_version`. Check the preview after an upgrade.

The editors show their version and changelog. When a document was saved with a newer editor version than the page you have open, the editor asks you to reload.

## MJML for AI agents

[Retrieve the MJML reference](/docs/api-reference/mjml/reference/) gives tooling and AI models what they need to write valid MJML for Emailit: every component with its allowed parents, children and attributes (type and default), the Temple guide, authoring rules and a compact plain-text `reference_text` for prompts.

On the [MCP server](/docs/mcp/), sessions of the Emailit team also get these tools in the `templates` toolset:

| Tool | Description |
| --- | --- |
| `get-mjml-reference` | The reference as text: the MJML and editor versions, authoring rules, the Temple guide and the component reference. |
| `validate-mjml` | [Validate MJML](/docs/api-reference/mjml/validate/): `valid` and the diagnostics. |
| `render-mjml` | [Render MJML](/docs/api-reference/mjml/render/): the compiled HTML and, with `variables`, `rendered_html`. |
| `create-template`, `update-template` | Also take `editor: "mjml"` and `source`, the MJML markup or MJML JSON as a string. Emailit compiles the HTML. |
| `create-campaign`, `update-campaign` | Also take `content_type: "mjml"` with the MJML in `content`. |

When a save fails validation, the tool error lists the error and warning diagnostics with their line numbers, so the agent can fix the source. A typical flow: read the reference, write the MJML, call `validate-mjml` until `valid` is `true`, save with `create-template`, then check a recipient's version with `render-mjml`. Other sessions don't see these tools or parameters.

## In the dashboard

- **Templates**: create a template and choose **MJML Visual Editor (Alpha)** or **MJML Code Editor (Alpha)**.
- **Visual Editor**: drag and drop on the rendered email, a layers tree, a property panel for every MJML attribute, document settings (head, fonts, styles and default attributes), Temple variables in any property and conditional blocks around components.
- **Code Editor**: autocomplete for MJML tags, attributes, values and Temple, inline validation with quick fixes, and formatting.
- **Both editors**: a live desktop and mobile preview compiled in the browser with MJML 5.4.1, a preview with sample data (Temple rendered), a problems list and an AI assistant when it's enabled. You can switch between Visual and Code on the same document; switching to Visual needs code without syntax errors. Saving is blocked while the MJML has errors.
- **Editing together**: teammates who open the same template or campaign edit it in real time. See [Editing together](#editing-together).
- **Edit with AI**: describe a change to an MJML template or campaign without opening the editor.
- **Import**: an `.mjml` file, an MJML JSON or Emailit MJML document `.json` file, or a ZIP with `template.mjml` and an `images/` folder at the root.
- **Export**: MJML (markup), MJML JSON (the stored document), HTML, or a ZIP with `template.mjml`, `template.html` and `images/`. Export works for everyone.
- **Campaigns**: choose the MJML Visual or Code Editor for the campaign content.
- **Automations**: design the email of a **Send email** step in place. See [Automations](#automations).

## Related

- [Create and edit templates](/docs/templates/editors/)
- [Temple template language](/docs/templates/temple/)
- [MJML API reference](/docs/api-reference/mjml/)
- [Import and export](/docs/templates/import-export/)

---
Source: https://emailit.com/docs/templates/mjml/
