Mailstack is a private mail organizer. Gmail stays the source of truth. The apps add a local layer for classification, search, priority, companies, and action suggestions. Nothing in this repository sends mail, and remote images inside a message are not fetched.
The Android phone app is the product that has been carried through a real release. The other folders are desktop and tablet clients at different stages. Read CONTEXT.md before changing the project. It is the continuity log for decisions, versions, and rules.
Repository: greninja-op/Mail-Stack
Gmail is the cloud copy. The phone keeps a rebuildable local copy and organizes that copy on the device. Tokens never sit in the mail tables. A demo account never starts a sync.
flowchart TB
subgraph signin [Sign-in]
Browser[System browser]
PKCE[PKCE challenge]
Tokens[Encrypted token store]
Browser --> PKCE --> Tokens
end
subgraph fetch [Fetch]
API[Gmail API]
Pace[Request pacing and saved cursor]
Room[(Room, one account at a time)]
Tokens --> API --> Pace --> Room
end
subgraph local [On the device, in order]
Company[Company]
Class[Classification]
Priority[Priority]
Time[Dates and deadlines]
Actions[Action suggestions]
Search[Search index]
Company --> Class --> Priority --> Time --> Actions --> Search
end
Room --> Company
Search --> UI[Home, Mail, Categories, Companies, Actions]
Opening a message does not call back to the sender. HTML is sanitized, scripts are dropped, and remote images are not downloaded.
sequenceDiagram
participant User
participant App
participant Browser
participant Google
participant Room
User->>App: Sign in
App->>Browser: Authorization URL
Browser->>Google: Consent
Google->>App: Redirect with the code
App->>Google: Exchange the code
App->>Room: Save mail pages
Note over App,Room: Tokens stay out of Room
App->>App: Classify, prioritize, and index
User->>App: Open a message
App->>User: Sanitized body, no remote images
When a label is wrong, the phone resolves it in this order. Optional AI runs only if the user has turned it on, and it cannot send, delete, or weaken a security classification.
flowchart LR
Correction[Your correction] --> Rule[Your rule]
Rule --> Rules[On-device rules]
Rules --> AI[Optional AI]
AI --> Unknown[Unknown]
The phone owns the live database. Desktop apps share Rust contracts in desktop-core. The iPhone, iPad, and Android tablet screens use a local demo mailbox until they are wired to that phone data on purpose.
flowchart TB
Phone[Android phone]
Room[(Room schema 10)]
Core[desktop-core]
Windows[Windows window]
Linux[Linux adapters]
Mac[macOS adapters]
Demos[iPhone, iPad, Android tablet]
Phone --> Room
Windows --> Core
Linux --> Core
Mac --> Core
Demos -.-> Room
The dashed line is not a connection. Those three clients do not open Room and do not complete Google sign-in. Linux and macOS have no window. Windows shows demo mail until an OAuth client id is configured.
| Platform | Folder | Language | Status |
|---|---|---|---|
| Android phone | android/ |
Kotlin, Jetpack Compose | Shipping app. Application id com.greninjaop.mailorganizer. Version name 1.0.39, version code 40. |
| Android tablet | AndroidTablet/ |
Kotlin, Jetpack Compose | Wide-screen UI over a local demo mailbox. Not wired to the phone database or to live Gmail. |
| iPhone | iOS/ |
Swift, SwiftUI | Phone UI over a local demo mailbox. Requires Xcode. Not built on Windows. |
| iPad | iPadOS/ |
Swift, SwiftUI | Split-view UI over a local demo mailbox. Requires Xcode. Not built on Windows. |
| Windows | Windows/ |
Rust, egui | Interim desktop window on the shared Rust core. Packaging for the Microsoft Store is not finished. |
| Linux | Linux/ |
Rust | Backend adapters only. There is no Linux window. The earlier UI was removed because it was not good enough. |
| macOS | macOS/ |
Rust | Backend adapters only. SwiftUI has not been started. The earlier UI was removed because it was not good enough. |
| Shared desktop core | desktop-core/ |
Rust | Platform-neutral contracts used by Windows, Linux, and macOS. |
Phone business data lives in the Android app. There is no Kotlin Multiplatform module in this repository. Desktop clients share desktop-core instead of copying those rules into Swift or a second Kotlin tree.
The phone destinations are Home, Mail, Categories, Companies, and Actions. Mail folders include the inbox, Primary, Promotional, Social, Spam, and Starred. A wide Android window moves those destinations onto a leading rail and places the message list beside the open message. iPad uses a sidebar with the same split. A narrow window falls back to one column.
Windows uses a three-pane mail window. Its palette is separate from the phone: pastel blue #4285F4 / #8AB4F8 on canvas #F7F9FC (dark #151A22). The phone, iPhone, iPad, and Android tablet stay on the violet identity #5238E8 / #B9A8FF, with light background #F6F3FF and dark background #131120.
Linux and macOS do not have that UI yet. Their context files say so on purpose: Linux/CONTEXT.md and macOS/CONTEXT.md.
The phone module is the reference implementation.
- Sign-in is Google OAuth in the system browser, with PKCE. One sign-in is intended to cover Gmail read, Calendar events, and Tasks.
gmail.modifystays behind a separate confirmation. The redirect scheme iscom.greninjaop.mailorganizer. - Local storage is Room (schema version 10) plus DataStore. Account id is a foreign-key boundary. Tokens are not columns in the mail tables.
- Sync pages through the Gmail API, keeps a cursor, and paces requests. A demo or fixture account does not sync.
- On-device work covers classification, company detection, priority, full-text search, rules and corrections, dates and deadlines, conversation state, action suggestions, privacy tools, and an optional AI fallback that is off unless the user turns it on.
- HTML from mail is sanitized. JavaScript is not executed. Remote images are not downloaded.
Build from android/ with JDK 17 and Android SDK 35:
./gradlew :app:assembleDebug
./gradlew :app:testDebugUnitTestPoint the SDK at the machine with ANDROID_HOME or a gitignored local.properties. Do not commit keystores, OAuth client secrets, or API keys.
desktop-core is a Rust 2021 library (#![forbid(unsafe_code)]). It holds the contracts the desktop apps are supposed to share: accounts, OAuth URL and PKCE construction, a Gmail API trait, sync progress, mailbox models, classification, search query building, rules, HTML sanitizing, privacy redaction, and an AI router that defaults to off.
It does not open a SQLite file and it does not contain an HTTP client. Persistence in the crate is in memory. A real Gmail call, a real keychain, and a real database file are platform work that is not finished. MemoryCredentialStore is for tests.
The Cargo workspace at the repository root contains desktop-core and Windows. Linux and macOS are separate workspaces so they are not mixed into the Windows build.
cargo test -p desktop-core --lib
cargo test --manifest-path Linux/Cargo.toml
cargo test --manifest-path macOS/Cargo.tomlOn this Windows machine those Linux and macOS suites passed (17 tests and 11 tests). That run does not launch a Linux desktop or a Mac app. Linux secret service, macOS Keychain, and the system browser all return “not available” here rather than pretending a secret was saved or a login finished.
The Windows app crate is mailstack-windows. The window is egui / eframe 0.29, not WinUI. WinUI 3 and MSIX remain the packaging path and are blocked on signing. See Windows/packaging/RELEASE_STATUS.md.
The toolchain file Windows/rust-toolchain.toml pins stable Rust and x86_64-pc-windows-msvc. Linking needs Visual Studio Build Tools with the C++ workload and the Windows SDK. Without a live OAuth client id the window shows demo mail, and it labels that state as demo.
cargo run -p mailstack-windowsHierarchy and spacing for that window followed the rules in greninja-op/DesignSoul. DesignSoul is a design reference. It is not a dependency.
These three clients copy the phone’s information architecture and violet tokens. Their mail is a local demo set. They do not open the phone’s Room database and they do not complete Google sign-in.
| Client | Toolchain | Identity |
|---|---|---|
| iPhone | Xcode 16, iOS 17, SwiftUI, device family iPhone | com.greninjaop.mailstack |
| iPad | Xcode 16, iOS 17, SwiftUI, device family iPad | com.greninjaop.mailstack.ipad |
| Android tablet | Android Gradle Plugin 8.13.2, Kotlin 2.3.21, Compose BOM 2026.06.01, minSdk 26 | com.greninjaop.mailstack.tablet |
The tablet module does not ship a Gradle wrapper. Open it from Android Studio, or run Gradle with a local installation and ANDROID_HOME set. Dynamic color is off so the violet brand does not follow the wallpaper.
iPhone and iPad projects use Xcode’s synchronized folders. There is no Xcode on the Windows machine used for this repository, so those projects have not been compiled here.
Versions below are the ones pinned in the tree.
| Area | Choice | Where it is declared |
|---|---|---|
| Phone language | Kotlin 2.3.21 | android/gradle/libs.versions.toml |
| Phone UI | Jetpack Compose, Material 3, Compose BOM 2026.06.01 | same catalog |
| Android Gradle Plugin | 8.13.2 | same catalog |
| Phone compile / min SDK | 35 / 26 | android/app/build.gradle.kts |
| Phone database | Room 2.8.5, schema 10 | catalog and AppDatabase.kt |
| Phone preferences | DataStore 1.2.1 | catalog |
| Navigation and lifecycle | Navigation 2.9.8, Lifecycle 2.10.0 | catalog |
| Coroutines | 1.11.0 | catalog |
| Gmail access | Google API Client and the Gmail API service (google-api-services-gmail) |
catalog |
| Token storage on Android | AndroidX Security Crypto | catalog |
| Phone tests | JUnit 4.13.2, MockK, Turbine, Robolectric 4.17 | catalog |
| Desktop language | Rust, edition 2021 | root Cargo.toml |
| Desktop serialization and errors | serde, thiserror | root Cargo.toml |
| PKCE hashing | sha2, base64 | root Cargo.toml |
| Windows UI | egui and eframe 0.29 | Windows/Cargo.toml |
| Windows OS calls | windows crate 0.58 |
Windows/Cargo.toml |
| Apple UI | SwiftUI on iOS 17 | iOS/ and iPadOS/ Xcode projects |
Third-party code is pulled from Google Maven, Maven Central, and crates.io by those manifests. This repository does not vendor those libraries.
android/ Phone app (source of truth)
AndroidTablet/ Tablet Compose UI
iOS/ iPhone SwiftUI app
iPadOS/ iPad SwiftUI app
Windows/ Windows egui app
desktop-core/ Shared Rust contracts
Linux/ Linux backend adapters and context
macOS/ macOS backend adapters and context
execution plan/ Phase plans per platform
CONTEXT.md Continuity log
Plans live under execution plan/<platform>/. A plan file is not evidence that the phase is implemented. Where a plan and the current phone UI disagree, the phone UI wins.
- Mail HTML is untrusted. It is sanitized before display.
- Logs are not a place for bodies, tokens, or passwords.
- AI output cannot send, delete, or downgrade a security classification. The desktop AI router ships disabled.
- Company logos on the phone follow a fixed pipeline: a bundled logo, then a catalog asset, then a favicon lookup, then a letter. People stay on letter avatars. A favicon lookup sends the sender domain to Google. The desktop and tablet demos use letter circles only.
- Do not commit
.envfiles,local.properties, keystores, or OAuth client secrets.
The desktop Cargo workspace is marked Proprietary. This repository does not include a separate license file. Do not assume an open-source grant.
