# The email format

Every Flyanote email is one JSON document. The editor, the API, AI drafts, and the compiler all read and write the same format, so an email you build in one works in the others. The document says what the email holds; your brand kit says how it looks. We turn the two into HTML that works across email clients.

This page describes version `1.1`. Versions before 1.0 are drafts and may still change. The tables, limits, and errors below are generated from the format itself.

## On this page

* [The schema](https://flyanote.com/docs/email-format#schema-urls)
* [The document](https://flyanote.com/docs/email-format#top-level)
* [Sections](https://flyanote.com/docs/email-format#sections)
* [Blocks](https://flyanote.com/docs/email-format#blocks)
* [Text](https://flyanote.com/docs/email-format#text-model)
* [Values and styles](https://flyanote.com/docs/email-format#values)
* [Links](https://flyanote.com/docs/email-format#links)
* [Merge tags and Liquid](https://flyanote.com/docs/email-format#liquid)
* [Ids](https://flyanote.com/docs/email-format#ids)
* [Limits](https://flyanote.com/docs/email-format#limits)
* [Versions](https://flyanote.com/docs/email-format#versioning)
* [Errors](https://flyanote.com/docs/email-format#errors)

## The schema

The format is published as a JSON Schema (draft 2020-12). Point your validator or your editor at it, and set `schema_version` in every document. The schema checks a document's structure: its fields, types, lengths, and patterns. The rules across fields, such as unique ids or a table's cells, run when you save or validate, and each refusal is one of the error codes below, so a document the schema accepts can still be refused. A few checks need your account, such as whether a brand kit token or an uploaded image exists.

* <https://flyanote.com/schemas/email/v1.0.json>
* <https://flyanote.com/schemas/email/v1.1.json>
* <https://flyanote.com/schemas/email/v1.json>
* <https://flyanote.com/schemas/llm/v1.0.draft.json>
* <https://flyanote.com/schemas/llm/v1.0.patch.json>
* <https://flyanote.com/schemas/llm/v1.1.draft.json>
* <https://flyanote.com/schemas/llm/v1.1.patch.json>
* <https://flyanote.com/schemas/llm/v1.draft.json>
* <https://flyanote.com/schemas/llm/v1.patch.json>
* <https://flyanote.com/schemas/index.json>

Under `llm/` are the flatter profiles an AI model writes, a whole draft (`.draft`) or a change to one (`.patch`). They have versions of their own, and each says which version of the format it expands into. `index.json` lists every version and its files.

## The document

A document has a `schema_version`, a `meta` object, optional `settings`, and its `sections`, top to bottom. Every object is closed: a property the format doesn’t have is an error, never ignored. Names are snake\_case.

### The document

An email, as the editor, the API, and AI write it. This schema checks its structure: shapes, types, lengths, and patterns. The rules across properties (ids, widths, a table’s cells, the footer, loop scopes, and Liquid) are the API’s validation, whose error codes are listed at https://flyanote.com/docs/email-format, so a document this schema accepts may still be refused.

| Property                   | Takes                                                                        |
| -------------------------- | ---------------------------------------------------------------------------- |
| schema\_version (required) | "1.1"                                                                        |
| meta (required)            | Meta                                                                         |
| settings                   | Settings                                                                     |
| sections (required)        | a list of up to 100: ColumnsSection, GridSection, HeroSection or HtmlSection |

### meta

| Property            | Takes                                           |
| ------------------- | ----------------------------------------------- |
| subject (required)  | text, up to 200 characters                      |
| preheader           | text, at least 1 and up to 200 characters       |
| lang (required)     | text, up to 35 characters                       |
| dir                 | one of "ltr" or "rtl"                           |
| category (required) | one of "transactional" or "marketing"           |
| variables           | a list of up to 500: text, up to 300 characters |
| provenance          | Provenance                                      |

`category` sets the rules. Marketing mail gets the footer the law requires, with your organization’s name, its postal address, and an unsubscribe link, plus the unsubscribe headers inboxes look for. Transactional mail, like a receipt, has no unsubscribe link, and shows your name and address unless its footer block turns them off. `variables` is worked out on every save from the merge tags the email uses; anything you send there is replaced.

### settings

| Property            | Takes                                                                                                                                                                                                                                                                            |
| ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| width               | a whole number from 480 to 700                                                                                                                                                                                                                                                   |
| brand               | text. The brand kit this email uses, by its id (kit\_…). Absent, the email follows the account’s default brand, whichever that is when it’s built.                                                                                                                               |
| palette             | text. One of the brand kit’s palettes, by id. Absent, main.                                                                                                                                                                                                                      |
| theme               | text. One of the palette’s themes: light (its colors as they stand), dark (its dark values), or the id of one of its themes. Absent, light.                                                                                                                                      |
| kit\_version        | a whole number from 1 to 9,007,199,254,740,991\. The version of the brand kit this email was made against: one pin covers the brand, its palette, and its theme. Needs brand, since a version means nothing without its kit. Absent, the email follows the kit’s newest version. |
| background          | [Color](https://flyanote.com/docs/email-format#value-color)                                                                                                                                                                                                                      |
| content\_background | [Color](https://flyanote.com/docs/email-format#value-color)                                                                                                                                                                                                                      |
| color               | [Color](https://flyanote.com/docs/email-format#value-color)                                                                                                                                                                                                                      |
| link\_color         | [Color](https://flyanote.com/docs/email-format#value-color)                                                                                                                                                                                                                      |
| link\_underline     | true or false                                                                                                                                                                                                                                                                    |
| font\_heading       | [Font](https://flyanote.com/docs/email-format#value-font)                                                                                                                                                                                                                        |
| font\_body          | [Font](https://flyanote.com/docs/email-format#value-font)                                                                                                                                                                                                                        |
| mobile              | an object with h1, h2, h3, body, button, footer, side\_padding and buttons\_full\_width                                                                                                                                                                                          |
| dark                | an object with background, content\_background, color and link\_color                                                                                                                                                                                                            |

## Sections

A section is a full-width band of the email. Its `kind` says what it is: columns side by side, a grid of items, a hero over a background image, or your own HTML.

### columns section

| Property           | Takes                                            |
| ------------------ | ------------------------------------------------ |
| kind (required)    | "columns"                                        |
| id (required)      | an id                                            |
| label              | text, at least 1 and up to 60 characters         |
| provenance         | Provenance                                       |
| columns (required) | a list of up to 4: Column                        |
| widths             | a list of up to 4: a whole number from 15 to 100 |
| mobile             | an object with stack and reverse                 |
| style              | SectionStyle                                     |

### grid section

| Property            | Takes                                                           |
| ------------------- | --------------------------------------------------------------- |
| kind (required)     | "grid"                                                          |
| id (required)       | an id                                                           |
| label               | text, at least 1 and up to 60 characters                        |
| provenance          | Provenance                                                      |
| per\_row (required) | one of 2, 3 or 4                                                |
| mobile              | an object with per\_row                                         |
| gap                 | [Spacing](https://flyanote.com/docs/email-format#value-spacing) |
| items (required)    | a list of up to 24: GridItem                                    |
| style               | SectionStyle                                                    |

### hero section

One column over a background image.

| Property          | Takes                                                                                         |
| ----------------- | --------------------------------------------------------------------------------------------- |
| kind (required)   | "hero"                                                                                        |
| id (required)     | an id                                                                                         |
| label             | text, at least 1 and up to 60 characters                                                      |
| provenance        | Provenance                                                                                    |
| blocks (required) | a list of up to 8: TextBlock, ImageBlock, ButtonBlock, DividerBlock, SpacerBlock or LogoBlock |
| style (required)  | HeroStyle                                                                                     |

### html section

A whole section of the customer’s own HTML with Liquid.

| Property        | Takes                                         |
| --------------- | --------------------------------------------- |
| kind (required) | "html"                                        |
| id (required)   | an id                                         |
| label           | text, at least 1 and up to 60 characters      |
| provenance      | Provenance                                    |
| html (required) | text, at least 1 and up to 100,000 characters |
| style           | HtmlSectionStyle                              |

### A column

| Property          | Takes                                                                                                                                                        |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| id (required)     | an id                                                                                                                                                        |
| blocks (required) | a list of up to 50: TextBlock, ImageBlock, ButtonBlock, DividerBlock, SpacerBlock, SocialBlock, LogoBlock, FooterBlock, HtmlBlock, RepeatBlock or TableBlock |
| style             | ColumnStyle                                                                                                                                                  |

## Blocks

Blocks hold the content, and a block’s `type` says what it is. Columns take every block. Hero sections take `text`, `image`, `button`, `divider`, `spacer` and `logo`. Grid items and repeats take `text`, `image`, `button`, `divider` and `spacer`. A repeat can’t hold another repeat. The footer block goes in the last section, at most once.

### text block

| Property         | Takes                                                                                |
| ---------------- | ------------------------------------------------------------------------------------ |
| type (required)  | "text"                                                                               |
| id (required)    | an id                                                                                |
| provenance       | Provenance                                                                           |
| lines (required) | a list of up to 200: ParagraphLine, HeadingLine, BulletLine, NumberLine or QuoteLine |
| style            | TextStyle                                                                            |

### image block

| Property        | Takes                                                     |
| --------------- | --------------------------------------------------------- |
| type (required) | "image"                                                   |
| id (required)   | an id                                                     |
| provenance      | Provenance                                                |
| src (required)  | [Src](https://flyanote.com/docs/email-format#value-src)   |
| alt             | text, up to 300 characters                                |
| decorative      | true                                                      |
| width           | text                                                      |
| href            | [Href](https://flyanote.com/docs/email-format#value-href) |
| style           | ImageStyle                                                |

### button block

| Property         | Takes                                                     |
| ---------------- | --------------------------------------------------------- |
| type (required)  | "button"                                                  |
| id (required)    | an id                                                     |
| provenance       | Provenance                                                |
| label (required) | text, up to 80 characters                                 |
| href (required)  | [Href](https://flyanote.com/docs/email-format#value-href) |
| variant          | one of "fill" or "outline"                                |
| width            | text                                                      |
| style            | ButtonStyle                                               |

### divider block

| Property        | Takes        |
| --------------- | ------------ |
| type (required) | "divider"    |
| id (required)   | an id        |
| provenance      | Provenance   |
| style           | DividerStyle |

### spacer block

| Property          | Takes                                                           |
| ----------------- | --------------------------------------------------------------- |
| type (required)   | "spacer"                                                        |
| id (required)     | an id                                                           |
| provenance        | Provenance                                                      |
| height (required) | [Spacing](https://flyanote.com/docs/email-format#value-spacing) |
| style             | SpacerStyle                                                     |

### social block

| Property            | Takes                                               |
| ------------------- | --------------------------------------------------- |
| type (required)     | "social"                                            |
| id (required)       | an id                                               |
| provenance          | Provenance                                          |
| networks (required) | a list of up to 12: an object with network and href |
| icon\_style         | one of "color", "dark" or "light"                   |
| icon\_size          | text                                                |
| style               | SocialStyle                                         |

### logo block

| Property        | Takes                                                     |
| --------------- | --------------------------------------------------------- |
| type (required) | "logo"                                                    |
| id (required)   | an id                                                     |
| provenance      | Provenance                                                |
| width           | text                                                      |
| href            | [Href](https://flyanote.com/docs/email-format#value-href) |
| style           | LogoStyle                                                 |

### footer block

| Property        | Takes                             |
| --------------- | --------------------------------- |
| type (required) | "footer"                          |
| id (required)   | an id                             |
| provenance      | Provenance                        |
| note            | a list of up to 10: ParagraphLine |
| show\_social    | true or false                     |
| show\_address   | true or false                     |
| style           | FooterStyle                       |

### html block

The customer’s own HTML with Liquid, linted. Not editable visually.

| Property        | Takes                                        |
| --------------- | -------------------------------------------- |
| type (required) | "html"                                       |
| id (required)   | an id                                        |
| provenance      | Provenance                                   |
| html (required) | text, at least 1 and up to 50,000 characters |

### repeat block

| Property          | Takes                                                                               |
| ----------------- | ----------------------------------------------------------------------------------- |
| type (required)   | "repeat"                                                                            |
| id (required)     | an id                                                                               |
| provenance        | Provenance                                                                          |
| over (required)   | text                                                                                |
| as (required)     | text                                                                                |
| limit             | a whole number from 1 to 200                                                        |
| blocks (required) | a list of up to 10: TextBlock, ImageBlock, ButtonBlock, DividerBlock or SpacerBlock |

### table block

| Property           | Takes                                             |
| ------------------ | ------------------------------------------------- |
| type (required)    | "table"                                           |
| id (required)      | an id                                             |
| provenance         | Provenance                                        |
| caption            | text, at least 1 and up to 300 characters         |
| columns (required) | a list of up to 4: an object with align and width |
| header (required)  | a list of up to 4: Cell                           |
| rows (required)    | a list of up to 200: TableRow                     |
| mobile             | an object with layout                             |
| style              | TableStyle                                        |

## Text

A text block is a list of `lines`, and each line is a list of `spans`. A line’s `kind` is one of `p`, `h1`, `h2`, `h3`, `bullet`, `number` and `quote`. Lists are flat: consecutive `bullet` and `number` lines make one list, a line at `level` 2 nests under the line before it, and `start` begins a new numbered list. Consecutive `quote` lines make one quote. A line with no spans is an empty line.

A span is either text or a field. A text span’s `text` may hold `\n` for a line break, but never `{{` or `{%`. A field span fills in a value, such as `contact.first_name`, with a `fallback` for when it’s empty and an optional `format`: `date`, `datetime`, `number` and `money`. `money` needs a `currency` (an ISO 4217 code, or a path to one) and reads the value as a whole number of that currency’s minor units: `1999` with `USD` shows $19.99\. Both kinds take a `link`, a `color`, a `highlight`, a `size`, and `marks`: `bold`, `italic`, `underline`, `strike`, `sup` and `sub`.

When you send `{{ contact.first_name }}` in text, we turn it into a field span for you. Any other Liquid in text is an error.

## Values and styles

Most styles take a token from your brand kit, such as `brand.primary` or `space.md`, or an exact value, such as a `#rrggbb` color or `24px`. A token follows your kit when it changes; an exact value stays as written. Leave a style out to use the kit’s default. A style that isn’t set on an element comes from its column, then its section, then `settings`, then your kit.

Every element with a color also takes `dark`, exact colors for dark mode. Without them, we work out a readable dark version of each color.

`Color`

A palette token (brand.primary, text.default, extra.<slug>) or an exact color, #rrggbb. No alpha.

`Font`

font.heading, font.body, or a font family from the catalog. The catalog is checked in context.

`PhoneFontSize`

A size on phones: whole pixels from 12px to 72px.

`PhonePadding`

Side padding on phones, whole pixels from 0px to 200px: the left and right padding of each block inside that sets neither side itself.

`Hex`

An exact color, #rrggbb. Dark overrides take only these.

`Href`

https:, http:, mailto:, tel:, asset:ast\_<id>, kit:website, kit:links/<key>, kit:social/<network>, system:unsubscribe, system:preferences, system:view\_online, or a merge tag.

`FontSize`

A size token (size.body, size.h2) or whole pixels from 10px to 72px.

`Path`

A data path, such as contact.first\_name, data.order.total, or item.name.

`Spacing`

A space token (space.md) or whole pixels from 0px to 200px.

`Src`

asset:ast\_<id>, an http(s):// address, or a merge tag.

`Radius`

A radius token (radius.md) or whole pixels from 0px to 200px.

## Links

A link is one string, and its scheme says where it goes: a web page (`https:`), an email address (`mailto:`), a phone number (`tel:`), an uploaded file (`asset:` and the file’s id, `asset:ast_…`), a link saved in your brand kit (`kit:website`, `kit:links/donate`, `kit:social/instagram`), or one we fill in (`system:unsubscribe`, `system:preferences`, `system:view_online`). A kit link follows your kit when it changes.

* `http:` works, with a warning: use `https:` where you can.
* `javascript:`, `data:`, `file:`, `vbscript:`, and relative links are errors.
* The unsubscribe and preferences links are for marketing mail only.
* A link can be a merge tag, such as `https://example.com/orders/{{ data.order.id }}`. A link that’s wholly a merge tag is checked again once it’s filled in.

## Merge tags and Liquid

Merge tags fill in each person’s details. They read from `contact`, `account`, `message`, `campaign`, `data` (what you send with a transactional email), `links`, `now`, and, in your own HTML, `brand`. Where they may go:

| Where                                                                             | What it takes                                                 |
| --------------------------------------------------------------------------------- | ------------------------------------------------------------- |
| meta.subject, meta.preheader, an image’s alt, a button’s label, a table’s caption | Output tags: {{ path \| filter: value }}, no {% %} tags       |
| href, src, background\_image.src                                                  | Output tags, for links and images that differ for each person |
| Text spans                                                                        | None: use a field span                                        |
| A field span’s field and currency, a repeat’s over                                | A path, such as data.order.items, not Liquid                  |
| Your own HTML, in an html block or section                                        | Liquid, with brand too                                        |
| Styles, ids, settings, and fallbacks                                              | Never                                                         |

A repeat runs its blocks once for each item in a list, naming each one `as`: a receipt’s line items, say. That name can’t be one of the namespaces above. Transactional mail may read only `links.view_in_browser_url` from `links`.

## Ids

Every section, column, grid item, and block has an `id`: `sec_`, `col_`, `itm_`, or `blk_`, then 8 lowercase letters and digits (Crockford base32, so no `i`, `l`, `o`, or `u`). An id is unique within its document. The API fills in any you leave out and returns them, so you can change one part of an email later. A copied element gets a new id. Lines, spans, rows, and cells have no ids.

## Limits

These limits only stop abuse. The limit that matters is the size of the finished email: see [the size checks](https://flyanote.com/docs/checks#size).

| At most   | Of                                   |
| --------- | ------------------------------------ |
| 1,000,000 | bytes in the whole document, as JSON |
| 100       | sections in an email                 |
| 50        | blocks in a column                   |
| 4         | columns in a columns section         |
| 24        | items in a grid                      |
| 6         | blocks in a grid item                |
| 8         | blocks in a hero                     |
| 200       | lines in a text                      |
| 200       | spans in a line                      |
| 5,000     | characters in a span                 |
| 10        | lines in the footer’s note           |
| 50,000    | characters in a custom HTML block    |
| 100,000   | characters in a custom HTML section  |
| 4         | columns in a table                   |
| 200       | rows in a table                      |
| 50        | spans in a table cell                |
| 10        | blocks in a repeat                   |
| 200       | times a repeat runs                  |
| 12        | networks in a social block           |
| 2,048     | characters in a link                 |

## Versions

`schema_version` is `MAJOR.MINOR`. Until 1.0, versions are drafts and may change in place. From 1.0 on, a published version’s schema never changes:

* Something new, such as a block or a property, is a new minor version. Documents you already have stay valid.
* Renaming, removing, or tightening anything is a new major version, and we move saved emails forward for you.
* `/schemas/email/v1.json` is always the newest 1.x version. Pin an exact version, such as `v1.0.json`, if you need it never to change.

Error codes never change meaning. A new kind of error gets a new code.

## Errors

When a document doesn’t fit the format, each error has a `code`, a JSON Pointer to where it is in what you sent, the fix, and a `docs_url` that links to the code’s entry below. Branch on the code. Each entry is at `#<code-with-dashes>`, such as `#color-invalid` for `color_invalid`. Errors from the API itself, such as a missing key, are on [API errors](https://flyanote.com/docs/errors).

### Errors by kind

* [Structure](https://flyanote.com/docs/email-format#errors-structure)
* [Types of element](https://flyanote.com/docs/email-format#errors-types-of-element)
* [Values](https://flyanote.com/docs/email-format#errors-values)
* [Links and sources](https://flyanote.com/docs/email-format#errors-links-and-sources)
* [Text and merge tags](https://flyanote.com/docs/email-format#errors-text-and-liquid)
* [Rules across properties](https://flyanote.com/docs/email-format#errors-rules-across-properties)
* [Custom HTML](https://flyanote.com/docs/email-format#errors-custom-html)
* [Drafts from a model](https://flyanote.com/docs/email-format#errors-the-llm-profile)
* [The brand kit](https://flyanote.com/docs/email-format#errors-the-brand-kit)
* [Your account’s kit, images, and fonts](https://flyanote.com/docs/email-format#errors-the-context-validator)
* [Liquid](https://flyanote.com/docs/email-format#errors-liquid)

### Structure

The document’s shape: what goes where, what’s required, and what isn’t allowed.

#### `document_too_large`: The document is too large

Keep the whole document under 1 MB of JSON. Move long custom HTML into fewer sections, or split the email.

#### `type_invalid`: A value has the wrong type

Give this property the type the schema expects, such as a string, a number, an array, or an object.

#### `property_required`: A required property is missing

Add the property. The message names it.

#### `property_unknown`: A property is not part of the format

Remove the property, or check its spelling. Field names are snake\_case, such as content\_background.

#### `not_supported_yet`: Not supported in this version yet

Remove it. The name is reserved for a later version of the format.

#### `value_invalid`: A value is not one of the allowed values

Use one of the values the message lists.

#### `schema_version_unsupported`: The schema version is not one this service reads

Set schema\_version to the version your document was written for. The message names the current one.

#### `string_too_short`: A string is too short

Lengthen the string to at least the minimum the message gives, or remove the property if it is optional.

#### `string_too_long`: A string is too long

Shorten the string to at most the maximum the message gives.

#### `array_too_short`: A list has too few items

Add items until the list has at least the minimum the message gives.

#### `array_too_long`: A list has too many items

Remove items until the list has at most the maximum the message gives.

#### `number_out_of_range`: A number is out of range

Use a number inside the range the message gives.

#### `number_not_integer`: A number must be whole

Use a whole number, with no decimal part.

### Types of element

A section, block, or span with a `type` or `kind` the format doesn’t have.

#### `section_kind_unknown`: Unknown section kind

Use one of the section kinds the message lists: columns, grid, hero, or html.

#### `block_type_unknown`: Unknown block type

Use one of the block types the message lists.

#### `block_not_allowed_here`: This block type cannot go here

Move the block into a column, or use a block type this container allows. The message lists them.

#### `line_kind_unknown`: Unknown line kind

Use one of p, h1, h2, h3, bullet, number, or quote.

### Values

A color, size, or other value that isn’t written the way the format takes it.

#### `color_invalid`: Not a color

Use a palette token such as brand.primary or text.default, or an exact color as #rrggbb. Alpha and color names are not supported.

#### `hex_invalid`: Not an exact color

Dark overrides take an exact color only, as #rrggbb.

#### `dimension_invalid`: Not a valid size

Use a token such as space.md or radius.md, or whole pixels such as 16px, inside the range the message gives.

#### `font_size_invalid`: Not a valid font size

Use a size token such as size.body or size.h2, or whole pixels inside the range the message gives.

#### `font_invalid`: Not a valid font

Use font.heading or font.body, or the name of a font family from the catalog, such as Georgia.

#### `button_width_invalid`: Not a valid button width

Use auto, full, or whole pixels from 1px to 700px.

#### `palette_invalid`: Not a valid palette id

Use the id of one of the kit’s palettes, such as main: lowercase letters, digits, and underscores, but not kit, which is reserved.

#### `theme_invalid`: Not a valid theme id

Use light, dark, or the id of one of the palette’s themes: lowercase letters, digits, and underscores.

#### `brand_invalid`: Not a valid brand id

Use the id of one of the account’s brands as the API gives it (kit\_ and 26 lowercase letters and digits), or remove settings.brand to use the default brand.

#### `lang_invalid`: Not a language tag

Use a BCP 47 language tag, such as en or pt-BR.

#### `timestamp_invalid`: Not a UTC timestamp

Use an ISO 8601 timestamp in UTC, ending in Z, such as 2026-10-06T14:02:11Z.

#### `pointer_invalid`: Not a JSON Pointer

Use a JSON Pointer relative to the element, such as /alt.

#### `currency_invalid`: Not a currency

Use an ISO 4217 code such as USD, or a path to one in the data, such as data.order.currency.

### Links and sources

Where a link goes and where an image comes from.

#### `link_invalid`: Not a valid link

Use a full address with its scheme: https://, mailto:, tel:, asset:, kit:, or system:. A link can also be a merge tag, such as {{ data.tracking\_url }}.

#### `link_scheme_not_allowed`: This kind of link is not allowed

Use an https://, mailto:, or tel: link instead. javascript:, data:, file:, and vbscript: links are never allowed.

#### `link_relative`: A link has no scheme

Write the full address, starting with https://. Relative links don’t work in email.

#### `link_marketing_only`: This link is for marketing mail only

Remove the unsubscribe or preferences link, or set meta.category to marketing. Transactional mail has no unsubscribe.

#### `src_invalid`: Not a valid image source

Use an uploaded image (asset:<id>), a full web address (https:// is best), or a merge tag such as {{ data.image\_url }}.

### Text and merge tags

The text model: lines, spans, marks, and the merge tags inside them.

#### `span_text_liquid`: Merge tags in text must be field spans

Replace the tag with a field span, such as { "field": "contact.first\_name", "fallback": "friend" }. Liquid tags ({% %}) are allowed only in custom HTML.

#### `liquid_tag_not_allowed`: Liquid tags are not allowed here

Use only output tags such as {{ data.order.number }} here. Tags like {% if %} are allowed only in custom HTML.

#### `liquid_not_allowed`: Liquid is not allowed here

Remove the {{ }} or {% %}. This field is plain text.

#### `path_invalid`: Not a valid data path

Write a path such as contact.first\_name or data.order.total: a namespace, then names joined by dots, with no brackets.

#### `path_case_invalid`: A name under contact, account, message, campaign, links, or brand has a capital letter

Write names under our namespaces in lowercase, as contact.first\_name, in a field and in Liquid alike. Names under data, and under a repeat’s variable, may use any case.

#### `path_root_unknown`: A path starts with an unknown name

Start the path with contact, account, message, campaign, data, links, or now, or with the variable of a repeat that contains it.

#### `loop_variable_reserved`: A loop variable shadows a namespace

Pick another name, such as item. contact, account, message, campaign, data, links, now, brand, and forloop are taken.

#### `field_currency_without_money`: A currency is set on a field that isn’t money

Set format to money, or remove the currency.

#### `field_money_without_currency`: A money field has no currency

Set currency to an ISO 4217 code such as USD, or a path to one in the data, such as data.order.currency.

### Rules across properties

Rules that look at more than one property at once, such as unique ids and the footer.

#### `marks_conflict`: Superscript and subscript together

Keep either sup or sub on this span, not both.

#### `line_level_orphan`: A nested list line has no list above it

Put a bullet or number line before it, or remove level: 2.

#### `line_start_nested`: A nested list line cannot restart numbering

Remove start from this line. Only a top-level number line can start a new list.

#### `widths_invalid`: Column widths don’t fit the columns

Give one width per column, in whole percents that add up to 100, or remove widths for equal columns.

#### `table_cells_mismatch`: A table row has the wrong number of cells

Give every row, and the header, one cell per table column. Use an empty cell ({ "spans": \[\] }) for a blank.

#### `image_alt_missing`: An image has no alt text

Describe the image in alt, or set decorative: true if it carries no meaning.

#### `image_alt_conflict`: An image has alt text and is marked decorative

Remove alt from a decorative image, or remove decorative: true.

#### `image_width_required`: An image from outside needs a width

Set width, such as 300px. Images from an address or a merge tag can’t be measured when the email is built.

#### `background_fallback_missing`: A background image has no fallback color

Set background as well. It shows when images are off, and it’s what contrast is checked against.

#### `background_image_size_required`: A hero background from outside needs its size

Set background\_image.width and height in pixels, or use an uploaded image (asset:<id>).

#### `id_invalid`: Not a valid id

Use the element’s prefix (sec\_, col\_, itm\_, or blk\_) and 8 lowercase Crockford base32 characters, or leave the id out and let the API fill it in.

#### `id_duplicate`: Two elements share an id

Give the copy a new id, or leave its id out and let the API fill one in.

#### `kit_version_without_brand`: A kit version is pinned without its brand

Set settings.brand to the kit the version belongs to, or remove settings.kit\_version to follow the kit’s newest version.

#### `footer_duplicate`: More than one footer

Keep one footer block, in the last section.

#### `footer_not_last`: The footer isn’t in the last section

Move the footer block into the last section.

### Custom HTML

HTML you write yourself, in an `html` block or section, checked when it’s saved. The message gives the line and column inside the HTML.

#### `html_parse`: The custom HTML can’t be read as part of an email

Write the HTML that goes inside <body>: no <html>, <head>, <body>, or doctype, no NUL or other character HTML doesn’t allow, nothing nested over 100 deep, no tag with over 100 attributes, and no formatting tag such as <b> or <a> left open.

#### `html_unsafe`: The custom HTML holds something email can’t safely carry

Remove it, or use what the message suggests instead: a link instead of a form or frame, an <img> instead of a CSS or background image, plain HTML instead of SVG.

#### `html_style`: The custom HTML has a <style> tag

Put styles on each element (style="…") instead.

#### `html_layout_escape`: The custom HTML could move things out of its section

Remove position, transform, z-index, float, negative margins, computed sizes (calc and the like), custom properties, CSS escapes, and @import. Lay things out with tables and widths instead.

#### `html_css`: The custom HTML uses CSS email can’t carry

Use the properties and values the message suggests, written plainly in each element’s style attribute.

#### `html_color`: A color in the custom HTML can’t adapt to dark mode

Write each color as #rrggbb (such as #1a73e8), #rgb, rgb(), or a color name, one per declaration, in color, background-color, background, or a border.

#### `html_alt_missing`: An image in the custom HTML has no alt text

Describe the image in alt="…", or write alt="" if it’s only decoration.

#### `html_liquid_unsafe`: A merge tag sits where the custom HTML can’t hold it safely

Put merge tags in text, in quoted alt, title, and aria-label attributes, and in links and image sources. Brand colors, fonts, and sizes may also go in a style.

#### `html_liquid_unbalanced`: A Liquid tag opens and closes in different elements

Close every element you open inside an {% if %} or {% for %} before its next {% else %} or end tag.

#### `html_conditional`: An Outlook conditional comment isn’t one Flyanote can keep

Write <!--\[if mso\]> … <!\[endif\]--> with plain tags inside, close each element it opens in a later one beside it, and pair each <!--\[if !mso\]><!--> with <!--<!\[endif\]-->.

#### `html_after_footer`: Custom HTML comes after the footer

Move the html block above the footer. The footer comes last in every email.

### Drafts from a model

A draft or patch from an AI model. The pointer points into what the model wrote, not the document.

#### `llm_ref_unknown`: A block points at a section that isn’t there

Set at.section to the ref of one of the sections, or to a ref an earlier insert\_section op created.

#### `llm_ref_duplicate`: Two sections share a ref

Give each section its own ref, one that no element in the email already has as its id.

#### `llm_target_unknown`: An op targets an id that isn’t in the email

Use the id of an element the email has, as the projected email shows it.

#### `llm_target_invalid`: An op targets the wrong kind of element

replace\_lines takes a text block; set\_block takes a block; delete and move take a block or a section; after takes a block, column, grid item, or section id.

#### `llm_table_repeat_columns`: An edit changes the columns of a table that repeats a row

Keep the table’s columns as they are: it repeats a row per item, which the projected email doesn’t show. A person can change its columns in the editor.

#### `llm_preheader_conflict`: A set\_meta op both removes the preheader and sets one

Set clear\_preheader to true with preheader "" to remove it, or give the new preheader with clear\_preheader false.

#### `llm_draft_empty`: The draft has nothing in it but a footer

Write the email: at least one block other than the footer.

#### `llm_field_unused`: A field is set where it doesn’t apply

Each field says the block types, ops, or spans it applies to. Give it its unset value everywhere else ("", "kit", 0, false, or \[\], as it says), and put what you meant in the field that applies, such as button\_width for a button or after for insert\_block.

### The brand kit

The kit’s colors, fonts, logo, and links. The pointer points into the kit as you sent it.

#### `kit_main_palette_missing`: The first palette isn’t main

Make the first palette the one with id main. Every kit has it, and documents fall back to it.

#### `kit_palette_duplicate`: Two palettes share an id

Give each palette its own id.

#### `kit_extra_duplicate`: Two extra colors share a slug

Give each extra color in a palette its own slug.

#### `kit_dark_edited_invalid`: An edited dark color isn’t there

List in dark\_edited only colors the palette has, each with its dark value set in dark, or remove the entry to have the dark value generated.

#### `kit_theme_reserved`: A theme uses a reserved id

Give the theme another id. light is the palette’s own colors and dark its dark values, so a theme can’t take either.

#### `kit_theme_duplicate`: Two themes in a palette share an id

Give each theme in a palette its own id.

#### `kit_links_too_many`: Too many saved links

Keep at most 20 saved links.

#### `font_fallback_invalid`: Not a font fallback list

List font names separated by commas, without quotes, ending in serif, sans-serif, or monospace, such as Helvetica, Arial, sans-serif.

#### `kit_too_large`: The brand kit is too large

Keep the brand kit under 64 KiB of JSON: fewer palettes, extra colors, or saved links, or a shorter description.

### Your account’s kit, images, and fonts

What the document asks of your account: a token, image, font, or saved link it doesn’t have.

#### `palette_unknown`: The kit has no such palette

Use the id of one of the kit’s palettes, or remove settings.palette to use main.

#### `theme_unknown`: The palette has no such theme

Use light, dark, or the id of one of the palette’s themes, or remove settings.theme to use light.

#### `token_unknown`: The kit has no such color

Use a color the palette has, add it to the kit as an extra color, or use an exact color such as #b5462b.

#### `font_unknown`: The font isn’t in the catalog

Use a family from the font catalog, such as Georgia or Arial, spelled as the catalog spells it. In a document, font.heading and font.body use the kit’s fonts.

#### `asset_not_found`: The file isn’t in this account

Use a file uploaded to this account, or a full https:// address.

#### `link_kit_unknown`: The kit has no such link

Add the link to the kit (its website, a saved link, or a social profile), or use a full https:// address here.

#### `kit_logo_missing`: The kit has no logo

Add a logo to the brand kit, or remove the logo block.

#### `link_view_online_unavailable`: The online version isn’t available yet

Remove the system:view\_online link. Emails can’t be viewed online yet.

#### `subject_empty`: The email has no subject

Write a subject before you publish or send: it’s what a recipient sees first in their inbox.

#### `button_label_empty`: A button has no label

Write the button’s label, such as Read more or View your order, or remove the button.

#### `liquid_namespace_whole`: A whole namespace is read

Name each field you use, such as contact.first\_name, not contact on its own or a loop over it. Flyanote sends a template only the fields it names.

#### `variables_too_many`: The email reads too many fields to list

Read at most 500 different fields, each named in at most 300 characters.

### Liquid

Merge tags and Liquid that won’t run. Health checks and the API share these codes.

#### `liquid_syntax`: The Liquid doesn’t parse

Check the tag at the line and column given: a {{ needs its }}, and every {% if %} or {% for %} its end tag.

#### `liquid_template_too_large`: The Liquid is too long

Shorten this text. The message gives the limit.

#### `liquid_tag_unknown`: Not a Liquid tag Flyanote has

Use if, unless, case, for, assign, capture, comment, or raw. include and render aren’t available.

#### `liquid_filter_unknown`: Not a Liquid filter Flyanote has

Use one of the filters in the merge tag reference, such as fallback, format\_money, or localize\_date.

#### `liquid_filter_argument_invalid`: A filter has the wrong arguments

Give the filter the arguments the merge tag reference lists for it.

#### `liquid_dynamic_lookup`: A field is looked up by another value

Name each field directly, such as contact.first\_name, not contact\[key\]. Flyanote sends a template only the fields it names.

#### `liquid_name_reserved`: A Liquid variable uses a reserved name

Pick another name for the variable. contact, account, message, campaign, data, links, now, brand, and forloop are taken.

#### `liquid_loop_too_deep`: Loops nest too deep

Nest loops at most two deep.

#### `liquid_nesting_too_deep`: Tags nest too deep

Nest fewer tags inside each other. The message gives the limit.

#### `liquid_range_too_large`: A range is too large

Use a range with fewer numbers. The message gives the limit.

#### `liquid_range_bound_too_large`: A range starts or ends too far from zero

Keep the first and last numbers of a range between -9007199254740991 and 9007199254740991.

#### `liquid_brand_not_allowed`: Brand values work only in custom HTML

Remove the brand value here, or use the kit’s token in the element’s style instead.

#### `liquid_brand_in_tag`: A brand value is used in a tag

Output brand values only, as in {{ brand.colors.primary }}, not inside {% if %} or other tags.

#### `liquid_brand_mixed`: A brand value is mixed with data

Output the brand value on its own, in a separate {{ }} from any contact or data field.

#### `liquid_brand_unknown`: The kit has no such brand value

Check the spelling against the brand values your kit has, such as brand.colors.primary, brand.fonts.body, or brand.links.<key>.url.
