{"id":44850161,"url":"https://github.com/forwardemail/mail.forwardemail.net","last_synced_at":"2026-06-27T23:01:06.415Z","repository":{"id":338147408,"uuid":"1148537453","full_name":"forwardemail/mail.forwardemail.net","owner":"forwardemail","description":"Open-Source \u0026 Privacy-focused Webmail: A privacy-first, offline-capable Progressive Web App for Forward Email. Ships as static assets and runs entirely in the browser with local caching, full-text search, and multi-account support.","archived":false,"fork":false,"pushed_at":"2026-06-27T21:00:26.000Z","size":10290,"stargazers_count":31,"open_issues_count":28,"forks_count":5,"subscribers_count":3,"default_branch":"main","last_synced_at":"2026-06-27T22:19:06.471Z","etag":null,"topics":["app","caching","javascript","js","mail","node","nodejs","offline","pwa","service-worker","webmail"],"latest_commit_sha":null,"homepage":"https://mail.forwardemail.net","language":"HTML","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/forwardemail.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-02-03T04:32:08.000Z","updated_at":"2026-06-27T20:55:33.000Z","dependencies_parsed_at":"2026-03-12T06:04:29.742Z","dependency_job_id":null,"html_url":"https://github.com/forwardemail/mail.forwardemail.net","commit_stats":null,"previous_names":["forwardemail/mail.forwardemail.net"],"tags_count":145,"template":false,"template_full_name":null,"purl":"pkg:github/forwardemail/mail.forwardemail.net","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/forwardemail%2Fmail.forwardemail.net","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/forwardemail%2Fmail.forwardemail.net/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/forwardemail%2Fmail.forwardemail.net/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/forwardemail%2Fmail.forwardemail.net/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/forwardemail","download_url":"https://codeload.github.com/forwardemail/mail.forwardemail.net/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/forwardemail%2Fmail.forwardemail.net/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34870654,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-27T02:00:06.362Z","response_time":126,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["app","caching","javascript","js","mail","node","nodejs","offline","pwa","service-worker","webmail"],"created_at":"2026-02-17T06:18:14.815Z","updated_at":"2026-06-27T23:01:06.406Z","avatar_url":"https://github.com/forwardemail.png","language":"HTML","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Forward Email - Webmail, Desktop, and Mobile\n\nThis is the official, open-source, and end-to-end encrypted webmail client for [Forward Email](https://forwardemail.net). It is available as a fast and modern web app, a cross-platform desktop app for Windows, macOS, and Linux, and a native mobile app for iOS and Android.\n\n## Downloads \u0026 Releases\n\nOfficial desktop and Android artifacts are published on the [GitHub Releases](https://github.com/forwardemail/mail.forwardemail.net/releases) page. Desktop builds are produced by the public [GitHub Actions](https://github.com/forwardemail/mail.forwardemail.net/actions) release workflows, and the desktop release matrix now targets **macOS arm64/x64**, **Windows x64/arm64**, and **Linux x64/arm64**.\n\n| Platform    | Architecture          | Download                                                                                                           | Store                     |\n| :---------- | :-------------------- | :----------------------------------------------------------------------------------------------------------------- | :------------------------ |\n| **Web**     | —                     | [mail.forwardemail.net](https://mail.forwardemail.net)                                                             | —                         |\n| **Windows** | x64                   | `.msi` / `.exe` on [GitHub Releases](https://github.com/forwardemail/mail.forwardemail.net/releases)               | —                         |\n| **Windows** | arm64                 | `-setup.exe` on [GitHub Releases](https://github.com/forwardemail/mail.forwardemail.net/releases)                  | —                         |\n| **macOS**   | Apple Silicon \u0026 Intel | `.dmg` on [GitHub Releases](https://github.com/forwardemail/mail.forwardemail.net/releases)                        | App Store (Coming Soon)   |\n| **Linux**   | x64                   | `.deb` / `.AppImage` / `.rpm` on [GitHub Releases](https://github.com/forwardemail/mail.forwardemail.net/releases) | —                         |\n| **Linux**   | arm64                 | `.deb` / `.AppImage` / `.rpm` on [GitHub Releases](https://github.com/forwardemail/mail.forwardemail.net/releases) | —                         |\n| **Android** | Universal             | `.apk` / `.aab` on [GitHub Releases](https://github.com/forwardemail/mail.forwardemail.net/releases)               | Google Play (Coming Soon) |\n| **iOS**     | arm64                 | TestFlight / App Store distribution                                                                                | App Store (Coming Soon)   |\n\n### Ubuntu / Debian installation\n\nFor **Ubuntu x64 / amd64**, download the current `.deb` asset from [GitHub Releases](https://github.com/forwardemail/mail.forwardemail.net/releases), then install it with:\n\n```bash\nsudo apt update\nsudo apt install ./Forward.Email_\u003cversion\u003e_amd64.deb\n```\n\nFor **Ubuntu arm64 / aarch64**, install the matching arm64 `.deb` the same way:\n\n```bash\nsudo apt update\nsudo apt install ./Forward.Email_\u003cversion\u003e_arm64.deb\n```\n\nIf you prefer a portable binary, download the matching `.AppImage` for your architecture from [GitHub Releases](https://github.com/forwardemail/mail.forwardemail.net/releases), make it executable, and run it directly:\n\n```bash\nchmod +x Forward.Email_\u003cversion\u003e_\u003carch\u003e.AppImage\n./Forward.Email_\u003cversion\u003e_\u003carch\u003e.AppImage\n```\n\nReplace `\u003carch\u003e` with `amd64` or `arm64` to match the asset you downloaded. If you are building your own custom Linux binary instead of installing a published release, use the desktop development guide in [`docs/desktop-setup.md`](./docs/desktop-setup.md).\n\n\u003e **Note for macOS users:** If you download the `.dmg` from GitHub Releases, you may need to run the following command if you see a \"damaged\" or unverified app error:\n\u003e\n\u003e ```bash\n\u003e sudo xattr -rd com.apple.quarantine /Applications/ForwardEmail.app\n\u003e ```\n\u003e\n\u003e Replace `/Applications/ForwardEmail.app` with the actual path if you installed the app elsewhere.\n\n## Security \u0026 Privacy\n\nSecurity is the foundational principle of this application. We are committed to transparency and providing users with control over their data. For a detailed overview of our security practices, please see:\n\n- [Security Policy](https://forwardemail.net/security)\n- [security.txt](https://forwardemail.net/.well-known/security.txt)\n\n### Client-Side Encryption \u0026 App Lock\n\nThe application offers a robust **App Lock** feature that enables cryptographic encryption for your entire client-side database and settings — in the browser, on desktop, or on mobile. When enabled from the **Settings \u003e Privacy \u0026 Security** menu, all sensitive data stored locally (including message bodies, contacts, and API tokens) is encrypted at rest using the `XSalsa20-Poly1305` stream cipher from the audited `libsodium` library.\n\nThis feature can be secured using two methods:\n\n1.  **Passkey (WebAuthn)**: For the highest level of security, you can lock and unlock the application using a FIDO2/WebAuthn-compliant authenticator. This allows you to use hardware security keys or your device's built-in biometrics. The encryption key is derived directly from the authenticator using the PRF extension, meaning the key is never stored on the device itself.\n2.  **PIN Code**: For convenience, you can set a simple PIN code. This provides an iOS-like lock screen experience, ideal for quick access on mobile devices.\n\nOur implementation supports a wide range of authenticators for Passkey-based App Lock:\n\n| Type                        | Examples                                                                                                        |\n| :-------------------------- | :-------------------------------------------------------------------------------------------------------------- |\n| **Platform Authenticators** | Apple Touch ID, Face ID, Optic ID, Windows Hello, Android Biometrics (fingerprint, face)                        |\n| **Hardware Security Keys**  | YubiKey 5 Series, YubiKey Bio, Google Titan, Feitian ePass/BioPass, SoloKeys, Nitrokey 3, HID Crescendo, Ledger |\n| **Cloud/Software Passkeys** | iCloud Keychain, Google Password Manager, Samsung Pass, 1Password, Dashlane, Bitwarden, Proton Pass             |\n\n### Tamper-Proof Builds\n\nAll builds are handled by public GitHub Actions workflows directly from the source code. Desktop applications are signed with platform-specific certificates (Apple Developer ID and Windows EV Code Signing) and the Tauri updater uses Ed25519 signatures to verify the integrity of every update package. All auto-updates, notifications, and IDLE-like real-time push support are handled directly between the app and our servers with zero third-party involvement.\n\n## Features\n\n- **Blazing Fast**: Built with Rust and Svelte 5 for a lightweight and responsive experience.\n- **End-to-End Encrypted**: Cryptographic encryption for your entire client-side app — browser, desktop, or mobile.\n- **Open-Source**: All code for the web, desktop, and mobile apps is available on GitHub.\n- **Zero Third-Party Involvement**: Auto-updates, notifications, and real-time IDLE-like push support are handled directly by the app with no third-party servers or services involved.\n- **Real-time Updates**: Mailbox updates are pushed instantly via WebSockets.\n- **Cross-Platform Notifications**: Native desktop and mobile push notifications.\n- **Multi-account** — Login with multiple Forward Email accounts, alias auth, and optional API key override.\n- **Mailbox** — Folders, message threading, bulk actions, keyboard shortcuts, attachment handling, PGP decryption.\n- **Compose** — Rich text editor (TipTap), CC/BCC, emoji picker, attachments, draft autosave, offline outbox queue.\n- **Search** — Full-text search with FlexSearch, optional body indexing, saved searches, background indexing.\n- **Offline Support**: A custom main-thread sync engine provides offline access and queues outgoing actions, replacing the need for a Service Worker and ensuring functionality on all platforms including Ionic/Capacitor mobile.\n- **Calendar** — Month/week/day views, quick add/edit/delete, iCal export.\n- **Contacts** — CRUD operations, vCard import/export, deep links to compose/search.\n- **Demo Mode**: Evaluate the app's features offline without an account.\n- **`mailto:` Handler**: Registers as the default email client on desktop platforms.\n- **Auto-Updates**: Desktop apps automatically check for and install new versions securely.\n\n## Architecture Overview\n\nThe application is built on a unified architecture that reuses the same Svelte 5 web application as the UI for all platforms. Tauri v2 provides the cross-platform shell, using a Rust backend for native capabilities and system webviews for rendering the UI.\n\n```mermaid\ngraph TD\n    subgraph \"Web App (Svelte 5 + Vite)\"\n        A[UI Components] --\u003e B(Stores)\n        B --\u003e C{API Client}\n        C --\u003e D[REST API]\n        B --\u003e E{WebSocket Client}\n        E --\u003e F[Real-time API]\n    end\n    subgraph \"Tauri (Desktop \u0026 Mobile)\"\n        G[Rust Backend] --\u003e H{System WebView}\n        H -- loads --\u003e A\n        I[Tauri IPC] -- JS Bridge --\u003e A\n        G -- IPC --\u003e I\n        J[Tauri Plugins] --\u003e G\n    end\n    D --- M(api.forwardemail.net)\n    F --- M\n```\n\nFor more detail, please see the full [Architecture Document](./docs/ARCHITECTURE.md).\n\n## Tech Stack\n\n| Category             | Technologies                                                         |\n| -------------------- | -------------------------------------------------------------------- |\n| **Web**              | Svelte 5, Vite, pnpm                                                 |\n| **Desktop \u0026 Mobile** | Tauri v2 (Rust backend, Svelte frontend)                             |\n| **Styling**          | Tailwind CSS 4, PostCSS                                              |\n| **State**            | Svelte Stores                                                        |\n| **Database**         | Dexie 4 (IndexedDB)                                                  |\n| **Search**           | FlexSearch                                                           |\n| **Editor**           | TipTap 2                                                             |\n| **Calendar**         | schedule-x                                                           |\n| **Real-time**        | WebSocket with msgpackr binary encoding                              |\n| **Encryption**       | libsodium-wrappers (XSalsa20-Poly1305, Argon2id), OpenPGP            |\n| **Passkeys**         | @passwordless-id/webauthn (FIDO2/WebAuthn with PRF extension)        |\n| **Testing**          | Vitest, Playwright for E2E tests, WebdriverIO for Tauri binary tests |\n| **Tooling**          | ESLint 9, Prettier 3, Husky, commitlint                              |\n\n### Key Components\n\n- **Main Thread** — Svelte components, stores, routing, UI rendering\n- **db.worker** — Owns IndexedDB via Dexie, handles all database operations\n- **sync.worker** — API fetching, message parsing (PostalMime), data normalization\n- **search.worker** — FlexSearch indexing and query execution\n\n### Documentation\n\nDetailed architecture documentation is available in the `docs/` directory:\n\n- [Architecture](./docs/ARCHITECTURE.md) — Full architecture document\n- [Vision \u0026 Architecture](docs/building-webmail-vision-architecture.md) — Design principles and architectural patterns\n- [Worker Architecture](docs/worker-architecture.md) — Worker responsibilities and message passing\n- [Cache \u0026 Indexing](docs/cache-indexing-architecture.md) — Storage layers and data flow\n- [Search](docs/building-webmail-search.md) — FlexSearch setup and query parsing\n- [Service Worker](docs/building-webmail-service-worker.md) — Asset caching strategy\n- [DB Schema \u0026 Recovery](docs/building-webmail-db-schema-recovery.md) — Database management\n- [Desktop Build CI](docs/desktop-build-ci.md) — How the desktop build is triggered and tested\n- [Desktop CI Secrets](docs/desktop-ci-secrets.md) — CI secrets setup for desktop signing\n- [Desktop Contributing](docs/desktop-contributing.md) — Desktop architecture and IPC patterns\n- [Desktop Setup](docs/desktop-setup.md) — Developer environment setup for desktop\n- [Desktop \u0026 Mobile Development](./docs/DEVELOPMENT.md) — Platform-specific development guide\n- [iOS Setup](./docs/ios-setup.md) — Local signing, CI, and TestFlight workflow\n- [Release Process](./docs/RELEASES.md) — How releases are managed\n- [Security Hardening](./docs/SECURITY.md) — Security practices and hardening\n- [App Lock Architecture](docs/app-lock-architecture.md) — Client-side encryption and App Lock design\n- [Push Notifications](./docs/PUSH_NOTIFICATIONS.md) — Push notification setup\n- [WebSocket](./docs/WEBSOCKET.md) — Real-time WebSocket protocol\n- [Tauri Testing](./docs/TAURI_TESTING.md) — Testing Tauri desktop/mobile apps\n- [Secrets](./docs/SECRETS.md) — Secrets management for CI/CD\n- [Workers](docs/building-webmail-workers.md) — Worker mesh architecture\n- [Technology Stack](docs/building-webmail-technology-stack.md) — Technology choices and rationale\n- [Mailbox Loading Flow](docs/mailbox-loading-flow.md) — Full request lifecycle for loading messages\n- [Clear-Site-Data](docs/clear-site-data-spec.md) — Client reset kill switch specification\n- [Deployment Checklist](docs/deployment-checklist.md) — Step-by-step deployment guide\n- [Building Webmail Series](docs/building-webmail-series.md) — Technical deep-dive blog series overview\n- [Vision Gap Analysis](docs/webmail-vision-gap-analysis.md) — Gap analysis between vision spec and implementation\n\n## Project Structure\n\n```\nsrc/\n├── main.ts                 # App bootstrap, routing, service worker registration\n├── config.ts               # Environment configuration\n├── stores/                 # Svelte stores (state management)\n│   ├── mailboxStore.ts     # Message list, folders, threading\n│   ├── mailboxActions.ts   # Move, delete, flag, label actions\n│   ├── messageStore.ts     # Selected message, body, attachments\n│   ├── searchStore.ts      # Search queries and index health\n│   ├── settingsStore.ts    # User preferences, theme, PGP keys\n│   └── ...\n├── svelte/                 # Svelte components\n│   ├── Mailbox.svelte      # Main email interface\n│   ├── Compose.svelte      # Email composer\n│   ├── Calendar.svelte     # Calendar view\n│   ├── Contacts.svelte     # Contact management\n│   ├── Settings.svelte     # User settings\n│   └── components/         # Reusable components\n├── workers/                # Web Workers\n│   ├── db.worker.ts        # IndexedDB operations\n│   ├── sync.worker.ts      # API sync and parsing\n│   └── search.worker.ts    # Search indexing\n├── utils/                  # Utilities\n│   ├── remote.js           # API client\n│   ├── db.js               # Database initialization\n│   ├── storage.js          # LocalStorage management\n│   └── ...\n├── lib/components/ui/      # UI component library (shadcn/ui)\n├── styles/                 # CSS (Tailwind + custom)\n├── locales/                # i18n translations\n└── types/                  # TypeScript definitions\n```\n\n## Getting Started\n\n### Prerequisites\n\n- Node.js 20+\n- pnpm 9.0.0+\n- [Rust](https://rustup.rs/) and [Tauri v2 prerequisites](https://v2.tauri.app/start/prerequisites/) (for desktop/mobile development)\n\n### Installation\n\n```bash\npnpm install\n```\n\n### Development\n\n```bash\npnpm dev              # Start web dev server (http://localhost:5174)\npnpm tauri dev        # Start desktop dev mode\npnpm tauri:android:dev  # Start Android dev mode (preflight checks + adb setup)\npnpm tauri:ios:dev      # Start iOS dev mode (preflight checks + simulator boot)\n```\n\n### Build\n\n```bash\npnpm build        # Build to dist/ + generate service worker\npnpm preview      # Preview production build locally\npnpm analyze      # Build with bundle analyzer\n```\n\n### Code Quality\n\n```bash\npnpm lint         # Run ESLint\npnpm lint:fix     # Fix linting issues\npnpm format       # Check formatting\npnpm format:fix   # Fix formatting\npnpm check        # Run svelte-check\n```\n\n### Testing\n\n```bash\n# Unit tests (Vitest)\npnpm test              # Run all tests\npnpm test:watch        # Watch mode\npnpm test:coverage     # Generate coverage report\n\n# E2E tests (Playwright)\npnpm exec playwright install --with-deps  # First-time setup\npnpm test:e2e          # Run e2e tests\n```\n\n## Contributing\n\n### Commit Messages\n\nThis project uses [Conventional Commits](https://www.conventionalcommits.org/) enforced by commitlint. Every commit message must follow the format:\n\n```\ntype(scope): description\n```\n\n| Type       | When to use                             | Version bump |\n| ---------- | --------------------------------------- | ------------ |\n| `feat`     | New feature                             | minor        |\n| `fix`      | Bug fix                                 | patch        |\n| `docs`     | Documentation only                      | none         |\n| `refactor` | Code change that neither fixes nor adds | none         |\n| `perf`     | Performance improvement                 | patch        |\n| `test`     | Adding or updating tests                | none         |\n| `chore`    | Build, CI, tooling changes              | none         |\n\nScope is optional: `fix(compose): handle pasted recipients` or `fix: handle pasted recipients` are both valid.\n\nTo trigger a **major** version bump, add a `BREAKING CHANGE:` footer:\n\n```\nfeat: redesign settings page\n\nBREAKING CHANGE: settings store schema changed, requires cache clear\n```\n\n### Releasing\n\nReleases are managed locally using [np](https://github.com/sindresorhus/np). Version bumps still flow through `pnpm release`, and desktop artifact publishing is handled by the Tauri desktop release workflow plus the `pnpm release:desktop` helper for desktop-only hotfixes.\n\n```bash\npnpm release            # interactive version prompt, runs checks, pushes, publishes GitHub Release\n```\n\nThis command will:\n\n1. Verify a clean working tree and up-to-date `main` branch\n2. Run lint, format, tests, and build\n3. Bump the version in `package.json` and create a git tag\n4. Push the commit and tag to GitHub\n5. Publish a GitHub Release\n\nFor desktop releases, pushing a desktop tag or running the workflow manually triggers **Release Desktop (Tauri)** (`.github/workflows/release-desktop.yml`), which creates or updates a draft GitHub Release and uploads the macOS x64/arm64, Windows x64/arm64, and Linux x64/arm64 desktop artifacts.\n\n## Configuration\n\nCreate a `.env` file to override defaults:\n\n```bash\n# API base URL (Vite requires VITE_ prefix for client exposure)\nVITE_WEBMAIL_API_BASE=https://api.forwardemail.net\n```\n\n## Deployment\n\n\u003e **First time setup?** See the complete [Deployment Checklist](docs/deployment-checklist.md) for step-by-step instructions on Cloudflare, GitHub Actions, and DNS configuration.\n\n### Infrastructure\n\n```mermaid\ngraph TB\n    subgraph Edge[\"Cloudflare Edge\"]\n        subgraph Worker[\"Cloudflare Worker\"]\n            W1[\"SPA routing (returns index.html for /mailbox, etc)\"]\n            W2[\"Cache headers (immutable for assets, no-cache HTML)\"]\n        end\n        Worker --\u003e R2\n        subgraph R2[\"Cloudflare R2\"]\n            R2A[\"Static assets (dist/)\"]\n            R2B[\"Fingerprinted bundles (/assets/*.js, *.css)\"]\n        end\n    end\n```\n\n### Cache Strategy\n\n| Asset Type                                  | Cache-Control                 | Reason                                       |\n| ------------------------------------------- | ----------------------------- | -------------------------------------------- |\n| `index.html`, `/mailbox`, `/calendar`, etc. | `no-cache, no-store`          | Always fetch fresh HTML for updates          |\n| `/assets/*` (JS, CSS)                       | `immutable, max-age=31536000` | Fingerprinted by Vite, safe to cache forever |\n| `sw.js`, `sw-*.js`, `version.json`          | `no-cache, must-revalidate`   | Service worker must check for updates        |\n| `/icons/*`                                  | `max-age=2592000`             | 30 days, rarely change                       |\n| Fonts (`.woff2`)                            | `immutable, max-age=31536000` | Fingerprinted, cache forever                 |\n\n### CI/CD Pipeline\n\nThis repository currently documents two primary release workflows:\n\n1. **Release Desktop (Tauri)** (`.github/workflows/release-desktop.yml`) for macOS x64/arm64, Windows x64/arm64, and Linux x64/arm64 desktop artifacts. See [Desktop Build CI guide](docs/desktop-build-ci.md) for the platform matrix, runner details, and artifact expectations.\n2. **Release Mobile** (`.github/workflows/release-mobile.yml`) for signed Android APK/AAB output and signed iOS IPA upload to TestFlight. See [Release Process](docs/RELEASES.md), [iOS Setup](docs/ios-setup.md), and [Secrets](docs/SECRETS.md) for the exact signing inputs and release flow.\n\nLocal validation for release work should still cover the standard web checks before tagging a release:\n\n1. **Install** — `pnpm install --frozen-lockfile`\n2. **Lint** — `pnpm lint`\n3. **Format** — `pnpm format`\n4. **Unit tests** — `pnpm test -- --run`\n5. **Build** — `pnpm build`\n6. **Desktop build smoke test** — `pnpm tauri:build` for the target platform you are validating\n\nFor exact secret generation, GitHub environment setup, and platform-specific signing steps, use [docs/SECRETS.md](./docs/SECRETS.md) as the canonical guide, with [docs/desktop-ci-secrets.md](./docs/desktop-ci-secrets.md) and [docs/ios-setup.md](./docs/ios-setup.md) as platform-specific companions.\n\n### Required Secrets \u0026 Variables\n\n**GitHub Secrets:**\n\n| Secret                               | Description                                                       |\n| ------------------------------------ | ----------------------------------------------------------------- |\n| `R2_ACCOUNT_ID`                      | Cloudflare account ID (also used for Workers)                     |\n| `R2_ACCESS_KEY_ID`                   | R2 API access key                                                 |\n| `R2_SECRET_ACCESS_KEY`               | R2 API secret key                                                 |\n| `CLOUDFLARE_ZONE_ID`                 | Zone ID for cache purge                                           |\n| `CLOUDFLARE_API_TOKEN`               | API token with R2 + Workers + cache-purge permissions             |\n| `TAURI_SIGNING_PRIVATE_KEY`          | Tauri updater Ed25519 signing key                                 |\n| `TAURI_SIGNING_PRIVATE_KEY_PASSWORD` | Password for the Tauri signing key                                |\n| `APPLE_CERTIFICATE`                  | Base64-encoded macOS `.p12` signing certificate                   |\n| `APPLE_CERTIFICATE_PASSWORD`         | Password used to export the macOS `.p12`                          |\n| `APPLE_SIGNING_IDENTITY`             | Apple Developer ID signing identity                               |\n| `APPLE_ID`                           | Apple ID for notarization                                         |\n| `APPLE_PASSWORD`                     | App-specific password for notarization                            |\n| `APPLE_TEAM_ID`                      | Apple Developer Team ID                                           |\n| `WINDOWS_CERTIFICATE`                | Base64-encoded exportable Windows `.pfx` code-signing certificate |\n| `WINDOWS_CERTIFICATE_PASSWORD`       | Password used to export the Windows `.pfx`                        |\n| `ANDROID_KEYSTORE_BASE64`            | Android signing keystore (base64)                                 |\n| `ANDROID_KEYSTORE_PASSWORD`          | Password for the Android keystore                                 |\n| `ANDROID_KEY_ALIAS`                  | Android signing key alias                                         |\n| `ANDROID_KEY_PASSWORD`               | Password for the Android signing key                              |\n| `IOS_CERTIFICATE_BASE64`             | Base64-encoded iOS Apple Distribution `.p12`                      |\n| `IOS_CERTIFICATE_PASSWORD`           | Password used to export the iOS `.p12`                            |\n| `IOS_PROVISIONING_PROFILE_BASE64`    | Base64-encoded App Store provisioning profile                     |\n| `APP_STORE_CONNECT_API_KEY`          | Full contents of the downloaded App Store Connect `.p8` key       |\n| `APP_STORE_CONNECT_KEY_ID`           | App Store Connect API key ID                                      |\n| `APP_STORE_CONNECT_ISSUER_ID`        | App Store Connect issuer ID                                       |\n\n**GitHub Variables:**\n\n| Variable               | Description                                                              |\n| ---------------------- | ------------------------------------------------------------------------ |\n| `R2_BUCKET`            | R2 bucket name for static assets                                         |\n| `IOS_SIGNING_IDENTITY` | Optional iOS signing identity override; defaults to `Apple Distribution` |\n\nFor generation steps and exact setup instructions, see [docs/SECRETS.md](./docs/SECRETS.md).\n\n### Cloudflare API Token Setup\n\nCreate a token at **My Profile → API Tokens → Create Token → Create Custom Token**:\n\n**Permissions:**\n\n| Scope   | Permission      | Access |\n| ------- | --------------- | ------ |\n| User    | User Details    | Read   |\n| Account | Workers Scripts | Edit   |\n| Zone    | Cache Purge     | Purge  |\n\n**Account Resources:**\n\n- Select **Include → Specific account → [Your Account]**\n- Or **Include → All accounts** (if you have only one)\n\n**Zone Resources:**\n\n- Select **Include → Specific zone → [Your Domain]**\n- Or **Include → All zones**\n\n\u003e **Common mistake:** Setting permissions but leaving Account/Zone Resources as \"All accounts from...\" dropdown without explicitly selecting. You must click and select your specific account/zone.\n\n### Worker Setup\n\nThe CDN worker (`worker/`) handles:\n\n1. **SPA Routing** — Returns `index.html` for navigation requests to `/mailbox`, `/calendar`, `/contacts`, `/login`\n2. **Cache Headers** — Sets correct `Cache-Control` per asset type\n3. **Security Headers** — `X-Content-Type-Options`, `X-Frame-Options`\n\nAfter first deployment, configure the custom domain:\n\n1. **Cloudflare Dashboard → Workers \u0026 Pages → webmail-cdn**\n2. **Settings → Triggers → Add Custom Domain**\n3. Enter your domain (e.g., `mail.example.com`)\n\n### Manual Deployment\n\n```bash\n# Build the app\npnpm build\n\n# Deploy to R2 (requires AWS CLI configured with R2 credentials)\naws --endpoint-url \"https://ACCOUNT_ID.r2.cloudflarestorage.com\" \\\n    s3 sync dist/ \"s3://BUCKET_NAME/\" --delete\n\n# Deploy worker\ncd worker\npnpm install\nnpx wrangler deploy\n\n# Purge Cloudflare cache\ncurl -X POST \"https://api.cloudflare.com/client/v4/zones/ZONE_ID/purge_cache\" \\\n    -H \"Authorization: Bearer API_TOKEN\" \\\n    -H \"Content-Type: application/json\" \\\n    --data '{\"purge_everything\":true}'\n```\n\n### Troubleshooting\n\n**Stale assets after deploy:**\n\n- Verify cache purge succeeded in GitHub Actions logs\n- Check browser DevTools → Network → Disable cache and refresh\n- Users with disk-cached HTML may need to clear browser cache or wait for the fallback recovery UI\n\n**SPA routes return 404:**\n\n- Ensure the worker is deployed and bound to your domain\n- Check worker logs: `cd worker \u0026\u0026 npx wrangler tail`\n\n**Service worker not updating:**\n\n- Check `version.json` is being fetched fresh (no cache)\n- Verify `sw.js` has `no-cache` header in Network tab\n\n## License\n\n[Business Source License 1.1](LICENSE.md) - Forward Email LLC\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fforwardemail%2Fmail.forwardemail.net","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fforwardemail%2Fmail.forwardemail.net","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fforwardemail%2Fmail.forwardemail.net/lists"}