INTERNATIONAL CHINESE PLATFORM
-完成你的平台注册
-请在注册页面输入下面的六位验证码:
-${code}
-验证码有效期至 ${expiry}。如果这不是你的操作,请忽略本邮件。
-From f23e54c84b1099c0b76540fe9c4788b55741f621 Mon Sep 17 00:00:00 2001
From: Henrythefoodie <13022037121@163.com>
Date: Fri, 24 Jul 2026 11:28:43 +0800
Subject: [PATCH] Polish registration verification email
---
...-07-24-luxury-verification-email-design.md | 58 +++++
.../2026-07-24-luxury-verification-email.md | 102 +++++++++
server/services/mail-provider.js | 206 +++++++++++++++++-
server/test/mail-provider.test.js | 68 +++++-
server/test/mail-relay.test.js | 3 +
5 files changed, 425 insertions(+), 12 deletions(-)
create mode 100644 docs/plans/2026-07-24-luxury-verification-email-design.md
create mode 100644 docs/plans/2026-07-24-luxury-verification-email.md
diff --git a/docs/plans/2026-07-24-luxury-verification-email-design.md b/docs/plans/2026-07-24-luxury-verification-email-design.md
new file mode 100644
index 0000000..b826169
--- /dev/null
+++ b/docs/plans/2026-07-24-luxury-verification-email-design.md
@@ -0,0 +1,58 @@
+# Luxury verification email design
+
+## Goal
+
+Turn the registration verification email into a trustworthy brand touchpoint
+without weakening deliverability, accessibility, security, or support for older
+mail clients.
+
+## Visual direction
+
+The selected direction is **Eastern academy meets international editorial**.
+It reuses the product palette instead of introducing a separate email brand:
+
+- warm ivory `#fffdf7` and parchment `#f1eee5` for the canvas;
+- ink teal `#172c35` for the main panel and typography;
+- cinnabar `#bf3d2f` for the verification action;
+- restrained champagne gold `#c9a86a` for dividers and small details.
+
+The memorable element is a six-digit code presented like a contemporary
+digital seal. Generous whitespace, one display-serif heading, precise
+bilingual microcopy, and thin ornamental rules provide the premium character.
+There are no stock illustrations, gradients, tracking pixels, remote fonts, or
+generic app-style cards.
+
+## Information hierarchy
+
+1. Hidden preheader: explains that this is a time-sensitive registration step
+ without exposing the code on a lock screen.
+2. Brand masthead: Chinese platform name, small English descriptor, and a
+ typographic seal.
+3. Main message: concise welcome and one instruction.
+4. Verification panel: six-digit code, ten-minute duration, and exact expiry in
+ Asia/Shanghai.
+5. Security note: never share the code; ignore the email when unrequested.
+6. Quiet footer: transactional-email explanation and copyright line.
+
+## Architecture and compatibility
+
+`createVerificationMessage` remains the only renderer. The HMAC relay payload,
+SMTP transport configuration, API validation, and registration flow do not
+change. The email uses presentation tables and inline styles because Outlook
+desktop still relies on a Word-based renderer. A small mobile media query is
+progressive enhancement; the base layout remains usable when style blocks are
+removed.
+
+Dynamic values are HTML-escaped even though the API already restricts the code
+to six digits. The HTML contains no remote image, script, form, or CSS URL. A
+complete plain-text alternative remains mandatory.
+
+## Acceptance criteria
+
+- Gmail accepts the production message and the API returns no development code.
+- The message is readable at 320 px and 640 px widths.
+- The code is prominent, selectable, and present in both HTML and plain text.
+- Content remains understandable with images disabled and CSS partially
+ stripped.
+- Automated tests protect subject, expiry, escaping, structure, and the absence
+ of remote assets.
diff --git a/docs/plans/2026-07-24-luxury-verification-email.md b/docs/plans/2026-07-24-luxury-verification-email.md
new file mode 100644
index 0000000..0c91896
--- /dev/null
+++ b/docs/plans/2026-07-24-luxury-verification-email.md
@@ -0,0 +1,102 @@
+# Luxury Verification Email Implementation Plan
+
+> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
+
+**Goal:** Ship a premium, responsive registration-code email that preserves the
+existing secure relay and broad email-client compatibility.
+
+**Architecture:** Keep rendering in `createVerificationMessage` and leave the
+Railway-to-Vercel HMAC contract unchanged. Build a self-contained, table-based
+HTML document with inline styles, a plain-text fallback, HTML escaping, and no
+remote assets.
+
+**Tech Stack:** Node.js, Nodemailer, semantic HTML email, inline CSS, Node test
+runner, Prettier, Vercel Functions.
+
+---
+
+### Task 1: Lock the message contract with tests
+
+**Files:**
+
+- Modify: `server/test/mail-provider.test.js`
+- Modify: `server/test/mail-relay.test.js`
+
+**Step 1:** Import `createVerificationMessage` and add assertions for the
+bilingual masthead, code, Shanghai expiry, presentation-table structure,
+preheader, security copy, and text alternative.
+
+**Step 2:** Add a defensive escaping case that passes HTML metacharacters
+directly to the renderer and verifies that executable markup never appears.
+
+**Step 3:** Run:
+
+```bash
+pnpm exec node --test server/test/mail-provider.test.js server/test/mail-relay.test.js
+```
+
+Expected: the new visual-contract assertions fail before implementation while
+the existing transport and HMAC tests continue to pass.
+
+### Task 2: Implement the premium renderer
+
+**Files:**
+
+- Modify: `server/services/mail-provider.js`
+
+**Step 1:** Add a small `escapeHtml` helper for renderer-owned dynamic content.
+
+**Step 2:** Improve the plain-text message with a clear title, code, expiry,
+security guidance, and support context.
+
+**Step 3:** Replace the minimal HTML fragment with a complete email document:
+hidden preheader, ink masthead, ivory body, cinnabar code panel, exact expiry,
+security note, and restrained footer.
+
+**Step 4:** Keep every critical style inline, use `role="presentation"` layout
+tables, and avoid external images, fonts, scripts, forms, and CSS URLs.
+
+**Step 5:** Re-run the two focused test files. Expected: all tests pass.
+
+### Task 3: Validate the visual output
+
+**Files:**
+
+- Temporary artifact only: `C:\tmp\international-chinese-verification-email.html`
+- Temporary artifact only: `C:\tmp\international-chinese-verification-email.png`
+
+**Step 1:** Render a deterministic example using
+`createVerificationMessage`.
+
+**Step 2:** Capture the HTML at a mobile-friendly viewport and inspect the
+screenshot for hierarchy, clipping, contrast, spacing, and code legibility.
+
+**Step 3:** Fix any visible issue before continuing and delete temporary
+preview artifacts after review.
+
+### Task 4: Run repository verification
+
+**Files:** No new files.
+
+**Step 1:** Run `pnpm lint:check`.
+
+**Step 2:** Run `pnpm format:check`.
+
+**Step 3:** Run `pnpm test:api`.
+
+**Step 4:** Run `pnpm build`.
+
+Expected: every command exits successfully.
+
+### Task 5: Release and production smoke test
+
+**Files:** Commit the renderer, tests, and design documents.
+
+**Step 1:** Commit and push `codex/luxury-verification-email`.
+
+**Step 2:** Merge only after GitHub CI and Vercel preview checks pass.
+
+**Step 3:** Confirm Railway and Vercel production deployments are healthy.
+
+**Step 4:** Request a production verification code through the public API and
+confirm HTTP 200, an expiry timestamp, and no `developmentCode`.
diff --git a/server/services/mail-provider.js b/server/services/mail-provider.js
index 7e0c120..a32c6b4 100644
--- a/server/services/mail-provider.js
+++ b/server/services/mail-provider.js
@@ -2,27 +2,211 @@ import { createHmac } from 'node:crypto'
import nodemailer from 'nodemailer'
+const HTML_ENTITIES = Object.freeze({
+ '&': '&',
+ '<': '<',
+ '>': '>',
+ '"': '"',
+ "'": '''
+})
+
+function escapeHtml(value) {
+ return String(value).replace(
+ /[&<>"']/g,
+ (character) => HTML_ENTITIES[character]
+ )
+}
+
function createVerificationMessage({ email, code, expiresAt, mailFrom }) {
+ const expiryDate = new Date(expiresAt)
+ if (Number.isNaN(expiryDate.getTime())) {
+ throw new TypeError('Verification code expiry is invalid')
+ }
const expiry = new Intl.DateTimeFormat('zh-CN', {
dateStyle: 'medium',
timeStyle: 'short',
timeZone: 'Asia/Shanghai'
- }).format(new Date(expiresAt))
+ }).format(expiryDate)
+ const expiryWithZone = `${expiry}(北京时间)`
+ const safeCode = escapeHtml(code)
+ const safeExpiry = escapeHtml(expiryWithZone)
return {
from: mailFrom,
to: email,
subject: '国际中文学习平台注册验证码',
- text: `你的注册验证码是 ${code},有效期至 ${expiry}。请勿向任何人透露此验证码。`,
- html: `
- INTERNATIONAL CHINESE PLATFORM 请在注册页面输入下面的六位验证码: ${code} 验证码有效期至 ${expiry}。如果这不是你的操作,请忽略本邮件。完成你的平台注册
-
+
+
|
+