Shank Errors and Pitfalls
Shank does not run at request time, so “errors” here are split into two
buckets: build/export errors that surface during pnpm dev /
pnpm export, and render errors that surface only once sirloin
substitutes placeholders and a client renders the HTML.
Build / export errors
Section titled “Build / export errors”email dev fails to start
Section titled “email dev fails to start”- Port already in use.
email devdefaults to3000. Kill the other process or runpnpm dev -- --port 3001. react/react-domversion drift.react-templates/package.jsonpinsreact@19.0.0andreact-dom@19.0.0and depends onreact-email 3.0.6and@react-email/components 0.0.32. If a transitive bump pulls React 18, JSX from@react-email/componentswill throw at render time. Runpnpm installfromapps/shank/react-templates/(not from the monorepo root) to get the locally pinned versions.
pnpm export writes nothing or writes to the wrong place
Section titled “pnpm export writes nothing or writes to the wrong place”- The export path is hardcoded relative to
apps/shank/react-templates/:--outDir='../../sirloin/internal/pkg/emails/templates'. - If you run
email exportfrom a different directory, the output lands in the wrong tree. Always run fromapps/shank/react-templates/. - If the path is missing (e.g. fresh checkout that pruned the sirloin
tree), the command silently fails to create directories. Verify with
git status apps/sirloin/internal/pkg/emails/templates/.
Tailwind classes do not appear in exported HTML
Section titled “Tailwind classes do not appear in exported HTML”react-emailinlines styles at export time via theTailwindwrapper component. If you import a component outside of<Tailwind>{...}</Tailwind>, classes are dropped. Existing templates wrap their entire body in<Tailwind>— keep that pattern.
Render-time errors (after sirloin sends)
Section titled “Render-time errors (after sirloin sends)”Placeholder appears literally in the email
Section titled “Placeholder appears literally in the email”Symptom: the recipient sees {{CHARACTER_NAME}} instead of the character’s
name.
- The token in the TSX must match the constant in
apps/sirloin/internal/pkg/emails/client.goexactly. Sirloin usesstrings.ReplaceAll(body, "{{CHARACTER_NAME}}", characterName)— a typo on either side leaves the literal token in the output. - Add new tokens in both places (template and
Send*method) in the same change.
Missing prop / empty value
Section titled “Missing prop / empty value”Sirloin passes raw strings to ReplaceAll. If a caller passes "", the
placeholder is replaced with an empty string and the email renders with
a gap where the value should be. Validate inputs
in the caller, not in the template.
PII leaks via preview text
Section titled “PII leaks via preview text”Preview text (<Preview>...</Preview>) is interpolated with the same
{{CHARACTER_NAME}} token. Some inboxes display preview text in
notifications. Avoid putting anything more sensitive than a character name
into preview strings. See
docs/src/content/docs/standards/security-model.md for the canonical PII
classes.
Email-client compatibility
Section titled “Email-client compatibility”- Outlook (desktop) renders via Word. Avoid CSS not supported there: flexbox,
grid,
position: absolute, custom fonts. Stick to the components from@react-email/components— they ship Outlook-safe markup. - Dark-mode handling: react-email does not auto-invert images. Provide
light-on-transparent assets where possible. Existing templates use
https://images.foxy.ai/logo1_*.pngwhich is light. - Litmus / Email-on-Acid: not currently wired into the build (no
external visual-check tooling appears in
apps/shank/react-templates/or.github/workflows/). TODO(unassigned): document whether designers run external visual checks before merging; if so, add the checklist link toshank-runbook.md.
When in doubt
Section titled “When in doubt”Run pnpm dev and inspect the live preview before exporting. The preview
matches the exported HTML 1:1 except for the unsubstituted {{TOKEN}}
placeholders.