email brand hygiene (#11)
implements proper email hygiene, including templates and a language .json Reviewed-on: #11 Co-authored-by: vickydotbat <vickydotbat@tutamail.com> Co-committed-by: vickydotbat <vickydotbat@tutamail.com>
This commit was merged in pull request #11.
This commit is contained in:
@@ -0,0 +1,712 @@
|
|||||||
|
# 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 `<title>`, 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></title>
|
||||||
|
```
|
||||||
|
|
||||||
|
to:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<title>{site_title}</title>
|
||||||
|
```
|
||||||
|
|
||||||
|
- [x] **Step 2: Add the guarded webfont link** (low-risk progressive enhancement)
|
||||||
|
|
||||||
|
Replace the empty webfont placeholder block (currently lines 30–32):
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!--[if !mso]><!-->
|
||||||
|
<!-- insert web font reference, eg: <link href='https://fonts.googleapis.com/css?family=Roboto:400,700' rel='stylesheet' type='text/css'> -->
|
||||||
|
<!--<![endif]-->
|
||||||
|
```
|
||||||
|
|
||||||
|
with:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!--[if !mso]><!-->
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Cinzel:wght@400;600&family=Jost:wght@400;600&display=swap" rel="stylesheet" />
|
||||||
|
<!--<![endif]-->
|
||||||
|
```
|
||||||
|
|
||||||
|
- [x] **Step 3: Update the button hover color in the progressive-enhancement `<style>`**
|
||||||
|
|
||||||
|
In the `.button-td:hover, .button-a:hover` rule (currently lines 118–122), change both `#555555` to the brand hover `#3a1830`:
|
||||||
|
|
||||||
|
```css
|
||||||
|
.button-td:hover,
|
||||||
|
.button-a:hover {
|
||||||
|
background: #3a1830 !important;
|
||||||
|
border-color: #3a1830 !important;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [x] **Step 4: Recolor the page background on `<body>` and `<center>`**
|
||||||
|
|
||||||
|
Change `<body … bgcolor="#f6f6f6">` (line 148) to `bgcolor="#ece6da"`, and the `<center style="width: 100%; background: #f6f6f6; …">` (line 151) to `background: #ece6da`.
|
||||||
|
|
||||||
|
- [x] **Step 5: Replace the light logo header with the dark letterhead band + gold hairline**
|
||||||
|
|
||||||
|
Replace the entire `<!-- Email Header : BEGIN -->` … `<!-- Email Header : END -->` table (currently lines 166–191) with:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- Letterhead Band : BEGIN -->
|
||||||
|
<table
|
||||||
|
role="presentation"
|
||||||
|
cellspacing="0"
|
||||||
|
cellpadding="0"
|
||||||
|
border="0"
|
||||||
|
align="center"
|
||||||
|
width="100%"
|
||||||
|
style="max-width: 600px"
|
||||||
|
>
|
||||||
|
<tr>
|
||||||
|
<td bgcolor="#18141d" style="padding: 24px 0; text-align: center; background: #18141d;">
|
||||||
|
{{{ if logo.src }}}
|
||||||
|
<img
|
||||||
|
src="{logo.src}"
|
||||||
|
height="auto"
|
||||||
|
width="{logo.width}"
|
||||||
|
alt="{site_title}"
|
||||||
|
border="0"
|
||||||
|
style="height: auto; width: {logo.width}px; background: #18141d; font-family: Cinzel, Georgia, 'Times New Roman', serif; font-size: 15px; line-height: 20px; color: #c2a35a;"
|
||||||
|
/>
|
||||||
|
{{{ else }}}
|
||||||
|
<span style="font-family: Cinzel, Georgia, 'Times New Roman', serif; font-size: 22px; color: #c2a35a;">{site_title}</span>
|
||||||
|
{{{ end }}}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td height="2" bgcolor="#c2a35a" style="height: 2px; line-height: 2px; font-size: 2px; background: #c2a35a;"> </td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<!-- Letterhead Band : END -->
|
||||||
|
```
|
||||||
|
|
||||||
|
> Note: the partial intentionally ends with the container still open (the existing file already closes `</div></center></body></html>` at the end — **delete those closing tags**, see Step 6). Templates supply the body table; `footer.html` supplies the closing tags.
|
||||||
|
|
||||||
|
- [x] **Step 6: Remove the stray closing tags so the partial leaves the container open**
|
||||||
|
|
||||||
|
Delete the final `</div>`, `</center>`, `</body>`, `</html>` (currently lines 192–195). The file must end right after `<!-- Letterhead Band : END -->`. (`footer.html` owns these closing tags.)
|
||||||
|
|
||||||
|
- [x] **Step 7: Verify the file is well-formed manually**
|
||||||
|
|
||||||
|
Read `email/partials/header.html` end-to-end. Confirm: one `<html>`/`<head>`/`<body>` opened and **not** closed; `email-container` div opened and **not** closed; letterhead `bgcolor="#18141d"` and hairline `bgcolor="#c2a35a"` present; `<title>{site_title}</title>`.
|
||||||
|
|
||||||
|
- [x] **Step 8: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add email/partials/header.html
|
||||||
|
git commit -m "feat(email): brand letterhead band + palette in shared header partial"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 2: Reskin shared `footer.html` (gold hairline, muted unsubscribe, closing tags)
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `email/partials/footer.html`
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Consumes: `{{{ if showUnsubscribe }}}`, `{url}`, `{uid}`, `{unsubUrl}` (NodeBB params).
|
||||||
|
- Produces: the gold hairline + footer text + the closing `</div></center></body></html>` and MSO close. Every template `IMPORT`s it last.
|
||||||
|
|
||||||
|
- [ ] **Step 1: Add a gold hairline above the footer and recolor the footer text**
|
||||||
|
|
||||||
|
Replace the entire current `email/partials/footer.html` with:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- Email Footer : BEGIN -->
|
||||||
|
<table role="presentation" cellspacing="0" cellpadding="0" border="0" align="center" width="100%" style="max-width: 680px;">
|
||||||
|
<tr>
|
||||||
|
<td height="1" bgcolor="#c2a35a" style="height: 1px; line-height: 1px; font-size: 1px; background: #c2a35a;"> </td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 28px 10px 40px 10px; width: 100%; font-size: 12px; font-family: Jost, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif; line-height: 18px; text-align: center; color: #9a9086;">
|
||||||
|
{{{ if showUnsubscribe }}}
|
||||||
|
[[email:notif.post.unsub.info]] <a href="{url}/uid/{uid}/settings" style="color: #a8893f;">[[email:unsub.cta]]</a>.
|
||||||
|
<br />[[email:notif.post.unsub.one-click]] <a href="{unsubUrl}" style="color: #a8893f;">[[email:unsubscribe]]</a>.
|
||||||
|
{{{ end }}}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<!-- Email Footer : END -->
|
||||||
|
<!--[if mso]>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<![endif]-->
|
||||||
|
</div>
|
||||||
|
</center>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add email/partials/footer.html
|
||||||
|
git commit -m "feat(email): gold hairline + muted unsubscribe in shared footer partial"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 3: Convert `reset.html` to the thin-body pattern + verify `IMPORT` resolves (worked example)
|
||||||
|
|
||||||
|
This task establishes the exact transform and **confirms the import path** before the others copy it. `reset.html` is the simplest template.
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `email/reset.html`
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Consumes from Tasks 1–2: `emails/partials/header.html`, `emails/partials/footer.html`.
|
||||||
|
- Produces: the canonical `IMPORT header → preheader → body → IMPORT footer` shape that Tasks 6–9 replicate. The preheader pattern and the `<!-- preheader -->` marker comment (asserted by Task 4's script) are defined here.
|
||||||
|
|
||||||
|
- [ ] **Step 1: Replace the entire `reset.html` with the thin body**
|
||||||
|
|
||||||
|
Replace the full contents of `email/reset.html` (all 446 lines) with:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- IMPORT emails/partials/header.html -->
|
||||||
|
<!-- preheader -->
|
||||||
|
<div style="display: none; max-height: 0; overflow: hidden; mso-hide: all;">[[email:reset.preheader]]</div>
|
||||||
|
<div style="display: none; max-height: 0; overflow: hidden; mso-hide: all;"> ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌</div>
|
||||||
|
<!-- Email Body : BEGIN -->
|
||||||
|
<table role="presentation" cellspacing="0" cellpadding="0" border="0" align="center" width="100%" style="max-width: 600px;">
|
||||||
|
<tr>
|
||||||
|
<td bgcolor="#fbf7ef">
|
||||||
|
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 40px 40px 6px 40px; font-family: Jost, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif; font-size: 15px; line-height: 20px; color: #3a3340;">
|
||||||
|
<h1 style="margin: 0; font-family: Cinzel, Georgia, 'Times New Roman', serif; font-size: 24px; line-height: 27px; color: #1a1418; font-weight: normal;">[[email:greeting-no-name]]</h1>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 0px 40px; font-family: Jost, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif; font-size: 15px; line-height: 20px; color: #3a3340;">
|
||||||
|
<h1 style="margin: 0 0 10px 0; font-family: Cinzel, Georgia, 'Times New Roman', serif; font-size: 18px; line-height: 21px; color: #9a9086; font-weight: normal;">[[email:reset.text1]]</h1>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 20px 40px; font-family: Jost, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif; font-size: 15px; line-height: 20px; color: #3a3340;">
|
||||||
|
<p style="margin: 0">[[email:reset.text2]]</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 32px 40px; font-family: Jost, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif; font-size: 15px; line-height: 20px; color: #3a3340;">
|
||||||
|
<!-- Button : BEGIN -->
|
||||||
|
<table role="presentation" cellspacing="0" cellpadding="0" border="0" align="center" style="margin: auto">
|
||||||
|
<tr>
|
||||||
|
<td style="border-radius: 3px; background: #2a1222; text-align: center;" class="button-td">
|
||||||
|
<a href="{reset_link}" style="background: #2a1222; border: 15px solid #2a1222; border-color: #c2a35a; font-family: Jost, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif; font-size: 13px; line-height: 1.1; text-align: center; text-decoration: none; display: block; border-radius: 3px; font-weight: bold;" class="button-a">
|
||||||
|
<span style="color: #f4ecd8" class="button-link">[[email:reset.cta]] →</span>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<!-- Button : END -->
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<!-- Email Body : END -->
|
||||||
|
<!-- IMPORT emails/partials/footer.html -->
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Build NodeBB and confirm the import path resolves**
|
||||||
|
|
||||||
|
From the NodeBB root where this theme is installed:
|
||||||
|
|
||||||
|
Run: `./nodebb build`
|
||||||
|
Expected: build completes with no template/IMPORT error. If it errors with an unresolved import, retry the import lines with `.tpl`:
|
||||||
|
`<!-- IMPORT emails/partials/header.tpl -->` / `footer.tpl`, rebuild, and **use whichever extension resolves** for every subsequent template. Record the working form in a one-line note at the top of `email/README.md`.
|
||||||
|
|
||||||
|
> If neither extension resolves (import genuinely unsupported for theme emails — not expected, core uses it): fall back to self-contained templates and delete the dead partials. Stop and raise this with the user before proceeding — it changes the whole plan.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Send a test reset email and eyeball it**
|
||||||
|
|
||||||
|
ACP → Settings → Email → "Send test email" won't trigger `reset`; instead trigger a real password reset for a test account, or temporarily point the test button at `reset`. Confirm: dark letterhead renders, plum button with gold border and readable parchment text, gold hairline above footer, preheader text shows in the inbox list, body card is cream not white.
|
||||||
|
|
||||||
|
- [ ] **Step 4: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add email/reset.html email/README.md
|
||||||
|
git commit -m "feat(email): convert reset.html to shared-partial thin body + preheader"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 4: Regression guard script (dependency-free)
|
||||||
|
|
||||||
|
Cheap catch for future drift, and the **gate** for Tasks 6–9. No test framework.
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `scripts/check-emails.js`
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Produces: a script that exits non-zero if any `email/*.html` (excluding `partials/`) is missing a header IMPORT, footer IMPORT, or `<!-- preheader -->` marker. Run after every template conversion.
|
||||||
|
|
||||||
|
- [x] **Step 1: Write the script**
|
||||||
|
|
||||||
|
```js
|
||||||
|
#!/usr/bin/env node
|
||||||
|
// ponytail: 30-line structural guard, not a render test. Catches drift, not pixels.
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const dir = path.join(__dirname, '..', 'email');
|
||||||
|
const files = fs.readdirSync(dir).filter((f) => f.endsWith('.html'));
|
||||||
|
|
||||||
|
const failures = [];
|
||||||
|
for (const f of files) {
|
||||||
|
const src = fs.readFileSync(path.join(dir, f), 'utf8');
|
||||||
|
const checks = [
|
||||||
|
[/IMPORT emails\/partials\/header\.(html|tpl)/, 'missing header IMPORT'],
|
||||||
|
[/IMPORT emails\/partials\/footer\.(html|tpl)/, 'missing footer IMPORT'],
|
||||||
|
[/<!-- preheader -->/, 'missing preheader marker'],
|
||||||
|
];
|
||||||
|
for (const [re, msg] of checks) {
|
||||||
|
if (!re.test(src)) failures.push(`${f}: ${msg}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (failures.length) {
|
||||||
|
console.error('Email template check FAILED:\n' + failures.join('\n'));
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
console.log(`Email template check passed (${files.length} templates).`);
|
||||||
|
```
|
||||||
|
|
||||||
|
- [x] **Step 2: Run it — expect failures for every not-yet-converted template, pass for `reset.html`**
|
||||||
|
|
||||||
|
Run: `node scripts/check-emails.js`
|
||||||
|
Expected: FAIL, listing `welcome.html`, `verify-email.html`, `reset_notify.html`, `registration_accepted.html`, `invitation.html`, `banned.html`, `test.html`, `notification.html`, `digest.html` (reset.html absent from the list). This proves the guard discriminates.
|
||||||
|
|
||||||
|
- [x] **Step 3: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add scripts/check-emails.js
|
||||||
|
git commit -m "test(email): structural regression guard for partial imports + preheader"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 5: Theme language file for preheader copy
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `languages/en-GB/email.json`
|
||||||
|
- Modify: `plugin.json`
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Produces: the `[[email:<name>.preheader]]` keys referenced by every template's preheader. Tasks 3, 6–9 reference these.
|
||||||
|
|
||||||
|
- [x] **Step 1: Create the language file with one preheader key per template**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"reset.preheader": "Reset your Shadows Over Westgate password",
|
||||||
|
"welcome.preheader": "Confirm your email to enter Shadows Over Westgate",
|
||||||
|
"verify-email.preheader": "Confirm your new email address",
|
||||||
|
"reset_notify.preheader": "Your Shadows Over Westgate password was changed",
|
||||||
|
"registration_accepted.preheader": "Your Shadows Over Westgate account is approved",
|
||||||
|
"invitation.preheader": "You have been invited to Shadows Over Westgate",
|
||||||
|
"banned.preheader": "Your Shadows Over Westgate account status has changed",
|
||||||
|
"test.preheader": "Test email from Shadows Over Westgate",
|
||||||
|
"notification.preheader": "You have a new notification on Shadows Over Westgate",
|
||||||
|
"digest.preheader": "Your Shadows Over Westgate activity digest"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [x] **Step 2: Register the languages directory in `plugin.json`**
|
||||||
|
|
||||||
|
Add a `"languages"` key alongside the existing `"templates": "templates"` key:
|
||||||
|
|
||||||
|
```json
|
||||||
|
"templates": "templates",
|
||||||
|
"languages": "languages",
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 3: Build and confirm keys resolve (no raw `[[email:reset.preheader]]` in output)**
|
||||||
|
|
||||||
|
Run: `./nodebb build` then re-send the `reset` test email.
|
||||||
|
Expected: build clean; the inbox preview shows "Reset your Shadows Over Westgate password", not the raw key.
|
||||||
|
|
||||||
|
Task 5 note: not run from this theme repo; there is no `./nodebb` executable here. JSON syntax was validated locally and `node scripts/check-emails.js` was run.
|
||||||
|
|
||||||
|
- [x] **Step 4: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add languages/en-GB/email.json plugin.json
|
||||||
|
git commit -m "feat(email): add theme language file with preheader copy"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 6: Convert the six single-column "text + button/link" templates
|
||||||
|
|
||||||
|
`welcome.html`, `verify-email.html`, `reset_notify.html`, `registration_accepted.html`, `invitation.html`, `test.html`. These share `reset.html`'s structure; apply the **same transform**, changing only the per-template keys/links below.
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `email/welcome.html`, `email/verify-email.html`, `email/reset_notify.html`, `email/registration_accepted.html`, `email/invitation.html`, `email/test.html`
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Consumes: the Task 3 thin-body shape (copy it), Task 5 preheader keys.
|
||||||
|
- Produces: six converted templates that pass `scripts/check-emails.js`.
|
||||||
|
|
||||||
|
**The transform (apply to each file):**
|
||||||
|
1. Delete everything from `<!doctype html>` through `<!-- Email Header : END -->` and replace with `<!-- IMPORT emails/partials/header.html -->` + the two-line preheader block from Task 3, using that template's preheader key (e.g. `[[email:welcome.preheader]]`).
|
||||||
|
2. Keep only the `<!-- Email Body : BEGIN -->` … `<!-- Email Body : END -->` table.
|
||||||
|
3. Delete the original footer table and trailing `</div></center></body></html>`/MSO close; replace with `<!-- IMPORT emails/partials/footer.html -->`.
|
||||||
|
4. Apply the **palette + font find/replace** across the retained body (uniform for all templates):
|
||||||
|
|
||||||
|
| Find | Replace |
|
||||||
|
|------|---------|
|
||||||
|
| `bgcolor="#ffffff"` | `bgcolor="#fbf7ef"` |
|
||||||
|
| `color: #333333` (on `<h1>`) | `color: #1a1418` |
|
||||||
|
| `color: #aaaaaa` | `color: #9a9086` |
|
||||||
|
| `color: #555555` | `color: #3a3340` |
|
||||||
|
| `background: #222222` and `border: 15px solid #222222` | `background: #2a1222` / `border: 15px solid #2a1222; border-color: #c2a35a` |
|
||||||
|
| `color: #ffffff` (button span) | `color: #f4ecd8` |
|
||||||
|
| any link `color:` default / `#333333` | `color: #a8893f` |
|
||||||
|
| `-apple-system,…,Segoe UI Symbol` stack on `<h1>` | `Cinzel, Georgia, 'Times New Roman', serif` |
|
||||||
|
| `-apple-system,…,Segoe UI Symbol` stack elsewhere | `Jost, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif` |
|
||||||
|
|
||||||
|
**Per-template specifics** (button/link href and any existing keys are already correct — do not change copy keys):
|
||||||
|
|
||||||
|
| File | Preheader key | Button/link href var |
|
||||||
|
|------|---------------|----------------------|
|
||||||
|
| `welcome.html` | `welcome.preheader` | confirmation link (existing `{confirm_link}` / as present) |
|
||||||
|
| `verify-email.html` | `verify-email.preheader` | confirmation link (as present) |
|
||||||
|
| `reset_notify.html` | `reset_notify.preheader` | none (informational — no button table; skip button rows) |
|
||||||
|
| `registration_accepted.html` | `registration_accepted.preheader` | `{url}` login link (as present) |
|
||||||
|
| `invitation.html` | `invitation.preheader` | invite link (existing var, as present) |
|
||||||
|
| `test.html` | `test.preheader` | none (skip button rows) |
|
||||||
|
|
||||||
|
- [x] **Step 1: Convert `welcome.html`** per the transform + its row above.
|
||||||
|
- [x] **Step 2: Convert `verify-email.html`.**
|
||||||
|
- [x] **Step 3: Convert `reset_notify.html`** (no button — keep only the text rows).
|
||||||
|
- [x] **Step 4: Convert `registration_accepted.html`.**
|
||||||
|
- [x] **Step 5: Convert `invitation.html`.**
|
||||||
|
- [x] **Step 6: Convert `test.html`** (no button).
|
||||||
|
|
||||||
|
- [x] **Step 7: Run the regression guard — expect only `banned`, `notification`, `digest` failing**
|
||||||
|
|
||||||
|
Run: `node scripts/check-emails.js`
|
||||||
|
Expected: FAIL listing exactly `banned.html`, `notification.html`, `digest.html`.
|
||||||
|
|
||||||
|
- [x] **Step 8: Build + commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./nodebb build
|
||||||
|
git add email/welcome.html email/verify-email.html email/reset_notify.html email/registration_accepted.html email/invitation.html email/test.html
|
||||||
|
git commit -m "feat(email): convert single-column templates to shared partials + brand"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 7: Convert `banned.html`
|
||||||
|
|
||||||
|
Separated because the ban template carries a reason/expiry block, not the standard button.
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `email/banned.html`
|
||||||
|
|
||||||
|
- [x] **Step 1: Read `email/banned.html`** to see its body rows (greeting, ban reason, expiry).
|
||||||
|
|
||||||
|
- [x] **Step 2: Apply the Task 6 transform** — IMPORT header + `[[email:banned.preheader]]` preheader + retained body (with the palette/font find/replace) + IMPORT footer. Keep all existing `[[email:ban.*]]` / reason / expiry keys and `{{{ if }}}` conditionals unchanged. Recolor any reason/quote block: card `bgcolor` → `#fbf7ef`, body text → `#3a3340`, any muted text → `#9a9086`.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Guard + build**
|
||||||
|
|
||||||
|
Run: `node scripts/check-emails.js` → expect only `notification.html`, `digest.html` failing.
|
||||||
|
Task 7 note: guard ran with only the expected remaining `notification.html` and `digest.html` failures. `./nodebb build` was not run because no executable `./nodebb` / NodeBB root is available in this theme repo.
|
||||||
|
|
||||||
|
- [x] **Step 4: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add email/banned.html
|
||||||
|
git commit -m "feat(email): convert banned.html to shared partials + brand"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 8: Convert `notification.html` + reskin `post-queue-body.html`
|
||||||
|
|
||||||
|
`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:**
|
||||||
|
- Modify: `email/notification.html`, `email/partials/post-queue-body.html`
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Consumes: `{{{ if showUnsubscribe }}}` (footer handles it), notification body params (`{notification.bodyLong}` / as present).
|
||||||
|
- Produces: converted `notification.html` passing the guard; reskinned post-queue partial.
|
||||||
|
|
||||||
|
- [x] **Step 1: Reskin `post-queue-body.html`** — add link/text color to its `<a>` and `<p>` tags so it matches the card: links `style="color: #a8893f;"`, body `<p>` `style="color: #3a3340;"`, labels keep `<strong>`. Structure unchanged.
|
||||||
|
|
||||||
|
```html
|
||||||
|
<p><strong>[[post-queue:category]]</strong></p>
|
||||||
|
<p><a href="{category.url}" style="color: #a8893f;">{category.name}</a></p>
|
||||||
|
<p>
|
||||||
|
<strong
|
||||||
|
>{{{ if topic.tid }}}[[post-queue:topic]]{{{ else }}}[[post-queue:title]]{{{
|
||||||
|
end }}}</strong
|
||||||
|
>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{{{ if topic.url }}}<a href="{topic.url}" style="color: #a8893f;">{topic.title}</a>{{{ else
|
||||||
|
}}}{topic.title}{{{ end }}}
|
||||||
|
</p>
|
||||||
|
<p><strong>[[post-queue:user]]</strong></p>
|
||||||
|
<p>
|
||||||
|
{{{ if user.url }}}<a href="{user.url}" style="color: #a8893f;">{user.username}</a>{{{ else
|
||||||
|
}}}{user.username}{{{ end }}}
|
||||||
|
</p>
|
||||||
|
<p style="color: #3a3340;">{content}</p>
|
||||||
|
```
|
||||||
|
|
||||||
|
- [x] **Step 2: Read `email/notification.html`** (lines ~166–417 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 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**
|
||||||
|
|
||||||
|
Run: `node scripts/check-emails.js` → expect only `digest.html` failing.
|
||||||
|
Run: `./nodebb build` → clean.
|
||||||
|
Task 8 note: guard ran with only the expected remaining `digest.html` failure. `./nodebb build` was not run because no executable `./nodebb` / NodeBB root is available in this theme repo.
|
||||||
|
|
||||||
|
- [x] **Step 5: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add email/notification.html email/partials/post-queue-body.html
|
||||||
|
git commit -m "feat(email): convert notification.html + reskin post-queue partial"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 9: Convert `digest.html` (preserve all loops)
|
||||||
|
|
||||||
|
The largest template — keeps `{{{ each notifications }}}`, `publicRooms`, `topTopics`, `popularTopics` loops and the `digest.title.{interval}` heading.
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `email/digest.html`
|
||||||
|
|
||||||
|
- [x] **Step 1: Replace the head/header (lines ~1–160) with IMPORT header + preheader**
|
||||||
|
|
||||||
|
Prepend `<!-- IMPORT emails/partials/header.html -->` + the two-line preheader block using `[[email:digest.preheader]]`, deleting everything through `<!-- Email Header : END -->`.
|
||||||
|
|
||||||
|
- [x] **Step 2: Apply the palette/font find/replace across the body and every loop row**
|
||||||
|
|
||||||
|
Same find/replace as Task 6, applied to all loop iterations. Specifically:
|
||||||
|
- card `bgcolor="#ffffff"` → `#fbf7ef`
|
||||||
|
- greeting `<h1>` `#333333` → `#1a1418`, font → Cinzel stack
|
||||||
|
- section headings `#aaaaaa` → `#9a9086`, font → Cinzel stack
|
||||||
|
- list-item link/text `#333333` → body `#3a3340`, links `#a8893f`
|
||||||
|
- teaser text `#666666` → `#3a3340`; secondary `#aaaaaa` → `#9a9086`
|
||||||
|
- the `border-bottom: 1px solid #dddddd` separator → `#e2d8c4`
|
||||||
|
- all `-apple-system,…` stacks → Jost stack (non-`<h1>`), Cinzel stack on `<h1>`
|
||||||
|
|
||||||
|
Leave every `{{{ each }}}`, `{{{ if }}}`, `{var}`, `[[email:digest.*]]`, and `{renderDigestAvatar(@value)}` untouched.
|
||||||
|
|
||||||
|
- [x] **Step 3: Replace the original footer with IMPORT footer**
|
||||||
|
|
||||||
|
Delete the digest's own footer table + trailing `</div></center></body></html>`/MSO close; replace with `<!-- IMPORT emails/partials/footer.html -->`.
|
||||||
|
|
||||||
|
- [x] **Step 4: Guard passes for all templates**
|
||||||
|
|
||||||
|
Run: `node scripts/check-emails.js`
|
||||||
|
Expected: PASS (`Email template check passed (10 templates).`).
|
||||||
|
|
||||||
|
- [ ] **Step 5: Build + test-send the digest**
|
||||||
|
|
||||||
|
Run: `./nodebb build`. Trigger a digest (ACP → manually run digest, or test account). Confirm loops render, headings are serif/gold-muted, separators visible, button-free.
|
||||||
|
|
||||||
|
Task 9 note: `node scripts/check-emails.js` passed with `Email template check passed (10 templates).` Lightweight marker checks confirmed the digest loops and `{renderDigestAvatar(@value)}` remain present. `./nodebb build` and digest test-send were not run because no executable `./nodebb` / NodeBB root is available in this standalone theme repo.
|
||||||
|
|
||||||
|
- [x] **Step 6: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add email/digest.html
|
||||||
|
git commit -m "feat(email): convert digest.html to shared partials + brand, loops intact"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 10: Update `email/README.md`
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `email/README.md`
|
||||||
|
|
||||||
|
- [x] **Step 1: Fix the Partials section** — it currently says the partials are "included by the templates above" (was false, now true). State that every non-partial template `IMPORT`s `header.html` first and `footer.html` last, using the form confirmed in Task 3 (`emails/partials/header.html`), and document `post-queue-body.html` as the queued-post notification body fragment without claiming the current `notification.html` imports it.
|
||||||
|
|
||||||
|
- [x] **Step 2: Add a brand/constraint note to Editing notes** — add a bullet: brand colors are **literal inline hex** (Outlook/Gmail strip `var()`); `scss/westgate/_tokens.scss` holds the source values but cannot be referenced at runtime. Note the dark letterhead band + light cream card pattern, and that preheader copy lives in `languages/en-GB/email.json`.
|
||||||
|
|
||||||
|
- [x] **Step 3: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add email/README.md
|
||||||
|
git commit -m "docs(email): document IMPORT structure + literal-hex brand constraint"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 11: Out-of-scope deliverability note for ops
|
||||||
|
|
||||||
|
The reskin is not an anti-spam fix; record what templates can't touch so it isn't mistaken for one.
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `email/README.md` (append a short "Deliverability — out of scope (ops)" section)
|
||||||
|
|
||||||
|
- [x] **Step 1: Append the ops section**
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## 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.
|
||||||
|
```
|
||||||
|
|
||||||
|
- [x] **Step 2: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add email/README.md
|
||||||
|
git commit -m "docs(email): flag out-of-scope deliverability work for ops"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 12: Final cross-client verification pass
|
||||||
|
|
||||||
|
**Files:** none (verification only).
|
||||||
|
|
||||||
|
- [ ] **Step 1: Build clean + guard green**
|
||||||
|
|
||||||
|
Run: `./nodebb build` (clean) and `node scripts/check-emails.js` (`passed (10 templates)`).
|
||||||
|
|
||||||
|
- [ ] **Step 2: Test-send every template type** via ACP "Send test email" and real triggers (reset, verify, digest).
|
||||||
|
|
||||||
|
- [ ] **Step 3: Eyeball across clients** — Gmail web, Outlook desktop (MSO), Apple Mail, and one dark-mode client (iOS Mail dark or Gmail dark). For each confirm: letterhead band renders with logo/hairline; plum button has gold border + readable parchment text; no broken Outlook layout; preheader shows in the inbox list; dark mode does not destroy legibility (cream card + dark band should survive auto-inversion better than a full-dark design).
|
||||||
|
|
||||||
|
- [ ] **Step 4: If issues found**, fix inline and re-run Steps 1–3; otherwise the branch is ready for the finishing-a-development-branch skill.
|
||||||
|
|
||||||
|
Task 12 note: `node scripts/check-emails.js` passed with `Email template check passed (10 templates).` `./nodebb` is not executable in this standalone theme repo, so `./nodebb build` was unavailable and is not marked complete. ACP test-sends, real trigger sends, and Gmail/Outlook/Apple Mail/dark-mode eyeball checks were not run because this repo has no NodeBB runtime/client access.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Self-review
|
||||||
|
|
||||||
|
**Spec coverage:**
|
||||||
|
- Reskin all 10 to brand (light treatment) → Tasks 1–3, 6–9. ✓
|
||||||
|
- Eliminate duplication via real `IMPORT` partials → Tasks 1–3. ✓
|
||||||
|
- Hygiene: non-empty `<title>` → Task 1 (`{site_title}`, deviation flagged); preheader → Tasks 3, 5, 6–9. ✓
|
||||||
|
- No hardcoded copy / new keys in language files → Task 5. ✓
|
||||||
|
- Document deliverability templates can't fix → Task 11. ✓
|
||||||
|
- Letterhead band, palette table, font stacks → Task 1 + Global Constraints. ✓
|
||||||
|
- Import-path verification → Task 3 Step 2. ✓
|
||||||
|
- `notification` + `post-queue-body`, `digest` loops preserved → Tasks 8–9. ✓
|
||||||
|
- README correction → Task 10. ✓
|
||||||
|
- Optional regression guard → Task 4. ✓
|
||||||
|
- Implementation order matches spec's suggested order. ✓
|
||||||
|
|
||||||
|
**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 6–9 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`), 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.
|
||||||
@@ -0,0 +1,215 @@
|
|||||||
|
# Email templates: brand + hygiene pass
|
||||||
|
|
||||||
|
**Date:** 2026-06-26
|
||||||
|
**Status:** Design (spec only — no implementation)
|
||||||
|
**Scope:** `email/` directory of `nodebb-theme-westgate`.
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
|
||||||
|
The 10 templates under `email/` are NodeBB core's Cerberus boilerplate copied
|
||||||
|
verbatim. Three issues:
|
||||||
|
|
||||||
|
1. **No brand.** All colors are generic greys — `#f6f6f6` page, `#ffffff`
|
||||||
|
card, `#222222` button, `#333/#555/#888/#aaa` text. None of the Westgate
|
||||||
|
palette (plum, gold, red, parchment) appears anywhere. Emails do not look
|
||||||
|
like they came from the same product as the forum.
|
||||||
|
2. **Massive duplication.** Each template inlines the full `<head>` (reset CSS,
|
||||||
|
media queries), header, and footer — ~400 lines per file, ~390 of them
|
||||||
|
identical across all 10. `partials/header.html` and `partials/footer.html`
|
||||||
|
exist but **nothing imports them** — they are dead code, and the README
|
||||||
|
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
|
||||||
|
preheader (inbox preview text). These make messages look unfinished in the
|
||||||
|
inbox list and are easy wins under deliverability heuristics.
|
||||||
|
|
||||||
|
## Goals
|
||||||
|
|
||||||
|
- Reskin all 10 templates to the Westgate brand using a **light, client-robust**
|
||||||
|
treatment (not full dark — see Rejected alternatives).
|
||||||
|
- Eliminate the duplication by wiring up real shared partials with Benchpress
|
||||||
|
`IMPORT`, mirroring how NodeBB core structures its own email templates.
|
||||||
|
- Close the template-level hygiene gaps (real `<title>`, preheader, valid HTML,
|
||||||
|
no hardcoded copy).
|
||||||
|
- Document the deliverability work that templates **cannot** fix, so the reskin
|
||||||
|
is not mistaken for a complete anti-spam fix.
|
||||||
|
|
||||||
|
## Non-goals
|
||||||
|
|
||||||
|
- Full dark-background email design (rejected — see below).
|
||||||
|
- Rewriting user-facing copy / in-world tone. Copy stays in the existing
|
||||||
|
`[[email:…]]` keys; this pass only adds keys that are missing. A copy/voice
|
||||||
|
pass is a separate language-file task.
|
||||||
|
- Any change to SPF/DKIM/DMARC, sending domain, or mail-server config (out of
|
||||||
|
template scope; flagged below for ops).
|
||||||
|
|
||||||
|
## Constraints (email rendering)
|
||||||
|
|
||||||
|
These shape every decision and must be respected in implementation:
|
||||||
|
|
||||||
|
- **No CSS custom properties.** Outlook and Gmail strip `var(--…)`. The brand
|
||||||
|
palette is therefore applied as **literal hex, inline on each element**. The
|
||||||
|
`--wg-*` tokens in `scss/westgate/_tokens.scss` are the source of truth for
|
||||||
|
the values, but cannot be referenced at runtime — they are copied as literals.
|
||||||
|
- **Table layout, inline CSS, MSO conditional comments stay.** Do not refactor
|
||||||
|
into modern/flex/grid CSS. The shared `<style>` block may only hold things
|
||||||
|
that must be in a stylesheet: `:hover` states and `@media` queries. Per-element
|
||||||
|
color/spacing stays inline.
|
||||||
|
- **Web fonts barely load in email.** Cinzel/Jost render only in Apple Mail and
|
||||||
|
iOS Mail. The serif fallback carries the brand look everywhere else.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
Adopt NodeBB core's email structure: each template is a thin body that imports a
|
||||||
|
shared head/header and a shared footer.
|
||||||
|
|
||||||
|
```
|
||||||
|
email/
|
||||||
|
partials/
|
||||||
|
header.html # doctype, <head>, brand <style>, letterhead band, open container
|
||||||
|
footer.html # gold hairline, unsubscribe block, closing tags
|
||||||
|
post-queue-body.html # NodeBB post queue flow renders this into notification body; reskinned
|
||||||
|
welcome.html # IMPORT header + body block + IMPORT footer
|
||||||
|
verify-email.html
|
||||||
|
reset.html
|
||||||
|
reset_notify.html
|
||||||
|
registration_accepted.html
|
||||||
|
banned.html
|
||||||
|
invitation.html
|
||||||
|
notification.html # renders {body}; post queue flow supplies rendered post-queue body
|
||||||
|
digest.html # keeps its loops; reskinned
|
||||||
|
test.html
|
||||||
|
```
|
||||||
|
|
||||||
|
Each non-partial template collapses from ~400 lines to ~40:
|
||||||
|
|
||||||
|
```
|
||||||
|
<!-- IMPORT emails/partials/header.html -->
|
||||||
|
<!-- preheader (hidden preview text) for this email -->
|
||||||
|
<!-- Email Body : BEGIN --> … this template's content … <!-- Email Body : END -->
|
||||||
|
<!-- IMPORT emails/partials/footer.html -->
|
||||||
|
```
|
||||||
|
|
||||||
|
**Import-path verification (implementation must confirm):** NodeBB core uses
|
||||||
|
`<!-- IMPORT emails/partials/header.tpl -->`. The theme ships `.html` files
|
||||||
|
compiled into the `emails/` view namespace. Confirm after `./nodebb build`
|
||||||
|
whether the resolved path is `emails/partials/header.html` (or `.tpl`); use
|
||||||
|
whichever the build resolves. If `IMPORT` cannot resolve the theme's partials,
|
||||||
|
fall back to keeping templates self-contained and delete the dead partials —
|
||||||
|
but the expectation, based on core, is that `IMPORT` works.
|
||||||
|
|
||||||
|
### header.html (shared) responsibilities
|
||||||
|
|
||||||
|
- Doctype, `<html>`, `<head>` with the existing reset CSS and MSO blocks
|
||||||
|
(carried over unchanged).
|
||||||
|
- Shared `<style>`: button `:hover`, the small-screen `@media` typography
|
||||||
|
block, `.notification-body img` rule. Update the hover color to the brand
|
||||||
|
hover (`#3a1830`).
|
||||||
|
- Optional, guarded webfont link for non-MSO clients:
|
||||||
|
`<!--[if !mso]><!--> <link href="…Cinzel…Jost…" rel="stylesheet"> <!--<![endif]-->`
|
||||||
|
Low risk, upgrades supporting clients, safe to omit if it complicates review.
|
||||||
|
- **Letterhead band:** a full-width dark plum bar (`#18141d`) holding the
|
||||||
|
`{logo.src}` logo, with a gold hairline (`#c2a35a`, 1–2px) beneath it. This
|
||||||
|
fixes the logo-on-light problem (the site logo is gold-on-dark) and reads as
|
||||||
|
brand letterhead. Preserve the existing `{{{ if logo.src }}} … {{{ else }}}`
|
||||||
|
fallback.
|
||||||
|
- Open the `email-container` div, preserving the existing `{{{ if rtl }}}`
|
||||||
|
direction handling.
|
||||||
|
|
||||||
|
### footer.html (shared) responsibilities
|
||||||
|
|
||||||
|
- Gold hairline above the footer.
|
||||||
|
- The existing `{{{ if showUnsubscribe }}}` unsubscribe block, reskinned to
|
||||||
|
muted footer text (`#9a9086`).
|
||||||
|
- Close container / center / body / html and the MSO closing block.
|
||||||
|
|
||||||
|
## Visual design
|
||||||
|
|
||||||
|
Light card + dark letterhead band. Palette is a deliverability-safe adaptation
|
||||||
|
of the gothic theme. All values literal-inline.
|
||||||
|
|
||||||
|
| Slot | Current | New | Source token |
|
||||||
|
|------|---------|-----|--------------|
|
||||||
|
| Page background | `#f6f6f6` | `#ece6da` (warm neutral) | adapted from `--wg-text-soft` family |
|
||||||
|
| Letterhead band | — (none) | `#18141d` | `--wg-panel` |
|
||||||
|
| Header hairline | — | `#c2a35a` | `--wg-gold` |
|
||||||
|
| Card background | `#ffffff` | `#fbf7ef` (cream) | adapted parchment |
|
||||||
|
| H1 / greeting | `#333333`, sans | `#1a1418`, serif stack | ink |
|
||||||
|
| Sub-heading | `#aaaaaa` | `#9a9086` | `--wg-text-muted` |
|
||||||
|
| Body text | `#555555` | `#3a3340` | ink |
|
||||||
|
| Button bg / text | `#222222` / `#fff` | `#2a1222` / `#f4ecd8`, gold `#c2a35a` border | `--wg-plum` / parchment / `--wg-gold` |
|
||||||
|
| Button hover | `#555555` | `#3a1830` | `--wg-plum-soft` |
|
||||||
|
| Links | default | `#a8893f` | `--wg-gold-soft` |
|
||||||
|
| Footer text | `#888888` | `#9a9086` | `--wg-text-muted` |
|
||||||
|
| Footer hairline | — | `#c2a35a` | `--wg-gold` |
|
||||||
|
|
||||||
|
**Font stacks** (literal, inline):
|
||||||
|
|
||||||
|
- Headings: `Cinzel, Georgia, 'Times New Roman', serif` — Cinzel where it
|
||||||
|
loads, Georgia serif everywhere else carries the engraved look.
|
||||||
|
- Body / UI: `Jost, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif`.
|
||||||
|
|
||||||
|
Rationale for light over dark: chosen in design review. Dark-background HTML
|
||||||
|
email is fragile — Outlook ignores `bgcolor` on many elements, dark-mode clients
|
||||||
|
re-invert colors unpredictably, and heavy dark blocks raise some spam scores.
|
||||||
|
The dark letterhead band gives brand recognition at the top while the light body
|
||||||
|
stays robust.
|
||||||
|
|
||||||
|
## Hygiene / anti-spam (template-level — in scope)
|
||||||
|
|
||||||
|
- **`<title>`:** populate per email (currently empty in all). Use an existing or
|
||||||
|
new `[[email:…]]` key describing the message; falls back gracefully.
|
||||||
|
- **Preheader:** add one hidden preview-text block at the top of each template's
|
||||||
|
body — the standard hidden-`<div>` + spacing-hack pattern. Per-template text
|
||||||
|
(e.g. reset → "Reset your Shadows Over Westgate password"). This is the inbox
|
||||||
|
list snippet; currently blank/garbage.
|
||||||
|
- **Copy:** keep all visible strings as `[[email:…]]` keys. Where the new
|
||||||
|
structure needs a string that has no key (preheader, title), add the key to
|
||||||
|
the language files. No hardcoded user-facing copy.
|
||||||
|
- **Valid HTML:** consolidation into partials removes the per-file drift risk;
|
||||||
|
resulting markup must be balanced and pass `./nodebb build`.
|
||||||
|
|
||||||
|
## Out of scope — deliverability (flag for ops)
|
||||||
|
|
||||||
|
Templates cannot affect these; list them so the reskin is not mistaken for a
|
||||||
|
spam fix:
|
||||||
|
|
||||||
|
- **SPF / DKIM / DMARC** records and alignment for the sending domain.
|
||||||
|
- From-address on a domain with sending reputation (not a bare/shared host).
|
||||||
|
- `List-Unsubscribe` and `List-Unsubscribe-Post` (one-click) **headers** — set
|
||||||
|
by the mail layer, distinct from the in-body unsubscribe link the footer
|
||||||
|
already renders.
|
||||||
|
- Dedicated sending domain/subdomain and IP warm-up if volume grows.
|
||||||
|
|
||||||
|
## Testing / verification
|
||||||
|
|
||||||
|
- `./nodebb build`, then ACP **"Send test email"** for each template type.
|
||||||
|
- Visual check across Gmail (web), Outlook (desktop/MSO), Apple Mail, and one
|
||||||
|
dark-mode client (iOS Mail dark or Gmail dark). Confirm: letterhead renders,
|
||||||
|
button is plum with readable text, no broken layout in Outlook, preheader
|
||||||
|
shows in the inbox list, dark mode does not destroy legibility.
|
||||||
|
- **Optional regression guard:** a small script asserting that every
|
||||||
|
`email/*.html` (excluding partials) imports header and footer, has a
|
||||||
|
non-empty `<title>` (via partial), and includes a preheader block. Cheap
|
||||||
|
catch for future drift; no test framework needed.
|
||||||
|
|
||||||
|
## README correction
|
||||||
|
|
||||||
|
`email/README.md` currently states the partials "are included by the templates
|
||||||
|
above" — false today. After this work it becomes true. Update the Partials and
|
||||||
|
Editing-notes sections to describe the IMPORT-based structure and the literal-hex
|
||||||
|
palette constraint.
|
||||||
|
|
||||||
|
## Implementation order (suggested)
|
||||||
|
|
||||||
|
1. Build `partials/header.html` + `partials/footer.html` with the new band,
|
||||||
|
palette, `<title>` hook, and confirm `IMPORT` resolves on one template
|
||||||
|
(`reset.html`, the simplest).
|
||||||
|
2. Add preheader + convert the remaining single-column templates
|
||||||
|
(welcome, verify-email, reset_notify, registration_accepted, invitation,
|
||||||
|
test, banned).
|
||||||
|
3. Convert the complex templates (`notification.html` + `post-queue-body.html`,
|
||||||
|
`digest.html`), preserving their loops/conditionals.
|
||||||
|
4. Add missing language keys (titles, preheaders).
|
||||||
|
5. Update README; optional regression script.
|
||||||
|
6. Build + ACP test-send all; cross-client visual pass.
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
# Build note: the email templates import `emails/partials/header.html` and `emails/partials/footer.html` 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 `email/` directory
|
||||||
|
here is built into NodeBB's `emails/` view namespace, so `email/reset.html`
|
||||||
|
overrides the core `reset` template. Run `./nodebb build` after editing so
|
||||||
|
the changes are compiled into `build/`.
|
||||||
|
- **Template engine.** [Benchpress](https://github.com/benchpressjs/benchpressjs),
|
||||||
|
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)` →
|
||||||
|
`sendToEmail` → `renderAndTranslate('emails/<name>', params)`. `<name>` is
|
||||||
|
the filename without extension (`reset.html` → 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.html` | New registration needing email confirmation | confirmation link |
|
||||||
|
| `verify-email.html` | User adds/changes an email and must confirm it | confirmation link |
|
||||||
|
| `reset.html` | User requests a password reset | `{reset_link}` |
|
||||||
|
| `reset_notify.html` | Confirmation that the password was changed | — |
|
||||||
|
| `registration_accepted.html` | Admin approves a queued registration | — |
|
||||||
|
| `banned.html` | User is banned | ban reason / expiry |
|
||||||
|
| `invitation.html` | A user is invited to the forum | invite link |
|
||||||
|
| `notification.html` | Per-event notification (reply, mention, etc.); also post-queue items | `showUnsubscribe`, `unsubUrl` |
|
||||||
|
| `digest.html` | Scheduled activity digest (daily/weekly/monthly) | unread topics, `showUnsubscribe` |
|
||||||
|
| `test.html` | "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.html -->`
|
||||||
|
and ends with `<!-- IMPORT emails/partials/footer.html -->`.
|
||||||
|
|
||||||
|
- `header.html` — shared email head/header markup.
|
||||||
|
- `footer.html` — shared footer with the unsubscribe block
|
||||||
|
(`{{{ if showUnsubscribe }}}`).
|
||||||
|
- `post-queue-body.html` — 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 `email/*.html` 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
|
||||||
|
|
||||||
|
- [NodeBB themes](https://docs.nodebb.org/development/themes/)
|
||||||
|
- [NodeBB templating](https://docs.nodebb.org/development/themes/templates/)
|
||||||
|
- [Email templating discussion](https://community.nodebb.org/topic/1502/email-templating)
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
<!-- IMPORT emails/partials/header.html -->
|
||||||
|
<!-- preheader -->
|
||||||
|
<div style="display: none; max-height: 0; overflow: hidden; mso-hide: all;">[[email:banned.preheader]]</div>
|
||||||
|
<div style="display: none; max-height: 0; overflow: hidden; mso-hide: all;"> ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌</div>
|
||||||
|
<!-- Email Body : BEGIN -->
|
||||||
|
<table role="presentation" cellspacing="0" cellpadding="0" border="0" align="center" width="100%" style="max-width: 600px;">
|
||||||
|
<tr>
|
||||||
|
<td bgcolor="#fbf7ef">
|
||||||
|
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 40px 40px 6px 40px; font-family: Jost, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif; font-size: 15px; line-height: 20px; color: #3a3340;">
|
||||||
|
<h1 style="margin: 0; font-family: Cinzel, Georgia, 'Times New Roman', serif; font-size: 24px; line-height: 27px; color: #1a1418; font-weight: normal;">[[email:greeting-with-name, {username}]]</h1>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 0px 40px; font-family: Jost, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif; font-size: 15px; line-height: 20px; color: #3a3340;">
|
||||||
|
<h1 style="margin: 0 0 10px 0; font-family: Cinzel, Georgia, 'Times New Roman', serif; font-size: 18px; line-height: 21px; color: #9a9086; font-weight: normal;">[[email:banned.text1, {username}, {site_title}]]</h1>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{{{ if reason }}}
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 20px 40px; font-family: Jost, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif; font-size: 15px; line-height: 20px; color: #3a3340;">
|
||||||
|
<p style="margin: 0">[[email:banned.text3]]</p>
|
||||||
|
<div style="margin: 0; padding: 6px 0px; font-family: Jost, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif; font-size: 13px; line-height: 26px; color: #3a3340;">
|
||||||
|
{reason}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{{{ end }}} {{{ if until }}}
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 20px 40px; font-family: Jost, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif; font-size: 15px; line-height: 20px; color: #3a3340;">
|
||||||
|
<p style="margin: 0">[[email:banned.text2, {until}]]</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{{{ end }}}
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<!-- Email Body : END -->
|
||||||
|
<!-- IMPORT emails/partials/footer.html -->
|
||||||
@@ -0,0 +1,178 @@
|
|||||||
|
<!-- IMPORT emails/partials/header.html -->
|
||||||
|
<!-- preheader -->
|
||||||
|
<div style="display: none; max-height: 0; overflow: hidden; mso-hide: all;">[[email:digest.preheader]]</div>
|
||||||
|
<div style="display: none; max-height: 0; overflow: hidden; mso-hide: all;"> ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌</div>
|
||||||
|
<!-- Email Body : BEGIN -->
|
||||||
|
<table role="presentation" cellspacing="0" cellpadding="0" border="0" align="center" width="100%" style="max-width: 600px;">
|
||||||
|
<!-- 1 Column Text : BEGIN -->
|
||||||
|
<tr>
|
||||||
|
<td bgcolor="#fbf7ef">
|
||||||
|
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 40px 40px 6px 40px; font-family: Jost, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif; font-size: 15px; line-height: 20px; color: #3a3340;">
|
||||||
|
<h1 style="margin: 0; font-family: Cinzel, Georgia, 'Times New Roman', serif; font-size: 24px; line-height: 27px; color: #1a1418; font-weight: normal;">[[email:greeting-with-name, {displayname}]]</h1>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 0px 40px; font-family: Jost, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif; font-size: 15px; line-height: 20px; color: #3a3340;">
|
||||||
|
<h1 style="margin: 0 0 10px 0; font-family: Cinzel, Georgia, 'Times New Roman', serif; font-size: 18px; line-height: 21px; color: #9a9086; font-weight: normal;">[[email:digest.title.{interval}]]</h1>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{{{ if notifications.length }}}
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 0px 40px; font-family: Jost, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif; font-size: 15px; line-height: 20px; color: #3a3340;">
|
||||||
|
<ul style="margin: 0; padding: 0;">
|
||||||
|
{{{ each notifications }}}
|
||||||
|
<li style="text-decoration: none; list-style-type: none; padding-bottom: 0.5em;">
|
||||||
|
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 16px; font-family: Jost, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif; width: 32px;">
|
||||||
|
{{{ if notifications.image }}}
|
||||||
|
<img style="vertical-align: middle; width: 32px; height: 32px; border-radius: 50%" src="{notifications.image}" alt="" />
|
||||||
|
{{{ else }}}
|
||||||
|
<div style="vertical-align: middle; width: 32px; height: 32px; line-height: 32px; font-size: 16px; background-color: {notifications.user.icon:bgColor}; color: white; text-align: center; display: inline-block; border-radius: 50%">{notifications.user.icon:text}</div>
|
||||||
|
{{{ end }}}
|
||||||
|
</td>
|
||||||
|
<td style="padding: 16px; font-family: Jost, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif; line-height: 16px; color: #3a3340;">
|
||||||
|
<p style="margin: 0;"><a style="text-decoration:none !important; text-decoration:none; color: #a8893f;" href="{notifications.notification_url}">{notifications.bodyShort}</a></p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</li>
|
||||||
|
{{{ end }}}
|
||||||
|
</ul>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{{{ end }}}
|
||||||
|
{{{ if publicRooms.length }}}
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 0px 40px; font-family: Jost, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif; font-size: 15px; line-height: 20px; color: #3a3340;">
|
||||||
|
<h1 style="margin: 16px 0 24px 0; font-family: Cinzel, Georgia, 'Times New Roman', serif; font-size: 18px; line-height: 21px; color: #9a9086; font-weight: normal;">[[email:digest.unread-rooms]]</h1>
|
||||||
|
<ul style="margin: 0; padding: 0;">
|
||||||
|
{{{ each publicRooms }}}
|
||||||
|
<li style="text-decoration: none; list-style-type: none; padding-bottom: 0.5em;">
|
||||||
|
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 16px; font-family: Jost, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif; line-height: 16px; color: #3a3340;">
|
||||||
|
<p style="margin: 0;"><a style="text-decoration:none !important; text-decoration:none; color: #a8893f;" href="{url}/chats/{./roomId}"># [[email:digest.room-name-unreadcount, {./roomName}, {./unreadCountText}]]</a></p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</li>
|
||||||
|
{{{ end }}}
|
||||||
|
</ul>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{{{ end }}}
|
||||||
|
{{{ if topTopics.length }}}
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 0px 40px; font-family: Jost, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif; font-size: 15px; line-height: 20px; color: #3a3340;">
|
||||||
|
<h1 style="margin: 16px 0 24px 0; font-family: Cinzel, Georgia, 'Times New Roman', serif; font-size: 18px; line-height: 21px; color: #9a9086; font-weight: normal;">[[email:digest.top-topics, {site_title}]]</h1>
|
||||||
|
<ul style="margin: 0; padding: 0;">
|
||||||
|
{{{ each topTopics }}}
|
||||||
|
<li style="text-decoration: none; list-style-type: none; padding-bottom: 0.5em;">
|
||||||
|
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 6px 16px; font-family: Jost, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif; width: 32px; vertical-align: middle;">{renderDigestAvatar(@value)}</td>
|
||||||
|
<td style="padding: 6px 16px; font-family: Jost, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif; line-height: 16px; color: #3a3340;">
|
||||||
|
<p style="margin: 0;"><a style="text-decoration:none !important; text-decoration:none; color: #a8893f;" href="{url}/topic/{topTopics.slug}"><strong>{topTopics.title}</strong></a></p>
|
||||||
|
<p style="margin: 0; font-size: 12px;"><a style="text-decoration:none !important; text-decoration:none; color: #9a9086; line-height: 16px;" href="{url}/uid/{topTopics.teaser.user.uid}"><strong>{topTopics.teaser.user.displayname}</strong></a></p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="2" style="padding: 8px 16px; font-family: Jost, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif; line-height: 16px; color: #3a3340;">
|
||||||
|
<p style="margin: 0; padding: 6px 0px; font-family: Jost, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif; font-size: 13px; line-height: 26px; color: #3a3340;">{topTopics.teaser.content}</p>
|
||||||
|
<p style="margin: 0; padding: 6px 0px; font-family: Jost, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif; line-height: 16px;">
|
||||||
|
<a style="text-decoration:none !important; text-decoration:none; text-transform: capitalize; color: #a8893f; line-height: 16px;" href="{url}/topic/{topTopics.slug}">
|
||||||
|
<small><strong><span style="color: #9a9086;">›</span> [[global:read-more]]</strong></small>
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</li>
|
||||||
|
{{{ if !@last }}}
|
||||||
|
<li style="text-decoration: none; list-style-type: none; margin: 0px 64px 16px 64px; border-bottom: 1px solid #e2d8c4"></li>
|
||||||
|
{{{ end }}}
|
||||||
|
{{{ end }}}
|
||||||
|
</ul>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{{{ end }}}
|
||||||
|
{{{ if popularTopics.length }}}
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 0px 40px; font-family: Jost, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif; font-size: 15px; line-height: 20px; color: #3a3340;">
|
||||||
|
<h1 style="margin: 16px 0 24px 0; font-family: Cinzel, Georgia, 'Times New Roman', serif; font-size: 18px; line-height: 21px; color: #9a9086; font-weight: normal;">[[email:digest.popular-topics, {site_title}]]</h1>
|
||||||
|
<ul style="margin: 0; padding: 0;">
|
||||||
|
{{{ each popularTopics }}}
|
||||||
|
<li style="text-decoration: none; list-style-type: none; padding-bottom: 0.5em;">
|
||||||
|
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 6px 16px; font-family: Jost, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif; width: 32px; vertical-align: middle;">{renderDigestAvatar(@value)}</td>
|
||||||
|
<td style="padding: 6px 16px; font-family: Jost, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif; line-height: 16px; color: #3a3340;">
|
||||||
|
<p style="margin: 0;"><a style="text-decoration:none !important; text-decoration:none; color: #a8893f;" href="{url}/topic/{popularTopics.slug}"><strong>{popularTopics.title}</strong></a></p>
|
||||||
|
<p style="margin: 0; font-size: 12px;"><a style="text-decoration:none !important; text-decoration:none; color: #9a9086; line-height: 16px;" href="{url}/uid/{popularTopics.teaser.user.uid}"><strong>{popularTopics.teaser.user.displayname}</strong></a></p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="2" style="padding: 8px 16px; font-family: Jost, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif; line-height: 16px; color: #3a3340;">
|
||||||
|
<p style="margin: 0; padding: 6px 0px; font-family: Jost, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif; font-size: 13px; line-height: 26px; color: #3a3340;">{popularTopics.teaser.content}</p>
|
||||||
|
<p style="margin: 0; padding: 6px 0px; font-family: Jost, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif; line-height: 16px;">
|
||||||
|
<a style="text-decoration:none !important; text-decoration:none; text-transform: capitalize; color: #a8893f; line-height: 16px;" href="{url}/topic/{popularTopics.slug}">
|
||||||
|
<small><strong><span style="color: #9a9086;">›</span> [[global:read-more]]</strong></small>
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</li>
|
||||||
|
{{{ if !@last }}}
|
||||||
|
<li style="text-decoration: none; list-style-type: none; margin: 0px 64px 16px 64px; border-bottom: 1px solid #e2d8c4"></li>
|
||||||
|
{{{ end }}}
|
||||||
|
{{{ end }}}
|
||||||
|
</ul>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{{{ end }}}
|
||||||
|
{{{ if recent.length }}}
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 0px 40px; font-family: Jost, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif; font-size: 15px; line-height: 20px; color: #3a3340;">
|
||||||
|
<h1 style="margin: 16px 0 24px 0; font-family: Cinzel, Georgia, 'Times New Roman', serif; font-size: 18px; line-height: 21px; color: #9a9086; font-weight: normal;">[[email:digest.latest-topics, {site_title}]]</h1>
|
||||||
|
<ul style="margin: 0; padding: 0;">
|
||||||
|
{{{ each recent }}}
|
||||||
|
<li style="text-decoration: none; list-style-type: none; padding-bottom: 0.5em;">
|
||||||
|
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 6px 16px; font-family: Jost, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif; width: 32px; vertical-align: middle;">{renderDigestAvatar(@value)}</td>
|
||||||
|
<td style="padding: 6px 16px; font-family: Jost, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif; line-height: 16px; color: #3a3340;">
|
||||||
|
<p style="margin: 0;"><a style="text-decoration:none !important; text-decoration:none; color: #a8893f;" href="{url}/topic/{recent.slug}"><strong>{recent.title}</strong></a></p>
|
||||||
|
<p style="margin: 0; font-size: 12px;"><a style="text-decoration:none !important; text-decoration:none; color: #9a9086; line-height: 16px;" href="{url}/uid/{recent.teaser.user.uid}"><strong>{recent.teaser.user.displayname}</strong></a></p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="2" style="padding: 8px 16px; font-family: Jost, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif; line-height: 16px; color: #3a3340;">
|
||||||
|
<p style="margin: 0; padding: 6px 0px; font-family: Jost, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif; font-size: 13px; line-height: 26px; color: #3a3340;">{recent.teaser.content}</p>
|
||||||
|
<p style="margin: 0; padding: 6px 0px; font-family: Jost, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif; line-height: 16px;">
|
||||||
|
<a style="text-decoration:none !important; text-decoration:none; text-transform: capitalize; color: #a8893f; line-height: 16px;" href="{url}/topic/{recent.slug}">
|
||||||
|
<small><strong><span style="color: #9a9086;">›</span> [[global:read-more]]</strong></small>
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</li>
|
||||||
|
{{{ if !@last }}}
|
||||||
|
<li style="text-decoration: none; list-style-type: none; margin: 0px 64px 16px 64px; border-bottom: 1px solid #e2d8c4"></li>
|
||||||
|
{{{ end }}}
|
||||||
|
{{{ end }}}
|
||||||
|
</ul>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{{{ end }}}
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<!-- 1 Column Text : END -->
|
||||||
|
</table>
|
||||||
|
<!-- Email Body : END -->
|
||||||
|
<!-- IMPORT emails/partials/footer.html -->
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
<!-- IMPORT emails/partials/header.html -->
|
||||||
|
<!-- preheader -->
|
||||||
|
<div style="display: none; max-height: 0; overflow: hidden; mso-hide: all;">[[email:invitation.preheader]]</div>
|
||||||
|
<div style="display: none; max-height: 0; overflow: hidden; mso-hide: all;"> ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌</div>
|
||||||
|
<!-- Email Body : BEGIN -->
|
||||||
|
<table role="presentation" cellspacing="0" cellpadding="0" border="0" align="center" width="100%" style="max-width: 600px;">
|
||||||
|
<tr>
|
||||||
|
<td bgcolor="#fbf7ef">
|
||||||
|
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 40px 40px 6px 40px; font-family: Jost, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif; font-size: 15px; line-height: 20px; color: #3a3340;">
|
||||||
|
<h1 style="margin: 0; font-family: Cinzel, Georgia, 'Times New Roman', serif; font-size: 24px; line-height: 27px; color: #1a1418; font-weight: normal;">[[email:greeting-no-name]]</h1>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 0px 40px; font-family: Jost, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif; font-size: 15px; line-height: 20px; color: #3a3340;">
|
||||||
|
<h1 style="margin: 0 0 10px 0; font-family: Cinzel, Georgia, 'Times New Roman', serif; font-size: 18px; line-height: 21px; color: #9a9086; font-weight: normal;">[[email:invitation.text1, {username}, {site_title}]]</h1>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 20px 40px; font-family: Jost, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif; font-size: 15px; line-height: 20px; color: #3a3340;">
|
||||||
|
<p style="margin: 0; padding: 6px 0px; font-family: Jost, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif; font-size: 13px; line-height: 26px; color: #3a3340;">[[email:invitation.text2, {expireDays}]]</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 32px 40px; font-family: Jost, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif; font-size: 15px; line-height: 20px; color: #3a3340;">
|
||||||
|
<!-- Button : BEGIN -->
|
||||||
|
<table role="presentation" cellspacing="0" cellpadding="0" border="0" align="center" style="margin: auto;">
|
||||||
|
<tr>
|
||||||
|
<td style="border-radius: 3px; background: #2a1222; text-align: center;" class="button-td">
|
||||||
|
<a href="{registerLink}" style="background: #2a1222; border: 15px solid #2a1222; border-color: #c2a35a; font-family: Jost, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif; font-size: 13px; line-height: 1.1; text-align: center; text-decoration: none; display: block; border-radius: 3px; font-weight: bold;" class="button-a">
|
||||||
|
<span style="color: #f4ecd8;" class="button-link">[[email:invitation.cta]] →</span>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<!-- Button : END -->
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<!-- Email Body : END -->
|
||||||
|
<!-- IMPORT emails/partials/footer.html -->
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
<!-- IMPORT emails/partials/header.html -->
|
||||||
|
<!-- preheader -->
|
||||||
|
<div style="display: none; max-height: 0; overflow: hidden; mso-hide: all;">[[email:notification.preheader]]</div>
|
||||||
|
<div style="display: none; max-height: 0; overflow: hidden; mso-hide: all;"> ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌</div>
|
||||||
|
<!-- Email Body : BEGIN -->
|
||||||
|
<table role="presentation" cellspacing="0" cellpadding="0" border="0" align="center" width="100%" style="max-width: 600px;">
|
||||||
|
<tr>
|
||||||
|
<td bgcolor="#fbf7ef">
|
||||||
|
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 40px 40px 6px 40px; font-family: Jost, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif; font-size: 15px; line-height: 20px; color: #3a3340;">
|
||||||
|
<h1 style="margin: 0; font-family: Cinzel, Georgia, 'Times New Roman', serif; font-size: 24px; line-height: 27px; color: #1a1418; font-weight: normal;">[[email:greeting-with-name, {username}]]</h1>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 0px 40px; font-family: Jost, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif; font-size: 15px; line-height: 20px; color: #3a3340;">
|
||||||
|
<h1 style="margin: 0 0 10px 0; font-family: Cinzel, Georgia, 'Times New Roman', serif; font-size: 18px; line-height: 21px; color: #9a9086; font-weight: normal;">{intro}</h1>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="notification-body">
|
||||||
|
<td style="padding: 20px 40px; font-family: Jost, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif; font-size: 15px; line-height: 20px; color: #3a3340;">
|
||||||
|
<p style="margin: 0; padding: 6px 0px; font-family: Jost, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif; font-size: 13px; line-height: 26px; color: #3a3340;">
|
||||||
|
{body}
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 32px 40px; font-family: Jost, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif; font-size: 15px; line-height: 20px; color: #3a3340;">
|
||||||
|
<!-- Button : BEGIN -->
|
||||||
|
<table role="presentation" cellspacing="0" cellpadding="0" border="0" align="center" style="margin: auto">
|
||||||
|
<tr>
|
||||||
|
<td style="border-radius: 3px; background: #2a1222; text-align: center;" class="button-td">
|
||||||
|
<a href="{notification_url}" style="background: #2a1222; border: 15px solid #2a1222; border-color: #c2a35a; font-family: Jost, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif; font-size: 13px; line-height: 1.1; text-align: center; text-decoration: none; display: block; border-radius: 3px; font-weight: bold;" class="button-a">
|
||||||
|
<span style="color: #f4ecd8" class="button-link">[[email:notif.cta{{{ if notification.cta-type }}}-{notification.cta-type}{{{ end }}}]] →</span>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<!-- Button : END -->
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<!-- Email Body : END -->
|
||||||
|
<!-- IMPORT emails/partials/footer.html -->
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
<!-- Email Footer : BEGIN -->
|
||||||
|
<table role="presentation" cellspacing="0" cellpadding="0" border="0" align="center" width="100%" style="max-width: 680px;">
|
||||||
|
<tr>
|
||||||
|
<td height="1" bgcolor="#c2a35a" style="height: 1px; line-height: 1px; font-size: 1px; background: #c2a35a;"> </td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 28px 10px 40px 10px; width: 100%; font-size: 12px; font-family: Jost, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif; line-height: 18px; text-align: center; color: #9a9086;">
|
||||||
|
{{{ if showUnsubscribe }}}
|
||||||
|
[[email:notif.post.unsub.info]] <a href="{url}/uid/{uid}/settings" style="color: #a8893f;">[[email:unsub.cta]]</a>.
|
||||||
|
<br />[[email:notif.post.unsub.one-click]] <a href="{unsubUrl}" style="color: #a8893f;">[[email:unsubscribe]]</a>.
|
||||||
|
{{{ end }}}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<!-- Email Footer : END -->
|
||||||
|
<!--[if mso]>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<![endif]-->
|
||||||
|
</div>
|
||||||
|
</center>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,196 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html
|
||||||
|
lang="en"
|
||||||
|
xmlns="http://www.w3.org/1999/xhtml"
|
||||||
|
xmlns:v="urn:schemas-microsoft-com:vml"
|
||||||
|
xmlns:o="urn:schemas-microsoft-com:office:office"
|
||||||
|
>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<!-- utf-8 works for most cases -->
|
||||||
|
<meta name="viewport" content="width=device-width" />
|
||||||
|
<!-- Forcing initial-scale shouldn't be necessary -->
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
|
<!-- Use the latest (edge) version of IE rendering engine -->
|
||||||
|
<meta name="x-apple-disable-message-reformatting" />
|
||||||
|
<!-- Disable auto-scale in iOS 10 Mail entirely -->
|
||||||
|
<title>{site_title}</title>
|
||||||
|
<!-- The title tag shows in email notifications, like Android 4.4. -->
|
||||||
|
<!-- Web Font / @font-face : BEGIN -->
|
||||||
|
<!-- NOTE: If web fonts are not required, lines 10 - 27 can be safely removed. -->
|
||||||
|
<!-- Desktop Outlook chokes on web font references and defaults to Times New Roman, so we force a safe fallback font. -->
|
||||||
|
<!--[if mso]>
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
font-family: sans-serif !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<![endif]-->
|
||||||
|
<!-- All other clients get the webfont reference; some will render the font and others will silently fail to the fallbacks. More on that here: http://stylecampaign.com/blog/2015/02/webfont-support-in-email/ -->
|
||||||
|
<!--[if !mso]><!-->
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Cinzel:wght@400;600&family=Jost:wght@400;600&display=swap" rel="stylesheet" />
|
||||||
|
<!--<![endif]-->
|
||||||
|
<!-- Web Font / @font-face : END -->
|
||||||
|
<!-- CSS Reset -->
|
||||||
|
<style>
|
||||||
|
/* What it does: Remove spaces around the email design added by some email clients. */
|
||||||
|
/* Beware: It can remove the padding / margin and add a background color to the compose a reply window. */
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
margin: 0 auto !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
height: 100% !important;
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
/* What it does: Stops email clients resizing small text. */
|
||||||
|
* {
|
||||||
|
-ms-text-size-adjust: 100%;
|
||||||
|
-webkit-text-size-adjust: 100%;
|
||||||
|
}
|
||||||
|
/* What it does: Centers email on Android 4.4 */
|
||||||
|
div[style*="margin: 16px 0"] {
|
||||||
|
margin: 0 !important;
|
||||||
|
}
|
||||||
|
/* What it does: Stops Outlook from adding extra spacing to tables. */
|
||||||
|
table,
|
||||||
|
td {
|
||||||
|
mso-table-lspace: 0pt !important;
|
||||||
|
mso-table-rspace: 0pt !important;
|
||||||
|
}
|
||||||
|
/* What it does: Fixes webkit padding issue. Fix for Yahoo mail table alignment bug. Applies table-layout to the first 2 tables then removes for anything nested deeper. */
|
||||||
|
table {
|
||||||
|
border-spacing: 0 !important;
|
||||||
|
border-collapse: collapse !important;
|
||||||
|
table-layout: fixed !important;
|
||||||
|
margin: 0 auto !important;
|
||||||
|
}
|
||||||
|
table table table {
|
||||||
|
table-layout: auto;
|
||||||
|
}
|
||||||
|
/* What it does: Uses a better rendering method when resizing images in IE. */
|
||||||
|
img {
|
||||||
|
-ms-interpolation-mode: bicubic;
|
||||||
|
}
|
||||||
|
/* What it does: A work-around for email clients meddling in triggered links. */
|
||||||
|
*[x-apple-data-detectors], /* iOS */
|
||||||
|
.x-gmail-data-detectors, /* Gmail */
|
||||||
|
.x-gmail-data-detectors *,
|
||||||
|
.aBn {
|
||||||
|
border-bottom: 0 !important;
|
||||||
|
cursor: default !important;
|
||||||
|
color: inherit !important;
|
||||||
|
text-decoration: none !important;
|
||||||
|
font-size: inherit !important;
|
||||||
|
font-family: inherit !important;
|
||||||
|
font-weight: inherit !important;
|
||||||
|
line-height: inherit !important;
|
||||||
|
}
|
||||||
|
/* What it does: Prevents Gmail from displaying an download button on large, non-linked images. */
|
||||||
|
.a6S {
|
||||||
|
display: none !important;
|
||||||
|
opacity: 0.01 !important;
|
||||||
|
}
|
||||||
|
/* If the above doesn't work, add a .g-img class to any image in question. */
|
||||||
|
img.g-img + div {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
/* What it does: Prevents underlining the button text in Windows 10 */
|
||||||
|
.button-link {
|
||||||
|
text-decoration: none !important;
|
||||||
|
}
|
||||||
|
/* What it does: Removes right gutter in Gmail iOS app: https://github.com/TedGoas/Cerberus/issues/89 */
|
||||||
|
/* Create one of these media queries for each additional viewport size you'd like to fix */
|
||||||
|
/* Thanks to Eric Lepetit (@ericlepetitsf) for help troubleshooting */
|
||||||
|
@media only screen and (min-device-width: 375px) and (max-device-width: 413px) {
|
||||||
|
/* iPhone 6 and 6+ */
|
||||||
|
.email-container {
|
||||||
|
min-width: 375px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<!-- Progressive Enhancements -->
|
||||||
|
<style>
|
||||||
|
/* What it does: Hover styles for buttons */
|
||||||
|
.button-td,
|
||||||
|
.button-a {
|
||||||
|
transition: all 100ms ease-in;
|
||||||
|
}
|
||||||
|
.button-td:hover,
|
||||||
|
.button-a:hover {
|
||||||
|
background: #3a1830 !important;
|
||||||
|
border-color: #3a1830 !important;
|
||||||
|
}
|
||||||
|
/* Media Queries */
|
||||||
|
@media screen and (max-width: 600px) {
|
||||||
|
/* What it does: Adjust typography on small screens to improve readability */
|
||||||
|
.email-container p {
|
||||||
|
font-size: 17px !important;
|
||||||
|
line-height: 26px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.notification-body img {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<!-- What it does: Makes background images in 72ppi Outlook render at correct size. -->
|
||||||
|
<!--[if gte mso 9]>
|
||||||
|
<xml>
|
||||||
|
<o:OfficeDocumentSettings>
|
||||||
|
<o:AllowPNG />
|
||||||
|
<o:PixelsPerInch>96</o:PixelsPerInch>
|
||||||
|
</o:OfficeDocumentSettings>
|
||||||
|
</xml>
|
||||||
|
<![endif]-->
|
||||||
|
</head>
|
||||||
|
<body
|
||||||
|
width="100%"
|
||||||
|
bgcolor="#ece6da"
|
||||||
|
style="margin: 0; mso-line-height-rule: exactly"
|
||||||
|
>
|
||||||
|
<center style="width: 100%; background: #ece6da; text-align: left">
|
||||||
|
<!--
|
||||||
|
Set the email width. Defined in two places:
|
||||||
|
1. max-width for all clients except Desktop Windows Outlook, allowing the email to squish on narrow but never go wider than 600px.
|
||||||
|
2. MSO tags for Desktop Windows Outlook enforce a 600px width.
|
||||||
|
-->
|
||||||
|
<div
|
||||||
|
style="max-width: 600px; margin: auto;{{{ if rtl }}} text-align: right; direction: rtl;{{{ end }}}"
|
||||||
|
class="email-container"
|
||||||
|
>
|
||||||
|
<!--[if mso]>
|
||||||
|
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="600" align="center">
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<![endif]-->
|
||||||
|
<!-- Letterhead Band : BEGIN -->
|
||||||
|
<table
|
||||||
|
role="presentation"
|
||||||
|
cellspacing="0"
|
||||||
|
cellpadding="0"
|
||||||
|
border="0"
|
||||||
|
align="center"
|
||||||
|
width="100%"
|
||||||
|
style="max-width: 600px"
|
||||||
|
>
|
||||||
|
<tr>
|
||||||
|
<td bgcolor="#18141d" style="padding: 24px 0; text-align: center; background: #18141d;">
|
||||||
|
{{{ if logo.src }}}
|
||||||
|
<img
|
||||||
|
src="{logo.src}"
|
||||||
|
height="auto"
|
||||||
|
width="{logo.width}"
|
||||||
|
alt="{site_title}"
|
||||||
|
border="0"
|
||||||
|
style="height: auto; width: {logo.width}px; background: #18141d; font-family: Cinzel, Georgia, 'Times New Roman', serif; font-size: 15px; line-height: 20px; color: #c2a35a;"
|
||||||
|
/>
|
||||||
|
{{{ else }}}
|
||||||
|
<span style="font-family: Cinzel, Georgia, 'Times New Roman', serif; font-size: 22px; color: #c2a35a;">{site_title}</span>
|
||||||
|
{{{ end }}}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td height="2" bgcolor="#c2a35a" style="height: 2px; line-height: 2px; font-size: 2px; background: #c2a35a;"> </td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<!-- Letterhead Band : END -->
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<p><strong>[[post-queue:category]]</strong></p>
|
||||||
|
<p><a href="{category.url}" style="color: #a8893f;">{category.name}</a></p>
|
||||||
|
<p>
|
||||||
|
<strong
|
||||||
|
>{{{ if topic.tid }}}[[post-queue:topic]]{{{ else }}}[[post-queue:title]]{{{
|
||||||
|
end }}}</strong
|
||||||
|
>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{{{ if topic.url }}}<a href="{topic.url}" style="color: #a8893f;">{topic.title}</a>{{{ else
|
||||||
|
}}}{topic.title}{{{ end }}}
|
||||||
|
</p>
|
||||||
|
<p><strong>[[post-queue:user]]</strong></p>
|
||||||
|
<p>
|
||||||
|
{{{ if user.url }}}<a href="{user.url}" style="color: #a8893f;">{user.username}</a>{{{ else
|
||||||
|
}}}{user.username}{{{ end }}}
|
||||||
|
</p>
|
||||||
|
<p style="color: #3a3340;">{content}</p>
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
<!-- IMPORT emails/partials/header.html -->
|
||||||
|
<!-- preheader -->
|
||||||
|
<div style="display: none; max-height: 0; overflow: hidden; mso-hide: all;">[[email:registration_accepted.preheader]]</div>
|
||||||
|
<div style="display: none; max-height: 0; overflow: hidden; mso-hide: all;"> ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌</div>
|
||||||
|
<!-- Email Body : BEGIN -->
|
||||||
|
<table role="presentation" cellspacing="0" cellpadding="0" border="0" align="center" width="100%" style="max-width: 600px;">
|
||||||
|
<tr>
|
||||||
|
<td bgcolor="#fbf7ef">
|
||||||
|
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 40px 40px 6px 40px; font-family: Jost, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif; font-size: 15px; line-height: 20px; color: #3a3340;">
|
||||||
|
<h1 style="margin: 0; font-family: Cinzel, Georgia, 'Times New Roman', serif; font-size: 24px; line-height: 27px; color: #1a1418; font-weight: normal;">[[email:welcome.text1, {site_title}]]</h1>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 0px 40px; font-family: Jost, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif; font-size: 15px; line-height: 20px; color: #3a3340;">
|
||||||
|
<h1 style="margin: 0 0 10px 0; font-family: Cinzel, Georgia, 'Times New Roman', serif; font-size: 18px; line-height: 21px; color: #9a9086; font-weight: normal;">[[email:welcome.text3]]</h1>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 32px 40px; font-family: Jost, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif; font-size: 15px; line-height: 20px; color: #3a3340;">
|
||||||
|
<!-- Button : BEGIN -->
|
||||||
|
<table role="presentation" cellspacing="0" cellpadding="0" border="0" align="center" style="margin: auto">
|
||||||
|
<tr>
|
||||||
|
<td style="border-radius: 3px; background: #2a1222; text-align: center;" class="button-td">
|
||||||
|
<a href="{url}" style="background: #2a1222; border: 15px solid #2a1222; border-color: #c2a35a; font-family: Jost, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif; font-size: 13px; line-height: 1.1; text-align: center; text-decoration: none; display: block; border-radius: 3px; font-weight: bold;" class="button-a">
|
||||||
|
<span style="color: #f4ecd8" class="button-link">[[email:digest.cta, {site_title}]] →</span>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<!-- Button : END -->
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<!-- Email Body : END -->
|
||||||
|
<!-- IMPORT emails/partials/footer.html -->
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
<!-- IMPORT emails/partials/header.html -->
|
||||||
|
<!-- preheader -->
|
||||||
|
<div style="display: none; max-height: 0; overflow: hidden; mso-hide: all;">[[email:reset.preheader]]</div>
|
||||||
|
<div style="display: none; max-height: 0; overflow: hidden; mso-hide: all;"> ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌</div>
|
||||||
|
<!-- Email Body : BEGIN -->
|
||||||
|
<table role="presentation" cellspacing="0" cellpadding="0" border="0" align="center" width="100%" style="max-width: 600px;">
|
||||||
|
<tr>
|
||||||
|
<td bgcolor="#fbf7ef">
|
||||||
|
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 40px 40px 6px 40px; font-family: Jost, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif; font-size: 15px; line-height: 20px; color: #3a3340;">
|
||||||
|
<h1 style="margin: 0; font-family: Cinzel, Georgia, 'Times New Roman', serif; font-size: 24px; line-height: 27px; color: #1a1418; font-weight: normal;">[[email:greeting-no-name]]</h1>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 0px 40px; font-family: Jost, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif; font-size: 15px; line-height: 20px; color: #3a3340;">
|
||||||
|
<h1 style="margin: 0 0 10px 0; font-family: Cinzel, Georgia, 'Times New Roman', serif; font-size: 18px; line-height: 21px; color: #9a9086; font-weight: normal;">[[email:reset.text1]]</h1>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 20px 40px; font-family: Jost, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif; font-size: 15px; line-height: 20px; color: #3a3340;">
|
||||||
|
<p style="margin: 0">[[email:reset.text2]]</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 32px 40px; font-family: Jost, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif; font-size: 15px; line-height: 20px; color: #3a3340;">
|
||||||
|
<!-- Button : BEGIN -->
|
||||||
|
<table role="presentation" cellspacing="0" cellpadding="0" border="0" align="center" style="margin: auto">
|
||||||
|
<tr>
|
||||||
|
<td style="border-radius: 3px; background: #2a1222; text-align: center;" class="button-td">
|
||||||
|
<a href="{reset_link}" style="background: #2a1222; border: 15px solid #2a1222; border-color: #c2a35a; font-family: Jost, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif; font-size: 13px; line-height: 1.1; text-align: center; text-decoration: none; display: block; border-radius: 3px; font-weight: bold;" class="button-a">
|
||||||
|
<span style="color: #f4ecd8" class="button-link">[[email:reset.cta]] →</span>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<!-- Button : END -->
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<!-- Email Body : END -->
|
||||||
|
<!-- IMPORT emails/partials/footer.html -->
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<!-- IMPORT emails/partials/header.html -->
|
||||||
|
<!-- preheader -->
|
||||||
|
<div style="display: none; max-height: 0; overflow: hidden; mso-hide: all;">[[email:reset_notify.preheader]]</div>
|
||||||
|
<div style="display: none; max-height: 0; overflow: hidden; mso-hide: all;"> ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌</div>
|
||||||
|
<!-- Email Body : BEGIN -->
|
||||||
|
<table role="presentation" cellspacing="0" cellpadding="0" border="0" align="center" width="100%" style="max-width: 600px;">
|
||||||
|
<tr>
|
||||||
|
<td bgcolor="#fbf7ef">
|
||||||
|
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 40px 40px 6px 40px; font-family: Jost, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif; font-size: 15px; line-height: 20px; color: #3a3340;">
|
||||||
|
<h1 style="margin: 0; font-family: Cinzel, Georgia, 'Times New Roman', serif; font-size: 24px; line-height: 27px; color: #1a1418; font-weight: normal;">[[email:greeting-with-name, {username}]]</h1>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 0px 40px; font-family: Jost, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif; font-size: 15px; line-height: 20px; color: #3a3340;">
|
||||||
|
<h1 style="margin: 0 0 10px 0; font-family: Cinzel, Georgia, 'Times New Roman', serif; font-size: 18px; line-height: 21px; color: #9a9086; font-weight: normal;">[[email:reset.notify.text1, {date}]]</h1>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 20px 40px; font-family: Jost, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif; font-size: 15px; line-height: 20px; color: #3a3340;">
|
||||||
|
<p style="margin: 0">[[email:reset.notify.text2]]</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<!-- Email Body : END -->
|
||||||
|
<!-- IMPORT emails/partials/footer.html -->
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<!-- IMPORT emails/partials/header.html -->
|
||||||
|
<!-- preheader -->
|
||||||
|
<div style="display: none; max-height: 0; overflow: hidden; mso-hide: all;">[[email:test.preheader]]</div>
|
||||||
|
<div style="display: none; max-height: 0; overflow: hidden; mso-hide: all;"> ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌</div>
|
||||||
|
<!-- Email Body : BEGIN -->
|
||||||
|
<table role="presentation" cellspacing="0" cellpadding="0" border="0" align="center" width="100%" style="max-width: 600px;">
|
||||||
|
<tr>
|
||||||
|
<td bgcolor="#fbf7ef">
|
||||||
|
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 40px 40px 6px 40px; font-family: Jost, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif; font-size: 15px; line-height: 20px; color: #3a3340;">
|
||||||
|
<h1 style="margin: 0; font-family: Cinzel, Georgia, 'Times New Roman', serif; font-size: 24px; line-height: 27px; color: #1a1418; font-weight: normal;">[[email:greeting-no-name]]</h1>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 0px 40px; font-family: Jost, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif; font-size: 15px; line-height: 20px; color: #3a3340;">
|
||||||
|
<h1 style="margin: 0 0 10px 0; font-family: Cinzel, Georgia, 'Times New Roman', serif; font-size: 18px; line-height: 21px; color: #9a9086; font-weight: normal;">[[email:test.text1]]</h1>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<!-- Email Body : END -->
|
||||||
|
<!-- IMPORT emails/partials/footer.html -->
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
<!-- IMPORT emails/partials/header.html -->
|
||||||
|
<!-- preheader -->
|
||||||
|
<div style="display: none; max-height: 0; overflow: hidden; mso-hide: all;">[[email:verify-email.preheader]]</div>
|
||||||
|
<div style="display: none; max-height: 0; overflow: hidden; mso-hide: all;"> ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌</div>
|
||||||
|
<!-- Email Body : BEGIN -->
|
||||||
|
<table role="presentation" cellspacing="0" cellpadding="0" border="0" align="center" width="100%" style="max-width: 600px;">
|
||||||
|
<tr>
|
||||||
|
<td bgcolor="#fbf7ef">
|
||||||
|
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 40px 40px 6px 40px; font-family: Jost, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif; font-size: 15px; line-height: 20px; color: #3a3340;">
|
||||||
|
<h1 style="margin: 0; font-family: Cinzel, Georgia, 'Times New Roman', serif; font-size: 24px; line-height: 27px; color: #1a1418; font-weight: normal;">[[email:greeting-with-name, {username}]]</h1>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 0px 40px; font-family: Jost, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif; font-size: 15px; line-height: 20px; color: #3a3340;">
|
||||||
|
<h1 style="margin: 0 0 10px 0; font-family: Cinzel, Georgia, 'Times New Roman', serif; font-size: 18px; line-height: 21px; color: #9a9086; font-weight: normal;">[[email:email.verify.text1]]</h1>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 20px 40px; font-family: Jost, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif; font-size: 15px; line-height: 20px; color: #3a3340;">
|
||||||
|
<p style="margin: 0">[[email:email.verify.text2]]</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 20px 40px; font-family: Jost, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif; font-size: 15px; line-height: 20px; color: #3a3340;">
|
||||||
|
<p style="margin: 0">[[email:email.verify.text3, {email}]]</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 32px 40px; font-family: Jost, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif; font-size: 15px; line-height: 20px; color: #3a3340;">
|
||||||
|
<!-- Button : BEGIN -->
|
||||||
|
<table role="presentation" cellspacing="0" cellpadding="0" border="0" align="center" style="margin: auto">
|
||||||
|
<tr>
|
||||||
|
<td style="border-radius: 3px; background: #2a1222; text-align: center;" class="button-td">
|
||||||
|
<a href="{confirm_link}" style="background: #2a1222; border: 15px solid #2a1222; border-color: #c2a35a; font-family: Jost, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif; font-size: 13px; line-height: 1.1; text-align: center; text-decoration: none; display: block; border-radius: 3px; font-weight: bold;" class="button-a">
|
||||||
|
<span style="color: #f4ecd8" class="button-link">[[email:welcome.cta]] →</span>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<!-- Button : END -->
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<!-- Email Body : END -->
|
||||||
|
<!-- IMPORT emails/partials/footer.html -->
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
<!-- IMPORT emails/partials/header.html -->
|
||||||
|
<!-- preheader -->
|
||||||
|
<div style="display: none; max-height: 0; overflow: hidden; mso-hide: all;">[[email:welcome.preheader]]</div>
|
||||||
|
<div style="display: none; max-height: 0; overflow: hidden; mso-hide: all;"> ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌</div>
|
||||||
|
<!-- Email Body : BEGIN -->
|
||||||
|
<table role="presentation" cellspacing="0" cellpadding="0" border="0" align="center" width="100%" style="max-width: 600px;">
|
||||||
|
<tr>
|
||||||
|
<td bgcolor="#fbf7ef">
|
||||||
|
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 40px 40px 6px 40px; font-family: Jost, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif; font-size: 15px; line-height: 20px; color: #3a3340;">
|
||||||
|
<h1 style="margin: 0; font-family: Cinzel, Georgia, 'Times New Roman', serif; font-size: 24px; line-height: 27px; color: #1a1418; font-weight: normal;">[[email:greeting-with-name, {username}]]</h1>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 0px 40px; font-family: Jost, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif; font-size: 15px; line-height: 20px; color: #3a3340;">
|
||||||
|
<h1 style="margin: 0 0 10px 0; font-family: Cinzel, Georgia, 'Times New Roman', serif; font-size: 18px; line-height: 21px; color: #9a9086; font-weight: normal;">[[email:email.verify.text1]]</h1>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 20px 40px; font-family: Jost, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif; font-size: 15px; line-height: 20px; color: #3a3340;">
|
||||||
|
<p style="margin: 0">[[email:email.verify.text2]]</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 20px 40px; font-family: Jost, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif; font-size: 15px; line-height: 20px; color: #3a3340;">
|
||||||
|
<p style="margin: 0">[[email:email.verify.text3, {email}]]</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 32px 40px; font-family: Jost, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif; font-size: 15px; line-height: 20px; color: #3a3340;">
|
||||||
|
<!-- Button : BEGIN -->
|
||||||
|
<table role="presentation" cellspacing="0" cellpadding="0" border="0" align="center" style="margin: auto">
|
||||||
|
<tr>
|
||||||
|
<td style="border-radius: 3px; background: #2a1222; text-align: center;" class="button-td">
|
||||||
|
<a href="{confirm_link}" style="background: #2a1222; border: 15px solid #2a1222; border-color: #c2a35a; font-family: Jost, system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif; font-size: 13px; line-height: 1.1; text-align: center; text-decoration: none; display: block; border-radius: 3px; font-weight: bold;" class="button-a">
|
||||||
|
<span style="color: #f4ecd8" class="button-link">[[email:welcome.cta]] →</span>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<!-- Button : END -->
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<!-- Email Body : END -->
|
||||||
|
<!-- IMPORT emails/partials/footer.html -->
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"reset.preheader": "Reset your Shadows Over Westgate password",
|
||||||
|
"welcome.preheader": "Confirm your email to enter Shadows Over Westgate",
|
||||||
|
"verify-email.preheader": "Confirm your new email address",
|
||||||
|
"reset_notify.preheader": "Your Shadows Over Westgate password was changed",
|
||||||
|
"registration_accepted.preheader": "Your Shadows Over Westgate account is approved",
|
||||||
|
"invitation.preheader": "You have been invited to Shadows Over Westgate",
|
||||||
|
"banned.preheader": "Your Shadows Over Westgate account status has changed",
|
||||||
|
"test.preheader": "Test email from Shadows Over Westgate",
|
||||||
|
"notification.preheader": "You have a new notification on Shadows Over Westgate",
|
||||||
|
"digest.preheader": "Your Shadows Over Westgate activity digest"
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@
|
|||||||
],
|
],
|
||||||
"scripts": ["public/client.js", "../nodebb-theme-harmony/public/harmony.js"],
|
"scripts": ["public/client.js", "../nodebb-theme-harmony/public/harmony.js"],
|
||||||
"templates": "templates",
|
"templates": "templates",
|
||||||
|
"languages": "languages",
|
||||||
"modules": {
|
"modules": {
|
||||||
"../admin/plugins/theme-quickstart.js": "../nodebb-theme-harmony/public/admin.js",
|
"../admin/plugins/theme-quickstart.js": "../nodebb-theme-harmony/public/admin.js",
|
||||||
"../client/account/theme.js": "../nodebb-theme-harmony/public/settings.js"
|
"../client/account/theme.js": "../nodebb-theme-harmony/public/settings.js"
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
// ponytail: 30-line structural guard, not a render test. Catches drift, not pixels.
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const dir = path.join(__dirname, '..', 'email');
|
||||||
|
const files = fs.readdirSync(dir).filter((f) => f.endsWith('.html'));
|
||||||
|
|
||||||
|
const failures = [];
|
||||||
|
for (const f of files) {
|
||||||
|
const src = fs.readFileSync(path.join(dir, f), 'utf8');
|
||||||
|
const trimmed = src.trim();
|
||||||
|
const checks = [
|
||||||
|
[
|
||||||
|
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'],
|
||||||
|
];
|
||||||
|
for (const [check, msg] of checks) {
|
||||||
|
if (check instanceof RegExp ? !check.test(src) : !check) failures.push(`${f}: ${msg}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (failures.length) {
|
||||||
|
console.error('Email template check FAILED:\n' + failures.join('\n'));
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Email template check passed (${files.length} templates).`);
|
||||||
Reference in New Issue
Block a user