# Email Templates: Brand + Hygiene Pass — Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Reskin all 10 `email/` templates to the Westgate brand, eliminate ~390 lines/file of duplication by wiring up real shared `header.html`/`footer.html` partials via Benchpress `IMPORT`, and close template-level inbox-hygiene gaps (non-empty ``, preheader preview text). **Architecture:** Each template collapses from ~400 lines to ~40: `IMPORT emails/partials/header.html` → preheader + body → `IMPORT emails/partials/footer.html`. The shared head/letterhead/footer carry the brand. The body keeps only this template's content and loops. Palette is applied as literal inline hex (no `var()` — Outlook/Gmail strip it); `scss/westgate/_tokens.scss` is the value source, copied as literals. **Tech Stack:** Benchpress templating (`{var}`, `{{{ if }}}`, `[[ns:key]]`, `<!-- IMPORT … -->`), HTML email (table layout, inline CSS, MSO conditional comments), NodeBB language JSON files, a tiny dependency-free Node assertion script for regression. ## Global Constraints Copied verbatim from the spec — every task implicitly includes these: - **No CSS custom properties.** `var(--…)` is stripped by Outlook/Gmail. All brand color/spacing is **literal hex, inline on each element**. - **Keep table layout, inline CSS, MSO conditional comments.** No flex/grid/modern CSS refactor. The shared `<style>` block holds only what *must* be in a stylesheet: `:hover` states and `@media` queries. - **No hardcoded user-facing copy.** Every visible string stays a `[[email:…]]` key. Strings with no existing key (preheaders) get a new key added to the theme language file. - **Web fonts barely load in email** (Cinzel/Jost only in Apple/iOS Mail). The serif/sans *fallback* stacks carry the look everywhere else. - **Markup must stay balanced and build cleanly** (`./nodebb build` from the NodeBB root where this theme is installed). - Reuse the current feature branch (`email-brand-hygiene-spec`). Never commit to `main`. ### Brand palette — literal hex (the single source of truth for every task) | Slot | Old value | New value | Token | |------|-----------|-----------|-------| | Page background | `#f6f6f6` | `#ece6da` | adapted neutral | | Letterhead band | — | `#18141d` | `--wg-panel` | | Header/footer hairline | — | `#c2a35a` | `--wg-gold` | | Card background | `#ffffff` | `#fbf7ef` | adapted parchment | | H1 / greeting | `#333333` | `#1a1418` | ink | | Sub-heading | `#aaaaaa` | `#9a9086` | `--wg-text-muted` | | Body text | `#555555` | `#3a3340` | ink | | Button bg | `#222222` | `#2a1222` | `--wg-plum` | | Button border | — | `#c2a35a` | `--wg-gold` | | Button text | `#ffffff` | `#f4ecd8` | parchment | | Button hover | `#555555` | `#3a1830` | `--wg-plum-soft` | | Links | default | `#a8893f` | `--wg-gold-soft` | | Footer text | `#888888` | `#9a9086` | `--wg-text-muted` | ### Font stacks — literal, inline - **Headings:** `Cinzel, Georgia, 'Times New Roman', serif` - **Body / UI:** `Jost, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif` Every existing `-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol` stack becomes the **Body** stack, except `<h1>` greeting/heading elements which become the **Headings** stack. --- ## Decisions & deviations from the spec (review before executing) Two points the spec leaves open; resolved here, flag to the user if either is wrong: 1. **`<title>` is `{site_title}`, not per-email.** Benchpress `IMPORT` is a static include with no argument passing, so a *shared* `header.html` cannot receive a per-template title. Using `{site_title}` makes the title **non-empty and graceful everywhere** (the actual hygiene gap), with zero fragile per-template wiring. Per-message specificity lives in the **preheader**, which *is* per-template and trivial. This satisfies the spec's "populate per email / falls back gracefully" intent without a brittle mechanism. If true per-email titles are required, the alternative is keeping `<head>` out of the partial — reject unless asked. 2. **Preheaders need a theme language file.** The theme currently ships no `languages/` dir and no `languages` key in `plugin.json`. New preheader copy can't be hardcoded (Global Constraint), so Task 5 creates `languages/en-GB/email.json` and registers it. Titles need no key (decision 1). --- ## File structure ``` email/ partials/ header.html # MODIFY: doctype, <head>, brand <style>, letterhead band, open container, title={site_title} footer.html # MODIFY: gold hairline, reskinned unsubscribe block, closing tags post-queue-body.html # MODIFY: reskinned (links/text colors) reset.html # MODIFY: thin body — IMPORT header + body + IMPORT footer + preheader (worked example) welcome.html # MODIFY: same transform verify-email.html # MODIFY reset_notify.html # MODIFY registration_accepted.html # MODIFY invitation.html # MODIFY banned.html # MODIFY test.html # MODIFY notification.html # MODIFY: keeps {body}; post queue flow supplies rendered post-queue body digest.html # MODIFY: keeps loops; reskinned README.md # MODIFY: partials now real; document IMPORT + literal-hex constraint languages/ en-GB/email.json # CREATE: preheader keys plugin.json # MODIFY: add "languages": "languages" scripts/check-emails.js # CREATE: dependency-free regression guard ``` --- ### Task 1: Reskin shared `header.html` (letterhead band, palette, title, styles) **Files:** - Modify: `email/partials/header.html` **Interfaces:** - Produces: an `IMPORT`-able partial that opens `<html><head>…</head><body>…<center>…<div class="email-container">` and renders the dark letterhead band with `{logo.src}`. Every template `IMPORT`s it first. It does **not** open the body content table — templates do that. - Consumes (Benchpress params, already provided by NodeBB): `{site_title}`, `{logo.src}`, `{logo.width}`, `{{{ if rtl }}}`, `{{{ if logo.src }}}`. - [x] **Step 1: Replace the empty title with `{site_title}`** In `email/partials/header.html`, change line 17 from: ```html <title> ``` to: ```html {site_title} ``` - [x] **Step 2: Add the guarded webfont link** (low-risk progressive enhancement) Replace the empty webfont placeholder block (currently lines 30–32): ```html ``` with: ```html ``` - [x] **Step 3: Update the button hover color in the progressive-enhancement `