Hello to anyone reading this! I wanted to share a desktop application I’ve spent the last few months building in Rust: Fumiko (文子).
It is an open-source (MPL-2.0), local email watcher and triage tool. Instead of sending emails to third-party cloud APIs and needing to pay a monthly subscription, Fumiko monitors inboxes across Gmail, Outlook, and generic IMAP, using on-device quantized models (via a bundled llama-server sidecar process or Ollama) to triage important emails (like interview invites, receipts, critical notices, or anything you want) onto a findings board.
Architecture & Crate Breakdown
I structured the project as a modular Cargo workspace to keep protocol handling, storage, and UI orchestration cleanly isolated:
oauth: RFC 8252 loopback authorization code flow with S256 PKCE. Binds to dynamic ephemeral OS ports (127.0.0.1:0) and validates CSRF state tokens in constant time.storage: Local persistence using SQLite in WAL mode (synchronous = NORMAL). Primary keys use time-ordered UUIDv7 to maintain B-Tree write locality. Implements automated mailbox capacity pruning that preserves flagged findings.email_core: The synchronization engine. Implements a two-phase sync model (Discovery vs. Hydration) using Gmail History IDs, Microsoft Graph Delta Links, and IMAPUIDVALIDITY. All IMAP sampling strictly enforcesBODY.PEEKto preserve the user's unread status on the remote server.local_ai: Multi-backend inference supervisor. Manages process lifecycles for a bundledllama-serveron port 11435, streams GGUF weights directly from Hugging Face, and enforces GBNF token-level grammar schemas so small 1B/3B models emit deterministic JSON without syntax hallucinations.app: Desktop presentation and state layer built with Dioxus 0.7. Manages the split-pane reading UI, system tray integration, dynamic CSS theming, and an isolated sandboxed iframe for rendering untrusted email HTML.
Some Fun Technical Hurdles
Working with Dioxus desktop and Tokio brought up some interesting challenges:
- Decoupled Worker Channels in Dioxus: To prevent route navigation from tearing active OAuth handshakes or background sync passes, all long-running tasks communicate with the root component scope over unbounded Tokio MPSC channels rather than being tied to route component lifecycles.
- Deterministic Process Supervision: Because sudden desktop exits or tray quits bypass standard Rust
Dropimplementations, I had to implement a global child process registry with explicitkill_builtin_server()hooks on window close, tray exit, and data wipes to avoid leaving orphanedllama-serverprocesses in memory. - Loopback Listener Cancellation: Implemented an RAII
ListenerGuardthat connects a dummy TCP stream back to the loopback port if the user clicks "Cancel" in the UI, unblockinglistener.accept()immediately so the thread doesn't hang. - Safe Sync Cursor Advancement: The sync engine enforces an invariant where incremental sync cursors only advance in SQLite if all discovered message metadata in that batch was successfully hydrated and written, preventing transient network drops from permanently skipping unread mail.
Tech Stack Summary
- Language: Rust 1.80+
- GUI / Shell: Dioxus 0.7 (Desktop)
- Async Runtime: Tokio
- Database: SQLite (WAL mode, pool capped to 5 connections)
- Secret Storage: OS native Keyring (Windows Credential Manager, Apple Keychain, Linux Secret Service)
- Inference: Bundled
llama-server/ external Ollama via HTTP streaming
I'm only 22 and still working on my CS degree, so this project was a huge learning curve for me in writing idiomatic Rust and structuring desktop state. I would love any feedback on the crate architecture, Dioxus signal patterns, or how I handled async task supervision.