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
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
- 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
react-emailinlines styles at export time via theTailwindwrapper component. If you import a component outside of<Tailwind>{...}</Tailwind>, classes are dropped. Both existing templates wrap their entire body in<Tailwind>— keep that pattern.
Render-time errors (after sirloin sends)
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
Sirloin passes raw strings to ReplaceAll. If a caller passes "", the
placeholder is replaced with an empty string and the email renders with
“Hey ! Your character is ready” (note the double space). Validate inputs
in the caller, not in the template.
Wrong default export in training-failed.tsx
apps/shank/react-templates/emails/training-failed.tsx currently declares
export const TrainingDoneEmail = ... and export default TrainingDoneEmail
inside the file (verified at last review). This is a copy-paste artifact —
the preview will mis-label the template, and any tooling that keys off the
component name will report training-failed as TrainingDoneEmail. Rename
to TrainingFailedEmail when next touched. The exported HTML
(apps/sirloin/internal/pkg/emails/templates/training-failed.html) is
not affected: the file has no <title> element and the
<Preview> text is the correct Your character {{CHARACTER_NAME}} training failed string. The bug is purely cosmetic in the TSX preview
and any tooling that keys off the exported component name.
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
- 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(@zen): document whether designers run external visual checks before merging; if so, add the checklist link toshank-runbook.md.
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.