This guide is for building the desktop app from source. If you only want to install a published desktop binary, use the instructions in the repository README. CI publishes official Linux release artifacts for x64 and arm64, with Linux arm64 shipping .deb and .rpm bundles.
| Tool | Version | Notes |
|---|---|---|
| Rust | stable (via rustup) | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh |
| Node.js | 20+ | |
| pnpm | 9+ | corepack enable && corepack prepare pnpm@latest --activate |
| Xcode CLI tools (macOS) | latest | xcode-select --install |
| WebView2 (Windows) | latest | Pre-installed on Windows 11; download for Windows 10 |
| Linux system libs | see below | Required for WRY/WebKit |
sudo apt-get update
sudo apt-get install -y \
libwebkit2gtk-4.1-dev \
libappindicator3-dev \
librsvg2-dev \
patchelf \
libssl-dev \
libgtk-3-dev \
libayatana-appindicator3-dev# Clone and install JS dependencies
pnpm install
# Install the Rust target for your platform (if not already present)
# macOS Apple Silicon:
rustup target add aarch64-apple-darwin
# macOS Intel:
rustup target add x86_64-apple-darwin
# Linux x64:
rustup target add x86_64-unknown-linux-gnu
# Linux arm64:
rustup target add aarch64-unknown-linux-gnu
# Windows x64:
rustup target add x86_64-pc-windows-msvc
# Windows arm64:
rustup target add aarch64-pc-windows-msvcIf you are developing on Linux arm64, you can install the published release artifacts directly from GitHub Releases or build from source on your target machine for local development and custom packaging. The corresponding Rust target remains:
rustup target add aarch64-unknown-linux-gnuIf you are developing on Windows arm64, install the ARM64 MSVC toolchain support in Visual Studio Build Tools before building the Tauri app locally, then add the matching Rust target:
rustup target add aarch64-pc-windows-msvcpnpm tauri:devThis starts the Vite dev server on http://localhost:5174 and opens the Tauri window pointing at it. Hot-reload works for the Svelte frontend. Rust changes require a restart.
DevTools: Press Cmd+Option+I (macOS) or Ctrl+Shift+I (Windows/Linux) to open the WebView inspector.
Release builds leave the inspector out unless WEB_INSPECTOR is true when you run pnpm tauri:build, pnpm tauri:android:build or pnpm tauri:ios:build (for example WEB_INSPECTOR=true pnpm tauri:build in a POSIX shell). The variable turns on the devtools Cargo feature through scripts/web-inspector-args.cjs. The desktop and mobile release workflows read it from the WEB_INSPECTOR repository variable; Snap and Flatpak builds always leave the inspector out. Keep it off for App Store and Google Play builds: on iOS the inspector sets a private WebKit preference that App Review rejects, and an inspectable web view exposes the app's stored data to anyone holding the unlocked device.
pnpm tauri:buildOutput paths (relative to project root):
| Platform | Output |
|---|---|
| macOS | src-tauri/target/release/bundle/dmg/*.dmg |
| Windows | src-tauri/target/release/bundle/msi/*.msi, src-tauri/target/release/bundle/nsis/*.exe |
| Linux | src-tauri/target/release/bundle/appimage/*.AppImage, src-tauri/target/release/bundle/deb/*.deb, src-tauri/target/release/bundle/rpm/*.rpm |
Linux arm64 release automation currently publishes the .deb and .rpm outputs. The .AppImage path remains relevant for local x64 builds, but it is not part of the ARM64 release lane.
No .env file is required. The API base URL is set in src/config.js and defaults to https://api.forwardemail.net. To override for local development, edit src/config.js directly.
Tauri's auto-updater runs from the Rust backend, so its HTTP requests will not appear in the webview devtools Network tab. Use one of these three paths to observe updater behavior.
All JS code paths in src/utils/updater-bridge.js log at every decision point with the [updater] prefix:
[updater] checkForUpdates() called
[updater] calling mod.check() — this issues an HTTP GET from the Rust backend
[updater] mod.check() returned { isNull: false, available: true, version: '0.9.21', currentVersion: '0.9.20', date: '2026-04-18' }
[updater] doCheck: update available → v0.9.21 (current v0.9.20)
[updater] no onUpdateAvailable callback — will auto-install silently
[updater] downloadAndInstall() starting for version 0.9.21
[updater] download started: 8421734 bytes
[updater] download progress: 10%
[updater] download progress: 20%
...
[updater] download finished — install + relaunch starting
These lines appear in:
- The webview devtools Console (⌥⌘I in dev builds, or in a release build made with
WEB_INSPECTOR=true). - The rotating log files on disk, via
tauri-plugin-log'sWebviewtarget. Up to 5 × 1 MB files. Locations:- macOS:
~/Library/Logs/net.forwardemail.mail/ - Linux:
~/.local/share/net.forwardemail.mail/logs/ - Windows:
%LOCALAPPDATA%\net.forwardemail.mail\logs\
- macOS:
- The app's own diagnostics UI (Settings → Diagnostics → "View Recent Logs"), which tails those same files.
The tauri_plugin_updater module is pinned to Debug level in src-tauri/src/lib.rs. Its log lines flow through the same tauri-plugin-log pipeline as JS logs, so they land in the same rotating files with the tauri_plugin_updater target:
2026-04-18T05:12:03Z [DEBUG][tauri_plugin_updater] checking for update from https://github.com/.../latest.json
2026-04-18T05:12:03Z [DEBUG][tauri_plugin_updater] latest version: 0.9.21, current: 0.9.20
In debug builds (pnpm tauri:dev), the overall log level is Debug so you'll see network retries, body parsing, and signature verification from the plugin as well.
When you need to confirm an HTTP request left the machine (e.g., suspecting a firewall or DNS issue):
# macOS — watch connections from the app process while triggering a check
lsof -iTCP -sTCP:ESTABLISHED -P -c 'Forward Email' -r 1
# Cross-platform — use Little Snitch (macOS), Proxyman, or mitmproxy to
# capture the actual request. Requires configuring the OS/proxy trust
# chain since update downloads are HTTPS.GitHub Releases assets use objects.githubusercontent.com / release-assets.githubusercontent.com for the .tar.gz download and github.com for the latest.json redirect.
| What you see | What it means |
|---|---|
rate-limited: last check was Xs ago |
The 5-minute minimum between checks is holding. Triggering again immediately is a no-op. |
mod.check() returned { isNull: true, ... } |
Tauri believes there is no newer version. You may already be on latest, or latest.json may be absent from the release. |
mod.check() threw: signature mismatch |
The pubkey in tauri.conf.json doesn't match the private key that signed the .sig file. |
downloadAndInstall failed: Failed to move… |
macOS install-location issue. Move the app into /Applications and re-launch. |
No [updater] log lines at all |
isTauriDesktop returned false (wrong platform), or the module never ran. Check the Rust log for the plugin's own activity. |
- "xcrun: error: invalid active developer path": Run
xcode-select --installto install CLI tools. - Xcode license not accepted: Run
sudo xcodebuild -license accept. - Wrong Rust target: Ensure you've added the correct target for your Mac (
aarch64-apple-darwinfor Apple Silicon,x86_64-apple-darwinfor Intel).
- "Package libwebkit2gtk-4.1 was not found": Install the system dependencies listed above.
- WRY build failures: Ensure all
-devpackages are installed. On Fedora/RHEL, usewebkit2gtk4.1-develand equivalents.
- "WebView2 runtime not found": Download and install from Microsoft.
- Build fails with MSVC errors: Install Visual Studio Build Tools with the "Desktop development with C++" workload.
- Windows arm64 build fails at link time: Ensure the Visual Studio ARM64 C++ build tools are installed before targeting
aarch64-pc-windows-msvc. - "Set as default" only opens Settings: Expected on modern Windows. The app can hand off to the system Default apps experience for
mailto, but it cannot silently claim the handler itself. - SmartScreen or Defender warns on install: Release installers are signed, but SmartScreen reputation for a new certificate builds from download history, so early releases can still show the prompt. Local builds are unsigned.
pnpm tauri:devhangs: Check that port 5174 is not in use. Kill any stale Vite processes.- Rust compilation slow on first build: Expected. Subsequent builds use incremental compilation and the Rust cache.