Variables
Use SML interpolation with {{name}} and ${name}, including where variables are allowed and how they compile.
SML templates interpolate per-recipient data with a flat variable lookup, no expressions, loops, or conditionals. Two equivalent syntaxes are supported:
<Text>Hi {{firstName}}, your plan is ${plan}.</Text>{{firstName}} and ${plan} both resolve the same way: a flat name looked up
against the send-time variables object. There's no dot-path evaluation beyond a flat
key, no filters, and no logic, the same interpolation semantics templates already
use today, carried over unchanged into SML.
Where variables are allowed
A {{variable}} (or ${variable}) can appear:
- As text content inside inline-model elements:
<Preview>,<Heading>,<Text>, and inside<Button>'s label. - Inside a string attribute value, e.g.
href="{{ctaUrl}}"orsrc="{{heroImage}}".
It cannot appear as a bare child of a block-only element (<Container>,
<Section>, <Row>, <Column>), wrap it in <Text> first. That's the same rule
that applies to plain text: block elements don't accept inline content directly.
<!-- Compile error: invalid-child -->
<Section>{{issueNumber}}</Section>
<!-- Correct -->
<Section>
<Text>{{issueNumber}}</Text>
</Section>In attributes
Any string attribute can carry a variable, most commonly href on <Button>/<a>
and src on <Image>:
<Button href="{{ctaUrl}}" class="bg-blue-600 text-white px-6 py-3 rounded-md">
Open dashboard
</Button>
<Image src="{{heroImage}}" alt="Template gallery" width={536} />Numeric attributes (level, width, height on <Heading>/<Image>/<Spacer>)
take a literal {16}-style number, not an interpolation, variables only ever
resolve to strings at send time.
Compiling and plain text
Variables print through the compiler verbatim, the compiled HTML and the generated
plain-text alternative both keep {{name}}/${name} as literal text; substitution
happens per recipient at send time, not at compile time. This is why a compiled
artifact can be cached and reused across every recipient of a send: the artifact is
one deterministic compile of the source, and interpolation happens downstream, per
recipient, in the send path.
Discovering the variables a template uses
Because interpolation is a flat name lookup with no dynamic construction, every
variable reference in a document can be statically collected, that's what powers
the editor's variables rail and the test-send form's schema-driven inputs, without
scanning raw JSON. Variable collection also walks <If test="..."> predicates.
a name referenced only inside a predicate (never in {{}}/${} interpolation)
still shows up in the variables rail and the test-send form.
Variables in conditionals
{{}}/${} interpolation and <If> predicates both read from the same flat
variable lookup, but they're not the same feature: interpolation always prints a
value (or leaves it blank), while <If test="plan == 'pro'"> uses a variable to
choose between two branches of markup at send time and never prints the
predicate itself. See Conditionals for the predicate
grammar and how a missing variable resolves inside a test.