|
Some checks are pending
CI / test (push) Waiting to run
Root ROADMAP.md: 5-milestone arc against the verified v1.0.0-rc2 baseline — GA-smoke + dogfood entry (M62), Done + folder views (M63), arrival pipeline completion (M64), compose completeness (M65), dogfood remediation + carryforwards + arc close (M66). Pre-1.0 versions 0.62.0-0.66.0 (owner decision); GA cut stays a post-arc owner call. Written for /milestone-cycle, executable by lesser models. Also: archive the M61 ship SESSION_NOTES and roll forward (section 8 seeds M62); rewrite TODOS.md to truth (M37-1/2/3/5 + bundler-traits resolved by later milestones — audit table added; F4 -> M66, M37-4 -> M65); pointer banner + M44-M47 checkmarks on the historical roadmap. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gz5s2C9dv2yjVYpR2j5Dby |
||
|---|---|---|
| .forgejo/workflows | ||
| .github/workflows | ||
| docs | ||
| inboxly | ||
| inboxly-bundler | ||
| inboxly-core | ||
| inboxly-extract | ||
| inboxly-imap | ||
| inboxly-store | ||
| inboxly-ui | ||
| .gitignore | ||
| AGENTS.md | ||
| Cargo.lock | ||
| Cargo.toml | ||
| CHANGELOG.md | ||
| CLAUDE.md | ||
| GEMINI.md | ||
| README.md | ||
| ROADMAP.md | ||
| TODOS.md | ||
📬 Inboxly
A recreation of Inbox by Google as a native desktop email client, built in Rust with Dioxus.
Google killed Inbox in April 2019. Nothing has replaced its unique approach to email — bundles, highlights, snooze, sweep, and smart extraction. Inboxly brings it back as a fully local, privacy-respecting desktop application.
✨ Features
📥 Inbox Feed
- Date-grouped sections — Pinned, Today, Yesterday, This Week, This Month, Earlier
- Email rows with sender avatar, subject, snippet, timestamp, attachment/unread indicators
- Bundle rows — collapsible groups (Social, Promos, Updates, Finance, etc.)
- Inbox Zero 🌞 — celebratory view when you've handled everything
✅ Triage Actions
- Done — archive threads with one click (keyboard:
e) - Pin 📌 — keep important threads at the top (keyboard:
=) - Sweep — bulk-archive all unpinned threads
- 7-second undo — timed snackbar with one-click revert
🕐 Snooze
- 5 preset options — Later Today, Tomorrow, This Weekend, Next Week, Someday
- Computed UTC times — respects time zones and weekend preferences
- Snoozed view — dedicated view for all snoozed threads
✉️ Compose
- Full compose view — To, Cc, Subject, Body fields (920dp max-width)
- Reply modes — New, Reply, Reply All, Forward
- Send / Discard — with confirmation
🔍 Search
- Query parser —
from:,to:,subject:,has:attachment,is:unreadoperators - Result view — empty state, no-results state, result list
📋 Menus & Context Actions
- PopupMenu widget — reusable dropdown/context menu overlay (custom Iced
Widget+Overlay) - Overflow menu (⋮) — appears on hover with Move to, Mark read/unread, Mute, Reply, Forward, Block sender, Report spam
- Right-click context menu — Done/Pin/Snooze quick actions + full overflow menu at cursor position
- RightClickArea widget — custom Iced widget for right-click event interception
👤 Account Switcher
- Inline expansion in nav drawer — 44px avatar, display name, email, chevron
- Multi-account support — switch between configured accounts with one click
- Active account indicator — blue highlight + checkmark
- Add account — navigates to Settings → Accounts
⚙️ Settings (6 tabs)
| Tab | Controls |
|---|---|
| 🎨 General | Theme (System/Light/Dark chips), Default View, Snooze Presets, Undo Timeout |
| 👤 Accounts | Account cards with avatar, add/edit/remove forms, active-deletion prevention |
| 📦 Bundles | Reorder (↑↓ arrows), throttle badge (Immediate/Daily/Weekly), visibility toggle |
| 🔔 Notifications | Desktop notification toggle, sound toggle, per-bundle selection |
| ⌨️ Keyboard Shortcuts | 18 remappable actions, click-to-capture UI, delta-only JSON persistence |
| 💾 Data & Storage | Clear cache, rebuild search index, export (stub), storage sizes, last sync |
🎨 Theming
- Light & dark themes — BigTop design tokens from original Google Inbox APK
- System detection — D-Bus
org.freedesktop.portal.Settingsfor automatic theme - Live switching — theme changes apply instantly from Settings
- Themed toolbar — blue (Inbox), orange (Snoozed), green (Done), grey (Settings)
⌨️ Keyboard Shortcuts
| Key | Action | Key | Action |
|---|---|---|---|
e |
Done (archive) | c |
Compose |
= |
Pin / unpin | r |
Refresh |
b |
Snooze | / |
Search |
j / k |
Next / previous | o |
Open thread |
Ctrl+Z |
Undo | ? |
Help |
g i / g s / g d |
Go to Inbox / Snoozed / Done | Esc |
Close / back |
Shift+R |
Reply | Shift+A |
Reply All |
f |
Forward |
All shortcuts are runtime-remappable via Settings → Keyboard Shortcuts.
🏗️ Architecture
7-crate Cargo workspace:
| Crate | Purpose |
|---|---|
inboxly-core |
🧱 Core types, traits, and error definitions |
inboxly-imap |
📡 IMAP sync engine and OAuth2 authentication |
inboxly-store |
💾 Maildir, SQLite, and Tantivy storage layer |
inboxly-bundler |
📦 Email categorisation engine |
inboxly-extract |
🔍 Smart extraction and highlight detection |
inboxly-ui |
🖥️ Iced-based desktop UI |
inboxly |
🚀 Binary entry point |
Dependency Graph
inboxly-core (foundation — zero internal deps)
├── inboxly-imap
├── inboxly-store
├── inboxly-extract
├── inboxly-bundler (+ inboxly-store)
└── inboxly-ui (all subcrates)
└── inboxly (binary)
Key Design Patterns
- Custom Iced widgets —
PopupMenu(Widget + Overlay),RightClickArea(event interception) - View-local message enums —
InboxViewMessagedecouples views from app-levelMessage - Auto-save settings — every control change persists immediately, no dirty-state tracking
- Delta persistence —
ShortcutMapstores only non-default overrides as JSON - Store adapter pattern —
StoreSettingsAdapterbridges crate boundaries without circular deps
🚀 Building
# Build
cargo build --workspace
# Test (1575 tests, full workspace)
cargo test --workspace
# Lint — full workspace, mirrors CI
cargo clippy --workspace --all-targets -- -D warnings
# Run
cargo run -p inboxly
Requires Rust edition 2024 (rustc 1.85+).
🔑 OAuth2 sign-in setup (Gmail / Outlook)
The "Sign in with Google" / "Sign in with Outlook" buttons need an OAuth client ID from your own provider app registration. Inboxly never ships a shared client ID — every install brings its own, so no one can impersonate the app. There are two ways to supply it; the app resolves env-var first, falling back to the in-app value.
In-app (recommended for end users). Register an app, then paste the client ID — no terminal, env var, or restart needed:
- Gmail → create an OAuth client ID (application type Desktop app) in the Google Cloud Console. Outlook → register an app in the Microsoft Entra admin center and copy its Application (client) ID.
- In Inboxly, open the first-run wizard or Settings → Accounts and pick the provider; the Sign-in setup form appears.
- Paste the client ID (leave Client secret blank for PKCE desktop apps), click Save, and the sign-in button lights up immediately.
The client ID is saved to config.toml ([oauth2.gmail] / [oauth2.outlook])
so it persists across restarts. An optional client secret (confidential apps
only) goes to your OS keyring, never to disk. Remove either with the form's
Clear button.
Environment variables (operators / CI). Export the provider's variable(s) in the shell that launches Inboxly; these take precedence over a stored value:
# Gmail
export INBOXLY_OAUTH2_CLIENT_ID=... # required
export INBOXLY_OAUTH2_CLIENT_SECRET=... # optional (Web-type apps only)
# Outlook
export INBOXLY_OUTLOOK_OAUTH2_CLIENT_ID=... # required
export INBOXLY_OUTLOOK_OAUTH2_CLIENT_SECRET=... # optional (confidential apps)
cargo run -p inboxly
If neither source provides a client ID, the sign-in button stays disabled and an inline hint points you at the Sign-in setup form.
📊 Project Status
| Milestone | Version | Tests | Description |
|---|---|---|---|
| M1-M25 | v0.25.0 | 715 | 🏁 Full v1 UI scaffold |
| M26 | v0.26.0 | 756 | 📋 PopupMenu widget |
| M27 | v0.27.0 | 774 | 📋 Overflow + context menus |
| M28 | v0.28.0 | 790 | 👤 Account switcher |
| M29 | v0.29.0 | 808 | ⚙️ Settings framework (3 tabs) |
| M30 | v0.30.0 | 841 | ⚙️ Settings complete (6 tabs) |
| M31 | v0.31.0 | — | 🧱 Store traits + action execution |
| M32 | v0.32.0 | 225 | 🔄 Iced 0.14 → Dioxus 0.7 framework conversion |
| M33 | v0.33.0 | 242 | 📥 Inbox feed + widgets restored on Dioxus |
| M33.1 | v0.33.1 | 870 | 🔧 Post-M33 polish (window title, dark mode) |
| M34 | v0.34.0 | 882 | 🧵 Thread detail view + HTML email rendering |
| M34.1 | v0.34.1 | 884 | 🔧 Post-M34 polish (test side effect, validate_external_url, Settings re-entry) |
| M35 | v0.35.0 | 961 | ✉️ SMTP engine + compose view + drafts (M35a refactor + M35b feature work) |
| M35.1 | v0.35.1 | 961 | 🔧 Post-M35 dogfooding polish (inline CSS, 1280×800 default, FAB hide, blur-to-chip) |
| M36 | v0.36.0 | 1072 | ✉️ Reply/ReplyAll/Forward + M35 cleanup (keyring, OAuth2 persistence, Sent write, explicit save) |
| M36.1 | v0.36.1 | 1076 | 🔧 Data-layer startup wiring — binary now instantiates Store / MaildirStore / ThreadReader |
| M37 | v0.37.0 | 1180 | 📎 Full Attachment Support — download, inline images, PDF preview, DnD, size guardrails, forward streaming |
| M38 | v0.38.0 | 1222 | 🔍 Advanced Search — operator parser, two-phase Tantivy+SQLite search, thread grouping, state filters, search history, SearchView |
| M39 | v0.39.0 | 1233 | 📦 End-to-End Bundling — auto-categorise on sync, move-to-bundle with affinity learning, block sender, rule dialog, throttle filtering |
| M40–M47 | v0.40.0–v0.47.0 | — | 🔧 Snooze system, smart highlights & extraction, reminders UI, settings-UI rendering, and the M44–M47 gap-remediation arc (first-run enhancement) |
| M48–M59 | v0.48.0–v0.59.0 | 1526 | 🎯 Design-parity arc — real auth, OAuth2 (Gmail+Outlook), on-demand body fetch + thread reader, trip itineraries, PDF/OCR extraction, persistent snooze, compose-signal refactor, bundle rules, shortcut editing, JSON data export |
| M60 | v1.0.0-rc1 | 1526 | 🏁 Design-parity arc terminus — carryforwards cleanup + release candidate |
| M61 | v1.0.0-rc2 | 1575 | 🔐 In-app OAuth activation — paste your own OAuth client_id in Settings / first-run wizard (Gmail + Outlook); no env var or restart. Review fixes F1–F3. |
Current: v1.0.0-rc2 (1575 tests, 0 clippy warnings, 7-crate workspace)
M36 highlights
M36 closes every M35.1 follow-up item and ships the
Reply / ReplyAll / Forward feature surface on top. Fourteen phases across
two sub-milestones under a /plan-eng-review pass (findings A1-A9) and a
Gemini outside-voice pass (findings G1-G8):
- M36a (Phases 0-5) — M35 cleanup: a real
keyring 3secrets backend (replaces theINBOXLY_SMTP_PASSWORDenv var), OAuth2 refresh token persistence with a rotation callback (A3), a local.Sent/write on every successful SMTP send + real IMAPAPPENDviaWellKnownFolders(A6), and an explicit Save Draft bridge with a three-layer persistence model (in-memory → SQLite → offline queue) plus a Navigate-with-compose auto-save guard (A8). NewinboxlyCLI subcommands:oauth2-authorize,set-password,delete-credentials. - M36b (Phases 6-13) — Reply feature surface: pure helpers for
subject normalization + References chain + Gmail-compatible quote
formatting, a
compose_state_from_originalDRY helper +ComposeModedispatch, a realOpenComposeReplyhandler with a body-fetch fallback for header-only local copies (G3) via newThreadReader::load_email, forward attachment passthrough, Reply / ReplyAll / Forward buttons in the thread message footer, an inline compose panel with a layout toggle (Inline ↔ FullScreen), a quoted-original placeholder, and 11 end-to-end state-machine integration tests.
M36.1 patch — data-layer startup wiring (v0.36.1). The runtime gap
M36 phase 14 flagged (the binary never instantiating Store /
MaildirStore / ThreadReader) is closed as of v0.36.1. main()
now resolves XDG paths, opens SQLite, initialises per-account Maildir
stores, and publishes both through new inboxly-ui::startup::STORE and
MAILDIR_STORES singletons (gated behind a MainThreadOnly<T> Sync
wrapper because rusqlite's Connection is !Sync). App() reads
them on first render and builds a ThreadReader for the active
account; SwitchAccount reconstructs it on every account change.
Fail-soft at every step — paths-unresolved, ensure_dirs, Store::open,
and MaildirStore::init errors all log a warning and leave the binary
in the pre-patch None state instead of crashing. CLI subcommands
(--help, oauth2-authorize, set-password, delete-credentials)
still short-circuit before any data-layer call, preserving the Gemini
G6 keyring-free-help invariant. See CHANGELOG.md for the full M36
phase-by-phase breakdown and the four documented scope reductions
(Phase 9 forward streaming, Phase 11 signal split, Phase 12 quoted
original expansion, Phase 5 IMAP draft replay).
M35 highlights
M35 closes the read/write loop. After M34 users could read email via the thread detail view; after M35 they can write it too. The milestone shipped in two sub-milestones:
- M35a — behaviour-preserving god-object refactor (31 flat fields on
Inboxlyextracted intoSettingsState,MenuState,SnoozeState) - M35b — 14 phases covering lettre 0.11 API verification, core data
types, SQLite drafts table, SMTP transport with dual message builders
(Gemini G1 Bcc-not-in-headers invariant), retry logic with PII-redacted
logging, IMAP APPEND helpers, sync-side Message-ID dedup (G8), offline
replay via a
DraftSendertrait, ComposeState + 23 Message variants, CSS, the ComposeView Dioxus component, FAB wiring, 30 s auto-save bridge, rfd attachment picker, and the send bridge with two-phase commit dismiss overlay (G9) + AppendSent fallback (G6).
All six M35b limitations scheduled for M36 were closed in M36a (Phases
0-5): keyring-backed password secrets replace the INBOXLY_SMTP_PASSWORD
env var, OAuth2 refresh token persistence with rotation callback is live
end-to-end, SmtpClient::send() writes to the local Maildir .Sent/
atomically on every success, the real AppendSent replay handler
resolves the Sent folder via WellKnownFolders, the explicit Save Draft
bridge landed with a three-layer persistence model and a
Navigate-with-compose auto-save guard, and the toolbar Draft chip shows
unsaved / saving / saved state. Remaining deferrals after M36 are four
smaller scope reductions (all TODO(post-M36) in-tree): forward
attachment streaming, compose signal split, quoted-original expanded
preview, and IMAP APPEND for drafts. See CHANGELOG.md for the full
phase-by-phase breakdown.
📄 Licence
GPL-3.0-only