Authoring

Frontmatter

The YAML header of a template — every key, its type, and which families accept it.

Every template starts with a YAML mapping between two --- lines. It names the template and its family, and carries the envelope text: the subject, the inbox preview line and, for some families, the sender or the issue number.

templates/payout.md
---
template: payout.sent
family: seller
subject: "Payout sent: {{amount}}"
preheader: "{{order_count}} orders, {{period}}. In your bank within 48 hours."
footer:
  note: Questions about this payout? Reply to this email.
---

Look up a key

KeyTypeRequiredNotes
templatestringyesMust equal the name passed to Compile.
familystringyesA family name in the registry passed to Compile.
subjecttextyesMerge tags allowed. At render, a subject that is empty or contains CR, LF or NUL is an error.
preheadertextrecommendedThe inbox preview line. Leaving it out is the warning frontmatter: no preheader. A tag with no value renders as nothing here rather than failing.
fromtextfamily-dependentRequired, and only allowed, when the family has FromFrontmatter.
issuetextfamily-dependentRequired, and only allowed, when the family has an H3 masthead. It fills {issue} in the masthead.
legalbooleannotrue switches to the family's Legal override: its stream, footer and copy.
footer.noteMarkdownnoThe F2 lead line.
footer.abouttextnoThe F2 {about} value in "This is a service email about …". "" drops that line.
footer.reasonMarkdownnoThe F1 "why you're getting this" line.
footer.navbooleannoTurns the F1 nav row on or off.
footer.optoutMarkdownnoThe F3 opt-out line.

"Text" is a YAML string that may hold merge tags. "Markdown" is a string of exactly one paragraph of inline Markdown; more than that is frontmatter: "footer.note" must be one paragraph. footer itself must be a mapping.

A key of the wrong YAML type is an error at the key: frontmatter: "subject" must be a string, frontmatter: "legal" must be a boolean, frontmatter: "footer.nav" must be a boolean. Unknown keys, at the top level or under footer, are errors too: frontmatter: unknown key "colour", frontmatter: unknown key "footer.links".

A missing required key is reported at 1:1, because there is no key to point at: frontmatter: missing "subject". Every other frontmatter error points at the key's own line and column. A merge-tag error in a one-line subject, preheader, from or issue points at the tag itself; in a footer.* value or a multi-line YAML string it points at the key.

Match the name

template exists so the file and the Go code can't drift apart. If you compile the file under another name, the compile fails:

Output
payout.sent:2:1: frontmatter: template "payout" does not match name "payout.sent"

The footer.* strings are shown in the family's footer, so they use the same {name} placeholders as the family's own footer copy — {site}, {brand}, {domain}, {region}, {address}, {about}, {prefs}, {unsubscribe} and {issue} — filled per message at render time. They can also hold merge tags.

Frontmatter
footer:
  note: "Payment is held until you confirm delivery. Questions? Visit the [Help Centre]({site}/help)."

An unknown placeholder is a compile error at the key: unknown placeholder "{sitee}". Placeholders work only in footer.* and in family copy. Anywhere else in a template, {site} is plain text. The values are listed in Families and streams.

Check what the family accepts

Four keys depend on the family, and lttr.Compile checks them against the registry you pass in (D29). The rules come from the family's fields, not its name:

Family settingFrontmatter ruleError when broken
FromFrontmatter: truefrom is required.frontmatter: family "b2b" requires "from"
FromFrontmatter: falsefrom is not allowed.frontmatter: "from" is only allowed for families that take it
Header: H3issue is required.frontmatter: family "newsletter" requires "issue"
Header: H1 or H2issue is not allowed.frontmatter: "issue" is only allowed for families with a masthead
Legal: nillegal: true is not allowed.frontmatter: family "buyer" has no legal variant
footer variantOnly the footer.* keys that footer shows.frontmatter: "footer.reason" is not used by the footer of family "buyer"
family not in the registry—frontmatter: unknown family "nope"

The footer variant decides which overrides apply. With legal: true, it is the variant of the family's Legal override:

FooterAccepted footer.* keys
F1 (marketing)reason, nav
F2 (service)note, about
F3 (outreach)optout
F3Internalnone

A literal from is also parsed as an address at compile time: frontmatter: "from" is not a valid address: …. A from with merge tags is parsed at render, and every from must be on the family's sender domain (SenderDomain, or Config.Domain), which is checked at render too.

The family rules run only once the template itself compiles. If a template has syntax or module errors, you see those first; fix them and the family errors appear on the next compile (D28).

See it in the Quiet example

The nine families of examples/quiet cover every rule:

FamilyHeaderFooterfromissuelegalfooter.*
buyer, seller, driver, securityH1F2nonononote, about
announcementH1F1 (F2 with legal: true)nonoyesreason, nav (note, about with legal: true)
newsletterH3F1norequirednoreason, nav
promoH1F1nononoreason, nav
b2bH1F3requirednonooptout
internalH2F3Internalnonononone

Two of the examples use the family-dependent keys:

templates/04-hub-journal.md
template: journal.digest
family: newsletter
issue: "{{issue}}"
templates/08-partner-outreach.md
template: outreach.partner
family: b2b
from: "Marisa Hodge <marisa@partners.caribhubs.com>"

Next steps

Copyright © 2026