Skip to content
Mail Quilt
Esc
↑↓navigate↵open⌘Jpreview
On this page

Document format

The JSON format Mail Quilt saves emails in: Rows, Columns and Blocks, every Block's props, and which colors, links and sizes the editor accepts.

Mail Quilt saves an email as a JSON document: Rows hold Columns, and Columns hold Blocks. This is the document mq-change gives you and value takes, so you can store it anywhere JSON goes.

The editor never repairs a document. If one doesn’t match this format, the editor refuses it, keeps the email it had, and fires mq-error with the reason. A field the editor doesn’t know is refused too, rather than dropped, so your next save can’t lose it.

Example

{
  "kind": "blocks",
  "version": 1,
  "settings": {
    "width": 600,
    "backgroundColor": "#f4f4f5",
    "fontFamily": "Arial, Helvetica, sans-serif",
    "preheader": "Your order is on its way",
    "language": "en"
  },
  "rows": [
    {
      "id": "row-header",
      "props": { "backgroundColor": "#ffffff" },
      "columns": [
        {
          "id": "column-header",
          "width": 100,
          "props": {},
          "blocks": [
            {
              "id": "logo",
              "type": "image",
              "props": {
                "src": "https://example.com/logo.png",
                "alt": "Acme",
                "width": 120,
                "href": "https://example.com"
              }
            },
            {
              "id": "title",
              "type": "heading",
              "props": {
                "level": 1,
                "align": "center",
                "content": {
                  "type": "doc",
                  "content": [
                    {
                      "type": "paragraph",
                      "content": [
                        { "type": "text", "text": "Thanks, {{first_name}}" }
                      ]
                    }
                  ]
                }
              }
            }
          ]
        }
      ]
    },
    {
      "id": "row-body",
      "props": { "backgroundColor": "#ffffff" },
      "columns": [
        {
          "id": "column-message",
          "width": 67,
          "props": {},
          "blocks": [
            {
              "id": "message",
              "type": "text",
              "props": {
                "color": "rgb(39, 39, 42)",
                "fontSize": 16,
                "content": {
                  "type": "doc",
                  "content": [
                    {
                      "type": "paragraph",
                      "content": [
                        { "type": "text", "text": "Your order " },
                        {
                          "type": "text",
                          "text": "#{{order_id}}",
                          "marks": [{ "type": "bold" }]
                        },
                        { "type": "text", "text": " has shipped." },
                        { "type": "hardBreak" },
                        {
                          "type": "text",
                          "text": "Track it",
                          "marks": [
                            {
                              "type": "link",
                              "attrs": { "href": "{{tracking_url}}" }
                            }
                          ]
                        },
                        { "type": "text", "text": " any time." }
                      ]
                    }
                  ]
                }
              }
            }
          ]
        },
        {
          "id": "column-action",
          "width": 33,
          "props": {},
          "blocks": [
            {
              "id": "track",
              "type": "button",
              "props": {
                "label": "Track order",
                "href": "{{tracking_url}}",
                "backgroundColor": "#2563eb",
                "color": "#ffffff",
                "align": "center"
              }
            }
          ]
        }
      ]
    },
    {
      "id": "row-footer",
      "props": {},
      "columns": [
        {
          "id": "column-footer",
          "width": 100,
          "props": {},
          "blocks": [
            {
              "id": "gap",
              "type": "spacer",
              "props": { "height": 16 }
            },
            {
              "id": "rule",
              "type": "divider",
              "props": { "color": "#d4d4d8", "thickness": 1 }
            },
            {
              "id": "footer",
              "type": "text",
              "props": {
                "align": "center",
                "color": "#71717a",
                "fontSize": 12,
                "content": {
                  "type": "doc",
                  "content": [
                    {
                      "type": "paragraph",
                      "content": [
                        {
                          "type": "text",
                          "text": "Acme, 1 Main Street, Springfield"
                        }
                      ]
                    }
                  ]
                }
              }
            }
          ]
        }
      ]
    }
  ]
}

Document

Field Value
kind Always "blocks".
version The format version, a whole number. The current version is 1, and a newer one is refused. When the format changes, documents saved in an older version will be upgraded as they load.
settings The email settings.
rows A list of Rows, top to bottom. It can be empty.

Email settings

Field Value
width Required. The email’s width, a whole number of pixels, 1 or more.
backgroundColor Required. A color.
fontFamily Required. A CSS font list, such as "Arial, Helvetica, sans-serif". The settings panel offers Arial, Courier New, Georgia, Tahoma, Times New Roman and Verdana.
preheader Optional. The short text inboxes show after the subject.
language Optional. The email’s language. The settings panel only accepts a language code such as en or pt-BR, but a saved document loads with any text here, so an email with a bad value can still be opened and fixed.

Row

Field Value
id An ID.
props An object with one optional field, backgroundColor, a color.
columns A list of 1 to 4 Columns, left to right.

Column

Field Value
id An ID.
width A percentage of the Row, from 1 to 100, so a Column is at least 1% wide. The widths of a Row’s Columns add up to 100. The editor’s layouts use 100, 50/50, 33/67, 67/33, 33.33/33.33/33.34 and four Columns of 25. Other widths load, but the side panel shows none of its layouts as chosen for that Row.
props Always an empty object, {}.
blocks A list of Blocks, top to bottom. It can be empty.

Blocks

Every Block has an id, which is an ID, a type from the list below, and props for that type. A field marked optional can be left out, and the default is what the email shows without it.

Text

"type": "text"

Prop Value
content Required. Rich text.
align Optional. "left", "center" or "right". Defaults to "left".
color Optional. A color. Defaults to #000000.
fontSize Optional. A whole number of pixels from 1 to 200. Defaults to 16.

Heading

"type": "heading"

Prop Value
level Required. 1, 2 or 3.
content Required. Rich text. A Heading exports as one heading, however many lines it has.
align Optional. "left", "center" or "right". Defaults to "left".
color Optional. A color. Defaults to #000000.
fontSize Optional. A whole number of pixels from 1 to 200. Defaults to 32, 24 or 20 for levels 1, 2 and 3.

Image

"type": "image"

Prop Value
src Optional. The image’s URL, starting with https: or http:, or a Merge Tag such as {{logo_url}}. A new Image has none until someone adds one.
alt Optional. What a screen reader says, and what shows if images are off.
width Optional. A whole number of pixels, 1 or more. Without it, the image fills the Column, and it never grows wider than the Column.
href Optional. A link. A linked Image needs alt text that isn’t blank, because the alt text is all that names the link.

Button

"type": "button"

Prop Value
label Required. The Button’s text, as plain text that isn’t blank.
href Optional. A link.
backgroundColor Optional. A color that isn’t rgba(), because a see-through one would come out darker than it should. Defaults to #2563eb.
color Optional. The text’s color. Defaults to #ffffff.
align Optional. "left", "center" or "right". Defaults to "left".

Divider

"type": "divider"

Prop Value
color Optional. A color. Defaults to #d4d4d8.
thickness Optional. A whole number of pixels from 1 to 100. Defaults to 1.

Spacer

"type": "spacer"

Prop Value
height Optional. A whole number of pixels from 1 to 1000. Defaults to 24.

Rich text

Text and Heading Blocks store their text as a limited form of ProseMirror JSON, the format Tiptap uses too:

{
  "type": "doc",
  "content": [
    {
      "type": "paragraph",
      "content": [
        { "type": "text", "text": "Hello, " },
        { "type": "text", "text": "world", "marks": [{ "type": "bold" }] },
        { "type": "hardBreak" },
        {
          "type": "text",
          "text": "Visit us",
          "marks": [
            { "type": "link", "attrs": { "href": "https://example.com" } }
          ]
        }
      ]
    }
  ]
}
  • doc holds one or more paragraphs.
  • A paragraph has an optional content list, which leaves it empty when it’s missing.
  • A text item has text, which can’t be empty, and optional marks.
  • A hardBreak is a line break inside a paragraph. It can have marks too.
  • The marks are bold, italic, underline and link. A link mark has attrs.href, which is a link. A piece of text can’t have the same mark twice.

Nothing else is accepted, such as lists, headings inside text, colors or other marks.

Values

IDs

An ID is a string that isn’t empty. Every Row, Column and Block in a document needs a different one. The editor creates random IDs for what it adds.

Colors

A color is written as #rgb, #rrggbb, rgb() or rgba(), such as #f00, #ff0000, rgb(255, 0, 0) or rgba(255, 0, 0, 0.5). Named colors such as red are refused. These are the formats MJML accepts, so every email that opens can also be exported.

A link starts with https:, http:, mailto: or tel:, or with a Merge Tag such as {{cta_url}}. Other schemes, such as javascript:, and relative URLs are refused.

Merge Tags

A Merge Tag such as {{first_name}} can go anywhere in text, and at the start of a link or image URL. Mail Quilt keeps it as written in the exported HTML, so your sending tool can fill it in. In a URL that starts with a Merge Tag, no colon can come between the tag and the first /, ? or #, so {{base}}/path is a link but {{x}}javascript: isn’t.