Skip to content

Shank Email Templates

Shank is not a runtime service. It is a build-time React Email project that compiles JSX templates to static HTML and writes the result into sirloin’s embedded template directory. There is no network surface, no endpoints, no public API.

The “API” of shank is the set of HTML files it produces and the placeholder tokens those files contain. Sirloin’s internal/pkg/emails package treats these as plain string templates and substitutes {{TOKEN}} placeholders at send time (see apps/sirloin/internal/pkg/emails/client.go).

  • React Email project: apps/shank/react-templates/
  • Templates: apps/shank/react-templates/emails/*.tsx
  • Export target (committed): apps/sirloin/internal/pkg/emails/templates/*.html

The export path is hardcoded in apps/shank/react-templates/package.json:

"export": "email export --outDir='../../sirloin/internal/pkg/emails/templates'"
Source TSX Exported HTML Sirloin sender
emails/manual-review-custom-vi-approved.tsx templates/manual-review-custom-vi-approved.html Client.SendManualReviewCustomVIApproved
emails/manual-review-custom-vi-rejected.tsx templates/manual-review-custom-vi-rejected.html Client.SendManualReviewCustomVIRejected
emails/manual-review-ri-approved.tsx templates/manual-review-ri-approved.html Client.SendManualReviewRIApproved
emails/manual-review-ri-rejected.tsx templates/manual-review-ri-rejected.html Client.SendManualReviewRIRejected

Placeholders: {{CHARACTER_NAME}}, {{CHARACTER_ID}} and {{REVIEW_RESULTS}} (check one template with grep -oE '\{\{[A-Z_]+\}\}' apps/sirloin/internal/pkg/emails/templates/<name>.html). Caller: apps/sirloin/internal/app/services/strip/striplikenessreviewemail.go.

The training-done and training-failed templates were retired in ENGINE-3; sirloin no longer sends a training-completion email.

apps/sirloin/internal/pkg/emails/client.go embeds the template directory at compile time:

//go:embed templates
var emailTemplates embed.FS

On NewClient, it reads every required template from the embedded directory into in-memory strings. Each Send* method does straight strings.ReplaceAll substitution on the {{TOKEN}} placeholders, then sends via SMTP+TLS using credentials passed to NewClient.

This means:

  • Templates are part of the sirloin binary after pnpm export + go build.
  • Shank changes are not deployed independently — sirloin must be rebuilt and redeployed to pick them up.
  • New placeholders require two coordinated changes: the template (shank) and the substitution call (sirloin).
  1. Add apps/shank/react-templates/emails/<name>.tsx.
  2. Use {{TOKEN}} placeholders for any dynamic content. Tokens are uppercase snake-case by convention ({{CHARACTER_NAME}}, {{CHARACTER_ID}}).
  3. Run pnpm export from apps/shank/react-templates/.
  4. Add a loader branch in Client.NewClient for the new HTML file.
  5. Add a Send<Name> method that does the strings.ReplaceAll substitution and calls c.send.
  6. Wire the caller (typically a worker in apps/sirloin/internal/app/worker/).
  7. Commit both the source TSX and the exported HTML.

See shank-runbook.md for the full release flow.