Skip to content

Latest commit

 

History

History
205 lines (165 loc) · 9.31 KB

building-webmail-technology-stack.md

File metadata and controls

205 lines (165 loc) · 9.31 KB

Building Webmail: Technology Stack

Every tool in this stack serves one goal: a static, client-only webmail app that behaves like a native client.

Stack Principles

flowchart LR
    subgraph Stack Principles
        A["SMALL RUNTIME / BIG CAPABILITY<br/>Push heavy work to workers.<br/>Load features on demand."]
        B["LOCAL-FIRST BY DEFAULT<br/>IndexedDB is the source of truth.<br/>The API only supplies deltas."]
        C["DETERMINISTIC UPDATES<br/>Static shell. SW controls updates.<br/>No surprises mid-session."]
        D["SECURITY AS BASELINE<br/>Sanitize HTML, encrypt secrets,<br/>zero third-party tracking."]
    end
Loading

Core Platform

flowchart LR
    A["Svelte 5<br/>Compile-time reactivity<br/>with runes"] --> B["Vite 5<br/>Fast dev HMR + build<br/>Vendor split"] --> C["Workbox 7<br/>Precache app shell<br/>SW updates"]
Loading
Layer Tool Version Why
Framework Svelte ^5.48 Runes ($state, $derived, $effect), compiled reactivity, tiny runtime
Build Vite ^5.0 Sub-second HMR, optimized chunking, ES modules
PWA Workbox ^7.0 Reliable precaching, SPA fallback, cache-first
Styling Tailwind CSS ^4.1 Utility-first, purged in production
Components shadcn/ui via Bits UI ^2.15 Accessible, composable primitives
TypeScript TypeScript ^5.3 Strict mode, worker typing
Package Mgr pnpm ^9.0 Fast, disk-efficient, strict
Node Node.js 20 LTS Stable, long-term support

Data & Storage

flowchart LR
    subgraph DATA LAYER
        A["Dexie 4<br/>IndexedDB wrapper<br/>13 tables, per-account"]
        B["FlexSearch<br/>Full-text search<br/>index, per-account"]
        C["Mutation Queue<br/>Offline actions<br/>meta table"]
    end
Loading
Component Tool Version Why
Database Dexie ^4.2 Schema layer over IndexedDB, compound keys, migrations
Search FlexSearch ^0.7 Fast client-side full-text, persistent indexes
HTTP Client ky ^1.14 Lightweight, retry-aware, hooks
Email Parsing PostalMime ^2.6 RFC-compliant MIME parsing in workers

Workers & Concurrency

flowchart TD
    subgraph Workers & Concurrency
        MT["Main Thread<br/>UI + Stores"] --> SW["sync.worker<br/>API fetch, PostalMime, OpenPGP"]
        MT --> DW["db.worker<br/>Dexie CRUD, Schema"]
        MT --> SEW["search.worker<br/>FlexSearch, Indexing, Queries"]
        SVC["+ Service Worker (Workbox) for asset caching"]
    end
Loading

Every worker communicates via MessageChannel, so there is no shared memory, no contention, and no UI stalls.

Composition & Content

Component Tool Version Why
Rich Text Editor TipTap ^2.6 Extensible, ProseMirror-based, formatting
Calendar Schedule-X ^1.63 Month/week/day views, no server rendering
Sanitizer DOMPurify ^3.1 XSS protection for HTML email content
Encryption OpenPGP ^6.2 Client-side PGP decrypt/encrypt
Markdown marked ^12.0 Compose in markdown, render as HTML
Emoji emoji-picker-element ^1.21 Native emoji selection
Icons Lucide Svelte ^0.562 Tree-shakeable SVG icon library
Dates date-fns ^3.6 Lightweight date formatting/parsing

Security & Privacy

flowchart LR
    subgraph SECURITY LAYERS
        A["DOMPurify<br/>HTML email sanitized<br/>before display"]
        B["Sandboxed Iframe<br/>Email body rendered<br/>isolated"]
        C["OpenPGP<br/>End-to-end encryption<br/>in sync worker"]
        D["+ Local-first storage (no server-side UI state)<br/>+ Static hosting (immutable, no server-rendered HTML)<br/>+ Zero third-party tracking<br/>+ CSP headers via Cloudflare Worker"]
    end
Loading

Build & Quality

Tool Purpose
ESLint 9 + TypeScript Linting with strict rules
Prettier 3 Consistent formatting
Husky + lint-staged Pre-commit quality gates
Vitest 2.1 Unit tests (jsdom)
Playwright 1.57 E2E browser tests
Lighthouse CI Performance, a11y, best practices (90+)
rollup-plugin-visualizer Bundle analysis (pnpm analyze)

Build-Time Environment Variables

Vite statically replaces these values at build time via the define option in vite.config.js. Compilation swaps every occurrence in source code with the literal string, so these values do not exist at runtime as real environment variables.

Variable Source Example Value Purpose
import.meta.env.VITE_PKG_VERSION package.json → version 0.0.1 Semantic version used for clear-site-data checks
import.meta.env.VITE_APP_VERSION ${pkg.version}-${BUILD_HASH} 0.0.1-a1b2c3d4 Full version with build hash for cache busting
import.meta.env.VITE_BUILD_HASH MD5 of ${pkg.version}-${Date.now()} a1b2c3d4 Unique per-build identifier
import.meta.env.VITE_WEBMAIL_API_BASE .env or CI environment https://api.forwardemail.net API base URL (set externally, not in define)

How it works

// vite.config.js
const pkg = require('./package.json');
const BUILD_HASH = createHash('md5')
  .update(`${pkg.version}-${Date.now()}`)
  .digest('hex')
  .slice(0, 8);

export default defineConfig({
  define: {
    'import.meta.env.VITE_APP_VERSION': JSON.stringify(`${pkg.version}-${BUILD_HASH}`),
    'import.meta.env.VITE_BUILD_HASH': JSON.stringify(BUILD_HASH),
    'import.meta.env.VITE_PKG_VERSION': JSON.stringify(pkg.version),
  },
});

At build time, Vite replaces source references like:

// Source
const version = import.meta.env.VITE_PKG_VERSION;

// After build
const version = '0.0.1';

Where they're used

  • VITE_PKG_VERSION: Boot-time version check in src/main.ts compares against a server-side clear_below threshold to trigger cache clearing (see clear-site-data-spec). Also displayed in Settings and sent with feedback reports.
  • VITE_APP_VERSION: Full version identifier for cache busting and diagnostics.
  • VITE_BUILD_HASH: Unique build fingerprint for tracking deployments.

TypeScript support

Build-time variables are typed in src/env.d.ts:

interface ImportMetaEnv {
  readonly VITE_PKG_VERSION: string;
  readonly VITE_APP_VERSION: string;
  readonly VITE_BUILD_HASH: string;
}

Performance Budget

flowchart TD
    subgraph PERFORMANCE TARGETS
        direction TB
        T["Lighthouse: 90+<br/>First Paint: < 1s<br/>Cached Boot: < 200ms<br/>Main Thread Work: Minimal<br/>Bundle (gzipped): Chunked"]
        H["HOW WE HIT THEM:<br/>Svelte compiles away the framework<br/>Vendor chunk: svelte, dexie, ky, openpgp, tiptap<br/>Lazy routes: calendar, contacts, compose<br/>Virtual scrolling for message lists<br/>Workers for all CPU-heavy operations"]
    end
Loading

Development Commands

pnpm dev              # Dev server on :5174 with HMR
pnpm build            # Production build + SW generation
pnpm preview          # Preview production build
pnpm check            # Svelte type checking
pnpm analyze          # Bundle visualization → dist/stats.html

pnpm test             # Unit tests (Vitest)
pnpm test:watch       # Watch mode
pnpm test:coverage    # Coverage report
pnpm test:e2e         # E2E tests (Playwright)

pnpm lint             # ESLint check
pnpm lint:fix         # ESLint auto-fix
pnpm format           # Prettier check
pnpm format:fix       # Prettier auto-fix

Next: Worker Mesh: the three-worker architecture that keeps the UI at 60fps.