01
Letterhead
One letterhead for everything Preceda posts: the seal and PRECEDA, Chambers at the right, a rule in ink, the body in the reading serif, a signature, and a colophon that says how the letter was posted and how to write back. Mail clients are not browsers: a 600px table, inline styles generated from the tokens, and one <style> block that only adds the phone layout and the Lamp look.
Anatomy
renderLetter(spec)- The shell. Returns { subject, html, text }; every string in the spec is escaped, every link must be absolute https.
masthead- Seal (a PNG: clients strip inline SVG), PRECEDA in live type tracked like the wordmark, and the sender at right in folio size.
rule- One ink rule under the masthead. Hairlines elsewhere.
particulars- Optional one-line facts under the rule (invitation number in gilt ink, introducer, validity). Stacks on a phone.
body- Paragraphs in the reading role (17/1.55), a code slip in mono, a ledger or a verbatim excerpt for team notices.
signature- “Chambers, Preceda”, with an optional closing (“Yours faithfully,”).
colophon- How the letter was posted, how to write back, and the legal line. Never promises a reply the sender cannot read.
correspondent- `unattended` (today’s no-reply address) or `chambers` (a read mailbox, only after SPF, DKIM and DMARC pass). It changes the masthead and the colophon, nothing else.
Variants
Sender: unattended (today)
Posted from a no-reply address. The masthead names Chambers without an address; the colophon says replies are not read and points to Report issue.
Paper
Lamp
Sender: Chambers (after the BA-3 gate)
Posted from chambers@preceda.ai. The address joins the masthead and the colophon invites letters to it.
Paper
Lamp
At 390 px
At or below 480 px the sheet runs edge to edge with 24 px gutters, the sender drops under the wordmark, and the particulars stack one to a line.
390 px
Accessibility
- Every letter is posted with a plain-text twin that says everything the HTML says, in the same order.
- Colour is never the only signal: the code sits in a bordered slip, the invitation number is named in words beside its gilt.
- The HTML declares lang="en-GB", uses role="presentation" on every layout table and gives the decorative seal an empty alt, so a screen reader reads the letter, not the tables.
- The wordmark is live type, so a client that blocks images still shows who wrote.
Do
- Build every email with a letters builder; deliver it in the product.
- Say “Chambers”; write as Chambers writes to counsel: precise, declarative, no hype.
- Serve the seal PNGs from the product’s own https domain (default https://app.preceda.ai/letters/).
Don’t
- Hand-type a colour, a font or a size in a template; every value comes from themeTokens and typeRoles.
- Put an emoji, “AI”, or an exclamation mark in a subject or a letter.
- Claim that replies are read while the sender is a no-reply address.
Usage
import { renderLetter } from '@luvkapur/preceda-design-system/letters';
const { subject, html, text } = renderLetter({
subject: 'Your code to sign in',
preheader: 'Your code is 483917.',
audience: 'member',
correspondent: { kind: 'unattended' },
blocks: [
{ kind: 'paragraph', runs: ['Your code to sign in to Preceda:'] },
{ kind: 'slip', groups: ['483', '917'] },
],
signature: {},
});
await transporter.sendMail({ from, to, subject, html, text });