Error catalog

Resolve every parse and compile diagnostic SML emits with the source condition, an example, and the fix.

Every SML diagnostic carries a source span and a message that names the fix, the shape is line L:C <message>, e.g.:

line 12:8 unknown class "flex", display:flex is unsupported in Outlook for Windows (Word engine), use <Row>/<Column>

Parse errors fail fast (the first one aborts parsing); class-resolution errors are collected so you can fix a whole attribute's worth of classes at once. This page groups every diagnostic by the source condition that triggers it.

Lexer errors

Raised while tokenizing, before the parser sees a well-formed tag or text run.

ConditionExampleFix
Unterminated attribute valuehref="https://example.com (missing closing quote)Add the closing quote.
Bare & in text or an attribute valueBen & Jerry'sEscape it: Ben &amp; Jerry's.
Malformed interpolation{{ first name }}, {{}}Use letters, digits, underscore, or dot only: {{firstName}}.
HTML comment or declaration<!-- note -->, <!DOCTYPE html>SML has no comment or doctype syntax, remove it.
Stray < in text5 < 10Escape it: 5 &lt; 10.
Unexpected character inside a tagA stray symbol between attributesRemove it; only identifiers, =, quotes, {/}, /, > are valid inside a tag.
Unexpected / inside a tag<Image / > with content followingUse /> immediately to self-close, with nothing after the tag name and attributes.

Tag and structure errors

Raised while parsing tags, close tags, and the document shape.

ConditionExampleFix
Unknown tag<Sectoin>Typo suggestions are offered when close (did you mean <Section>?); otherwise the full tag list is listed.
Unknown attribute<Text color="red"><Text> only accepts class, check the primitive reference.
Duplicate attribute<Image src="a.png" src="b.png" />Remove one.
Missing required attribute<Button>Click</Button> (no href)Add the required attribute, e.g. href="...".
Attribute value not quotedwidth=50%Quote strings: width="50%"; wrap numbers in braces: height={24}.
Attribute missing a value entirely<Spacer height>Add = and a value: height={24}.
Number attribute given a stringlevel="2"Use braces for numbers: level={2}.
String attribute given braceshref={docs}Quote it: href="docs".
Number out of bounds<Heading level={9}>, <Spacer height={5000} />Stay within the attribute's documented range (see the primitive reference).
Void element not self-closed<Spacer height={24}></Spacer>Self-close it: <Spacer height={24} />.
Unclosed tag<Text>Hello with no </Text>Add the matching closing tag.
Mismatched closing tag<Text>...</Heading>Close the innermost open tag first.
Text or {{variable}} directly inside a block element<Section>{{issueNumber}}</Section>Wrap it in <Text>: <Section><Text>{{issueNumber}}</Text></Section>.
Child not allowed in this content model<a> nested inside <Button> or another <a>Links cannot nest inside links or buttons, restructure the content.
<Preview> not firstA block before <Preview> inside <Email>Move <Preview> to be the very first child of <Email>.
Document doesn't start with <Email>A <Container> (or any tag) as the rootWrap everything in a single <Email> root.
Root element isn't <Email><Container>...</Container> at the top levelThe document root must be <Email>.
Content after </Email>Trailing text or a second element after the closing </Email>A document has exactly one <Email> root, remove anything after it.
<Else> not last inside <If> (else-not-last)A block after <Else> inside <If>Move <Else> to be the last child of <If>.
More than one <Else> inside <If> (duplicate-else)Two <Else> blocks inside the same <If>Only one <Else> is allowed, merge the branches.

Predicate errors

Raised while parsing an <If test="..."> predicate, see Conditionals for the full grammar. Predicate spans are offset into the containing document, so they point at the exact character inside the test="..." attribute.

ConditionExampleFix
predicate-emptytest=""Write a predicate, e.g. test="plan == 'pro'".
predicate-path-not-supportedtest="user.plan == 'pro'", test="plan.toUpperCase() == 'PRO'"Predicates use flat variable names only, no paths, no method calls. Pass a derived flat variable instead.
predicate-unexpected-charactertest='plan == "pro"' (double-quoted string), test="seats + 1 > 5" (arithmetic)Use single-quoted strings and drop unsupported characters/operators, the grammar has no arithmetic.
predicate-invalid-operatortest="plan = 'pro'" (single =), test="plan ! 'pro'"Use ==/!=/>/>=/</<=.
predicate-unterminated-stringtest="plan == 'pro" (missing closing quote)Add the closing '.
predicate-string-double-dashtest="note == 'a--b'"String literals can't contain --, predicates are embedded verbatim in HTML comment markers, and -- would end the comment early.
predicate-expected-literaltest="seats >", test="plan ==" (operator with no right-hand side)Add a literal after the operator: seats > 5 or plan == 'value'.
predicate-unexpected-tokentest="'pro' == plan" (literal first), test="not and" (keyword as name)Comparisons put the variable first: name == 'value'. Reserved words (and/or/not) can't be used as variable names.
predicate-unexpected-endtest="not", test="plan and", test="(" (predicate cut off mid-expression)Finish the expression, a variable name, comparison, or ( group ) is expected.
predicate-missing-parentest="(a or b" (unclosed group)Add the closing ).
predicate-trailing-inputtest="a b", test="a == 'x' == 'y'" (chained comparison)Join clauses with and/or, comparisons don't chain.

Class-resolution errors

Raised per class while resolving a class attribute, see the class-subset reference for the full supported list.

ConditionExampleFix
Unknown classclass="flex-col" (not in the registry)Use a supported utility, the error lists the supported families, or the valid values for that root.
Forbidden utilityclass="flex", class="shadow-lg"Named per class in the class-subset reference, each names the failing client and the primitive/utility to use instead.
Unsupported variantclass="md:text-lg", class="hover:underline"Only sm: and dark: are supported. md:/lg:/xl:/2xl: → use sm:. hover:/focus:/active: → remove it, email clients don't apply pseudo-state styles.
More than one variant on a classclass="sm:dark:bg-blue-400"At most one variant per class, split into separate sm: and dark: classes.
dark: on a non-color utilityclass="dark:p-4", class="dark:hidden"dark: applies only to color utilities (bg-*, text-{color}, border-{color}), remove dark: or restyle with a color family. See variants.
Trailing !important modifierclass="text-lg!"Remove the trailing !, the compiler manages !important for sm:/dark: rules itself.
Opacity/alpha modifierclass="bg-black/50"Remove the /modifier, it compiles to an alpha color, which renders solid black in Outlook for Windows. Use a solid color token instead.
Rejected arbitrary functionclass="bg-[var(--brand)]", class="p-[calc(1rem+2px)]", class="text-[oklch(0.6_0.1_250)]", class="bg-[rgb(0,0,0,0.5)]", class="bg-[hsl(210,50%,50%)]"Compiled email has no CSS custom properties and no reliable alpha/functional-color support, use a literal hex color or px value.
Warn-tier class (compiles, but flagged)class="mx-4", class="mt-2"Margin compiles but is stripped by some clients (Outlook.com dark mode, some webmail), prefer padding on the parent or <Spacer> for vertical gaps.

Internal errors

ConditionMeaning
internal-unexpected-nodeAn inline tag (strong/em/a) reached block expansion, the parser's content model should make this unreachable. If you see this, it's a compiler bug, not a source problem, please report it.

Debugging tips

  • Fix diagnostics top-to-bottom in a file, later errors sometimes cascade from an earlier unclosed tag or misplaced element.
  • Class-resolution errors are collected per attribute, so a class with three unsupported utilities reports all three at once rather than one per retry.
  • The class-subset reference and primitive reference are generated from and validated against the same registries the compiler resolves against, so "is this class/attribute supported" is always answerable from the docs without trial and error.

On this page