email brand hygiene #11

Merged
archvillainette merged 14 commits from email-brand-hygiene-spec into main 2026-06-27 08:02:15 +00:00
4 changed files with 23 additions and 15 deletions
Showing only changes of commit 53010830f7 - Show all commits
@@ -71,7 +71,7 @@ email/
invitation.html # MODIFY invitation.html # MODIFY
banned.html # MODIFY banned.html # MODIFY
test.html # MODIFY test.html # MODIFY
notification.html # MODIFY: keeps body, IMPORTs post-queue-body when applicable notification.html # MODIFY: keeps {body}; post queue flow supplies rendered post-queue body
digest.html # MODIFY: keeps loops; reskinned digest.html # MODIFY: keeps loops; reskinned
README.md # MODIFY: partials now real; document IMPORT + literal-hex constraint README.md # MODIFY: partials now real; document IMPORT + literal-hex constraint
languages/ languages/
@@ -526,7 +526,7 @@ git commit -m "feat(email): convert banned.html to shared partials + brand"
### Task 8: Convert `notification.html` + reskin `post-queue-body.html` ### Task 8: Convert `notification.html` + reskin `post-queue-body.html`
`notification.html` keeps its notification body and imports `post-queue-body.html` for post-queue notifications. `notification.html` keeps `{body}`. NodeBB's post queue flow renders `post-queue-body.html` server-side into that notification body before the email template renders.
**Files:** **Files:**
- Modify: `email/notification.html`, `email/partials/post-queue-body.html` - Modify: `email/notification.html`, `email/partials/post-queue-body.html`
@@ -558,9 +558,9 @@ git commit -m "feat(email): convert banned.html to shared partials + brand"
<p style="color: #3a3340;">{content}</p> <p style="color: #3a3340;">{content}</p>
``` ```
- [x] **Step 2: Read `email/notification.html`** (lines ~166417 are the header/body/footer). Note where it renders the notification body and whether it already `IMPORT`s `post-queue-body.html`. - [x] **Step 2: Read `email/notification.html`** (lines ~166417 are the header/body/footer). Note where it renders `{body}`; post-queue content is supplied by NodeBB before template render.
- [x] **Step 3: Apply the Task 6 transform to `notification.html`** — IMPORT header + `[[email:notification.preheader]]` preheader + retained body table (palette/font find/replace) + IMPORT footer. Preserve the `.notification-body` content and any `{{{ if }}}` post-queue branch; if the body renders a post-queue item, keep/add `<!-- IMPORT emails/partials/post-queue-body.html -->` inside that branch. Add `style="color: #a8893f;"` to notification body links. - [x] **Step 3: Apply the Task 6 transform to `notification.html`** — IMPORT header + `[[email:notification.preheader]]` preheader + retained body table (palette/font find/replace) + IMPORT footer. Preserve the `.notification-body` content that renders `{body}`; post-queue content is already rendered into that value by NodeBB before this template runs. Add `style="color: #a8893f;"` to notification body links.
- [ ] **Step 4: Guard + build** - [ ] **Step 4: Guard + build**
@@ -709,4 +709,4 @@ Task 12 note: `node scripts/check-emails.js` passed with `Email template check p
**Placeholder scan:** Color/font changes given as a concrete find/replace table; `header.html`/`footer.html`/`reset.html`/`post-queue-body.html` given as complete markup; per-template specifics tabulated. Tasks 69 reference complete bodies that already exist in the repo (transform, not author-from-scratch) — the engineer reads the existing file and applies the listed mechanical changes. No "TBD"/"handle edge cases". **Placeholder scan:** Color/font changes given as a concrete find/replace table; `header.html`/`footer.html`/`reset.html`/`post-queue-body.html` given as complete markup; per-template specifics tabulated. Tasks 69 reference complete bodies that already exist in the repo (transform, not author-from-scratch) — the engineer reads the existing file and applies the listed mechanical changes. No "TBD"/"handle edge cases".
**Type/name consistency:** Import paths (`emails/partials/header.html`/`footer.html`/`post-queue-body.html`), preheader keys (`[[email:<name>.preheader]]`), the `<!-- preheader -->` marker, and the guard script's regexes all agree across tasks. **Type/name consistency:** Import paths (`emails/partials/header.html`/`footer.html`), preheader keys (`[[email:<name>.preheader]]`), the `<!-- preheader -->` marker, and the guard script's regexes all agree across tasks. `post-queue-body.html` is rendered by NodeBB's post queue flow into notification body content.
@@ -15,9 +15,10 @@ verbatim. Three issues:
like they came from the same product as the forum. like they came from the same product as the forum.
2. **Massive duplication.** Each template inlines the full `<head>` (reset CSS, 2. **Massive duplication.** Each template inlines the full `<head>` (reset CSS,
media queries), header, and footer — ~400 lines per file, ~390 of them media queries), header, and footer — ~400 lines per file, ~390 of them
identical across all 10. `partials/header.html`, `partials/footer.html`, and identical across all 10. `partials/header.html` and `partials/footer.html`
`partials/post-queue-body.html` exist but **nothing imports them** — they are exist but **nothing imports them** — they are dead code, and the README
dead code, and the README wrongly claims they are included. wrongly claims they are included. `partials/post-queue-body.html` is rendered
by NodeBB's post queue flow into notification body content.
3. **Inbox hygiene gaps.** Every template has an empty `<title>` and no 3. **Inbox hygiene gaps.** Every template has an empty `<title>` and no
preheader (inbox preview text). These make messages look unfinished in the preheader (inbox preview text). These make messages look unfinished in the
inbox list and are easy wins under deliverability heuristics. inbox list and are easy wins under deliverability heuristics.
@@ -67,7 +68,7 @@ email/
partials/ partials/
header.html # doctype, <head>, brand <style>, letterhead band, open container header.html # doctype, <head>, brand <style>, letterhead band, open container
footer.html # gold hairline, unsubscribe block, closing tags footer.html # gold hairline, unsubscribe block, closing tags
post-queue-body.html # unchanged in structure; reskinned post-queue-body.html # NodeBB post queue flow renders this into notification body; reskinned
welcome.html # IMPORT header + body block + IMPORT footer welcome.html # IMPORT header + body block + IMPORT footer
verify-email.html verify-email.html
reset.html reset.html
@@ -75,7 +76,7 @@ email/
registration_accepted.html registration_accepted.html
banned.html banned.html
invitation.html invitation.html
notification.html # body imports post-queue-body.html when applicable notification.html # renders {body}; post queue flow supplies rendered post-queue body
digest.html # keeps its loops; reskinned digest.html # keeps its loops; reskinned
test.html test.html
``` ```
+1 -1
View File
@@ -4,7 +4,7 @@
<div style="display: none; max-height: 0; overflow: hidden; mso-hide: all;">&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;</div> <div style="display: none; max-height: 0; overflow: hidden; mso-hide: all;">&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;&nbsp;&zwnj;</div>
<!-- Email Body : BEGIN --> <!-- Email Body : BEGIN -->
<table role="presentation" cellspacing="0" cellpadding="0" border="0" align="center" width="100%" style="max-width: 600px;"> <table role="presentation" cellspacing="0" cellpadding="0" border="0" align="center" width="100%" style="max-width: 600px;">
<!-- 1 Column Text + Button : BEGIN --> <!-- 1 Column Text : BEGIN -->
<tr> <tr>
<td bgcolor="#fbf7ef"> <td bgcolor="#fbf7ef">
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%"> <table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
+11 -4
View File
@@ -9,13 +9,20 @@ const files = fs.readdirSync(dir).filter((f) => f.endsWith('.html'));
const failures = []; const failures = [];
for (const f of files) { for (const f of files) {
const src = fs.readFileSync(path.join(dir, f), 'utf8'); const src = fs.readFileSync(path.join(dir, f), 'utf8');
const trimmed = src.trim();
const checks = [ const checks = [
[/IMPORT emails\/partials\/header\.(html|tpl)/, 'missing header IMPORT'], [
[/IMPORT emails\/partials\/footer\.(html|tpl)/, 'missing footer IMPORT'], trimmed.startsWith('<!-- IMPORT emails/partials/header.html -->'),
'header IMPORT must be first',
],
[
trimmed.endsWith('<!-- IMPORT emails/partials/footer.html -->'),
'footer IMPORT must be last',
],
[/<!-- preheader -->/, 'missing preheader marker'], [/<!-- preheader -->/, 'missing preheader marker'],
]; ];
for (const [re, msg] of checks) { for (const [check, msg] of checks) {
if (!re.test(src)) failures.push(`${f}: ${msg}`); if (check instanceof RegExp ? !check.test(src) : !check) failures.push(`${f}: ${msg}`);
} }
} }