Munchable sends one HTML email. It carries a six digit code for signing in, it is 89 lines of table layout, and it is the only surface in the whole product that cannot import the palette.
The palette is a workspace package, @munchable/design-tokens. The phone app re-exports it, the web app of the same codebase uses it, the social video compositions import it. The marketing site keeps a deliberate copy in CSS. And then there is this file, which is checked into docs/email-templates/confirm-signup.html because the place it actually runs is a textarea in the Supabase dashboard.
No build step, no import, no type error. Just hex codes typed by hand.
Counting the drift
Nine distinct colours appear in the template. Six of them are exactly the palette:
#fff8ec bg (cream)
#ffffff surface
#3b2314 text (cocoa)
#7f5f47 textMuted
#eadcc6 border
#e7ac5b gold
One is a near miss. The box around the code uses #f6ecd9. The palette's biscuit tone, used for exactly this kind of inset panel, is #F5EAD8. One digit apart, invisible side by side, and no mechanism in the world will ever tell me which one was intended.
Two do not exist anywhere else in the product:
#9a8577 the footer paragraph
#b7a390 the tagline under it
Both are plausible members of the family. Neither is a token. They were invented in the moment because the footer wanted something quieter than textMuted, and the quieter thing was never needed anywhere else, so it never became a decision.
That is what hand-copying a palette costs in practice. Not a garish mismatch, which someone would notice and fix. Three quiet colours that are almost right.
Why it cannot just import the package
Transactional auth email in Supabase is a template stored in the project's auth settings, rendered server side with Go templating:
<div style="background:#f6ecd9; border:1.5px solid #e7ac5b; border-radius:16px;">
<span style="font-size:38px; font-weight:800; letter-spacing:10px; color:#3b2314;">
{{ .Token }}
</span>
</div>
{{ .Token }} and {{ .Email }} are filled in by the auth service, not by anything we deploy. There is no bundler in that path, so there is no way for the file to read a TypeScript constant. Keeping the canonical copy in the repo at least gives the dashboard version a source of truth, a diff and a review, and anyone who edits the live one without updating the file has made the repo wrong in a way a human can spot.
The alternative is to send the mail ourselves through our own provider, which is a real option and a much bigger change: it moves sign-in email into our own deliverability, our own retries, and our own templating. For one email with one variable in it, the dashboard template wins.
The rest of the file is 2004 and that is correct
The things that look dated are all deliberate:
- Tables for layout, because that is still what mail clients lay out reliably.
- Every style inline, repeated per element. The font stack is written out seven times in 89 lines.
- A hidden preheader div, so the inbox preview reads "Your Munchable confirmation code is 123456. It expires in 60 minutes." rather than the first words of the body.
-
<meta name="color-scheme" content="light">, because Munchable is a light-only product and a client inverting the cream into charcoal is not a dark theme, it is a broken one.
The webfont links are the one piece of near-theatre. The comment above them says it plainly: Apple Mail and iOS Mail will load them, everything else falls back to the system stack. So the brand face shows up for a minority of recipients, every inline style carries the fallback chain anyway, and the links stay because the cost is one request in the clients that honour them.
The copy is the part I spent longest on
You're receiving this because name@example.com was used to create a
Munchable account. If that wasn't you, you can safely ignore this
email. The account stays unconfirmed and nothing is set up.
Someone typing your address into a sign-up form is a thing that happens, and the usual version of that sentence tells you to ignore the mail without telling you what happens next. "The account stays unconfirmed and nothing is set up" is the sentence that means you can actually stop thinking about it.
The code paragraph does the same job for the other direction: it says the same code works on the web app and the phone app, because the one thing a person holding a six digit code wants to know is whether they are in the right place to type it.
Where the palette is supposed to live
If you want to see the colours as they are meant to be, the site is the reference. munchable.app is the cream, the cocoa ink and the gold outline in their native habitat, munchable.app/recipes is a wall of the inset panels the code box is imitating, and munchable.app/conditions shows the same type scale at full length.
One email, nine colours, six of them true. The useful lesson is not "centralise your tokens", which everybody already agrees with. It is that the surfaces which cannot import them are exactly the surfaces nobody looks at twice.
Top comments (0)