Files
sow-nodebb-theme/templates/emails
gitea-botandarchvillainette edc586d9bb Port templates to NodeBB 4.14 benchpress escaping (#19)
NodeBB 4.14 turned on real HTML escaping for single-brace template interpolations (4.13 used an identity escape). This ports raw-HTML output to the double-brace forms ({{helper(...)}}, {{txEscape(content)}}, {{tx(name)}}), mirroring the harmony 2.2.72 -> 3.0.15 and core 4.13.2 -> 4.14.0 template diffs. Fixes the escaped-markup breakage on westgate.pw after the 2026.07.11-1 deploy.

Verified on a fresh 4.14.0 dev stack: no escaped tags on /, /categories, /category/2, /topic/1, /search; contract tests pass.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: vickydotbat <vickydotbat@tutamail.com>
Reviewed-on: #19
Reviewed-by: xtul <mpiasecki720@protonmail.com>
Co-authored-by: gitea-bot <gitea-bot@noreply.git.westgate.pw>
Co-committed-by: gitea-bot <gitea-bot@noreply.git.westgate.pw>
2026-07-11 18:30:34 +00:00
..

Build note: the email templates import emails/partials/header.tpl and emails/partials/footer.tpl through NodeBB's emails/ view namespace.

Email templates

HTML email templates for the Westgate theme. NodeBB renders these and mails them to users on various events (password reset, digest, ban, etc.).

How NodeBB uses them

  • Discovery. NodeBB looks for email templates under a theme/plugin's templates directory. Core ships them in src/views/emails/*.tpl; a theme overrides one by shipping a file of the same name. The templates/emails/ directory here is built into NodeBB's emails/ view namespace, so templates/emails/reset.tpl overrides the core reset template. Run ./nodebb build after editing so the changes are compiled into build/.
  • Template engine. Benchpress, the same engine as the rest of the theme's .tpl files. Syntax:
    • {var} / {obj.field} — interpolate a value from the params object.
    • {{{ if cond }}} … {{{ else }}} … {{{ end }}} — conditionals (e.g. {{{ if logo.src }}}, {{{ if showUnsubscribe }}}, {{{ if rtl }}}).
    • [[namespace:key]] — i18n translation key, resolved server-side after render (e.g. [[email:reset.cta]], [[email:greeting-no-name]]). Strings live in NodeBB's language files, not here.
  • Send path. Emailer.send(template, uid, params)sendToEmailrenderAndTranslate('emails/<name>', params). <name> is the filename without extension (reset.tpl → template name reset).
  • Default params available in every template: url, site_title, logo.{src,width,height}, uid, username, displayname, rtl, plus showUnsubscribe / unsubUrl for digest- and notification-type mails. Each template also gets its own event-specific params (see table).

Templates and what triggers them

File NodeBB event Notable params
welcome.tpl New registration needing email confirmation confirmation link
verify-email.tpl User adds/changes an email and must confirm it confirmation link
reset.tpl User requests a password reset {reset_link}
reset_notify.tpl Confirmation that the password was changed
registration_accepted.tpl Admin approves a queued registration
banned.tpl User is banned ban reason / expiry
invitation.tpl A user is invited to the forum invite link
notification.tpl Per-event notification (reply, mention, etc.); also post-queue items showUnsubscribe, unsubUrl
digest.tpl Scheduled activity digest (daily/weekly/monthly) unread topics, showUnsubscribe
test.tpl "Send test email" button in the ACP

Partials

partials/ holds fragments imported by the templates above. Every non-partial template starts with <!-- IMPORT emails/partials/header.tpl --> and ends with <!-- IMPORT emails/partials/footer.tpl -->.

  • header.tpl — shared email head/header markup.
  • footer.tpl — shared footer with the unsubscribe block ({{{ if showUnsubscribe }}}).
  • post-queue-body.tpl — queued-post notification body fragment (category, topic, author, content), maintained for use by a notification branch/template.

Editing notes

  • These are emails: inline CSS, table layout, MSO/Outlook conditional comments. Keep that structure — don't refactor into modern CSS.
  • Brand colors are literal inline hex because Outlook/Gmail strip var(). scss/westgate/_tokens.scss holds the source values but is not available at runtime; keep the dark letterhead band + light cream card pattern. Preheader copy lives in languages/en-GB/email.json.
  • Don't hardcode user-facing copy; add/replace [[email:…]] keys and define them in the language files.
  • Run node scripts/check-emails.js after converting templates. It enforces the shared-partial structure by requiring a header import, footer import, and <!-- preheader --> marker in each templates/emails/*.tpl file.
  • Rebuild (./nodebb build) and use the ACP test-email button to verify.

Deliverability — out of scope for these templates (ops/mail layer)

Templates cannot affect these; they are flagged so the brand reskin is not mistaken for a complete anti-spam fix:

  • SPF / DKIM / DMARC records + alignment for the sending domain.
  • A From-address on a domain with sending reputation (not a bare/shared host).
  • List-Unsubscribe / List-Unsubscribe-Post (one-click) headers — set by the mail layer, distinct from the in-body unsubscribe link the footer renders.
  • A dedicated sending domain/subdomain and IP warm-up if volume grows.

References