MailHub Public Landing Page Design
Goal
Replace the current “anonymous root URL → login/admin app” experience with a developer-first public marketing homepage (Resend / Mailjet-inspired). Unauthenticated visitors see product value, API/SMTP/Webhook integration paths, and bilingual CTAs; authenticated users still reach the admin console at /.
Product Decisions
| Item |
Choice |
| Audience |
Developers first |
| Scope (MVP) |
Single-page landing with in-page anchors |
| Language |
zh-CN / en-US toggle (localStorage) |
| Pricing |
None |
| Implementation |
Static landing entry (Vite landing build), decoupled from React admin bundle |
| Visual system |
Same Modern SaaS indigo tokens as admin (#4F46E5, canvas, ink) |
Non-Goals (MVP)
- Multi-page docs site, blog, changelog site
- Pricing tables or billing
- Embedding the landing page inside the React admin SPA
- Changing send/webhook business APIs
- Fake customer logos or fabricated social proof
Current Behavior (to change)
- Unauthenticated browser traffic to protected pages redirects to
/login.
/ maps to public/index.html (admin app).
- Login/register assets are public; admin is session-gated.
Routing & Auth
| Path |
Unauthenticated |
Authenticated |
GET / |
Serve landing (landing.html) |
Serve admin (index.html) |
/login, /register, /forgot-password, /resend-verification, /reset-password |
login.html |
Optional redirect to admin if already logged in (existing) |
/app (optional alias) |
Redirect to /login |
Admin console |
Server rules (src/server.js)
- Treat landing HTML/CSS/JS as login-class public assets (no session required), same pattern as
/login.html.
- For
GET / (or mapped /index root):
- If
getRequestUser is present → index.html (admin).
- Else →
landing.html.
- Do not redirect anonymous
/ to /login.
- Keep
/api/* auth unchanged.
- Optional:
GET /app → admin when authed, else /login (deep link from landing CTAs if desired). Landing primary CTAs may simply use /register and /login.
Information Architecture (single page)
Sticky header
- Brand: MH mark + MailHub
- Anchor nav: Features · Integrate · API · SMTP · Webhooks
- Language toggle: 中文 / EN
- CTAs: Log in (secondary), Sign up / Get started (primary)
Hero
- Developer-oriented headline + subcopy (self-hosted, DKIM/SPF/DMARC, logs, webhooks)
- Primary CTA →
/register
- Secondary CTA →
#api (scroll)
- Visual: dark code panel with curl
POST /api/send sample
Trust strip (light)
- Text chips only: Self-hosted · DKIM · SPF · DMARC · Delivery webhooks
- No fake logos
Features grid (5–6 cards)
- Domain + DNS automation
- SMTP Submission
- HTTP Send API + tokens
- Delivery tracking & logs
- Webhooks (
sent / bounced / failed)
- Multi-user isolation
Integrate (3 columns)
| Column |
Content |
| API |
Token auth, POST /api/send, field list, link to register |
| SMTP |
Host/ports aligned with product submission config, example snippet |
| Webhooks |
Event types + signature headers summary, “configure after login” |
Deep sections
- #api — curl + JSON body + Authorization header (match existing token docs field names)
- #smtp — ports and connection notes consistent with runtime submission
- #webhooks — event type table +
X-MailHub-Signature one-liner
Closing CTA
- Headline + Register + Log in
- No pricing
Footer
- Product name, Log in, Register, optional GitHub link
- Copyright line
Visual Design
- CSS variables aligned with admin redesign:
--mh-primary: #4F46E5
- canvas / ink / border / radius / shadow
- Light marketing layout; dark code blocks for developer feel
- Comfortable spacing; responsive stack; mobile nav collapse or wrap
- System font stack (no CDN Inter required)
Internationalization
- Dictionary module for
zh-CN and en-US
- Persist choice in
localStorage (key e.g. mailhub-landing-locale)
- Default:
navigator.language starts with zh → zh-CN, else en-US
- Toggle updates all
data-i18n nodes and document.documentElement.lang
- Code identifiers remain English; comments/labels in dictionary may switch
Engineering
Build
Add Vite multi-page entry alongside existing index and login:
- Source:
landing.html at repo root (or under a dedicated path) + src/frontend/landing/main.ts + landing.css
- Output:
public/landing.html + hashed assets under public/assets/
Alternatively pure static files under public/ without TS if simpler—prefer Vite entry for consistency with login/admin pipeline.
Client behavior
- Smooth scroll for header anchors
- Copy-to-clipboard on code samples
- No API calls required for first paint
Tests
- Server: anonymous
GET / body/title or content-type indicates landing (or path resolution helper unit test)
- Server: authenticated session
GET / resolves to admin index
- Public: landing assets reachable without cookie
- Optional: i18n dictionary has both locales for required keys
File Map (expected)
| Path |
Role |
landing.html (source) |
Landing markup |
src/frontend/landing/main.ts |
i18n, interactions |
src/frontend/landing/landing.css |
Styles |
src/frontend/landing/i18n.js |
Copy dictionary |
vite.config.ts |
landing input |
src/server.js |
Root routing + public asset allowlist |
test/server-*.test.js |
Route tests |
public/landing.html + assets |
Build output |
Delivery Order
- Landing static structure + CSS tokens + zh/en copy
- Landing JS (i18n, scroll, copy)
- Vite entry + build
- Server public routing for
/ and assets
- Tests + build gate
- Deploy per Agents.md
Success Criteria
- Anonymous
/ shows marketing landing, not login form as the only chrome
- Bilingual toggle works and persists
- Register/Login CTAs work
- Logged-in
/ still loads admin console
- Existing auth and API behavior unchanged
npm test and npm run build pass
Open Follow-ups
- Full
/docs site
- Pricing page
- Marketing analytics
- Open Graph / social meta images