Development
Built in the open, tested to the bone.
Loupe is written in Dart with Flutter, split into small packages with explicit contracts, and released under the MPL-2.0. Read it, build it, change it.
- Automated tests
- 1,900+
- Packages
- 11
- Commits
- 453
- Licence
- MPL-2.0
The stack
- Flutter 3.47Dart 3.13, one codebase for Android and iOS
- Riverpod + go_routerState and navigation
- drift + SQLite FTS5Local store and full-text search
- sqlite3mcEncryption of the local database
- enough_mailIMAP, SMTP and MIME
- Own JMAP clientRFC 8620/8621, Sieve over JMAP (RFC 9661)
- dart_pg + pointycastleOpenPGP and pure-Dart S/MIME
- GitHub ActionsAnalysis, tests and signed nightly APKs
Architecture
The app talks to one interface, MailRepository. Behind it, the sync engine keeps an encrypted local store in step with your servers through IMAP or JMAP transports. Every package depends onmail_model, which holds the contracts and nothing else.
Details and every contract: docs/ARCHITECTURE.md · the plan: docs/plan/PLAN.md
| Package | What it does |
|---|---|
app | The Flutter app: screens, design system, demo mailbox, wiring (Riverpod, go_router). |
mail_model | Every shared type and interface. No I/O, no dependencies: the contract between packages. |
mail_sync | Sync engine, offline queue, device rules, and the live MailRepository. |
mail_store | Encrypted local store: drift, SQLite FTS5 for search, sqlite3mc for encryption. |
mail_imap | IMAP and SMTP on enough_mail, MIME composing, account discovery. |
mail_jmap | A JMAP client and transport: EmailSubmission, discovery, push, Sieve over JMAP. |
mail_sieve | Rules as Sieve: script generation, ManageSieve client, the rule runner. |
expr_search | The search language: parser, formatter, local matcher, IMAP/Gmail/JMAP compilers. |
readable | The Readable HTML reader, Original and Plain views, the image gallery, link analysis. |
mail_crypto | OpenPGP (dart_pg) with Autocrypt, and S/MIME in pure Dart on pointycastle. |
mail_calendar | iCalendar reading and writing, time zones, recurrences, iMIP replies. |
mail_platform | Keychain credential store, OAuth sign-in and token refresh. |
Build from source
You need Flutter 3.47 (Dart 3.13). The repository is a pub workspace, so one command resolves every package.
# get the code
git clone https://github.com/BuenGenio/loupe.git
cd loupe
flutter pub get # all packages at once
dart analyze # must be clean
tool/ci/test.sh # every package's tests
cd app && flutter run # on a device or emulatorSign in with Google and Microsoft needs your own OAuth client IDs: seedocs/oauth-setup.md. Without them those buttons stay hidden.
From commit to your phone
- Push to mainEvery change is a commit on GitHub.
- Analyse and test
dart analyzeand 1,900+ tests across the workspace. - Build and signRelease APKs for arm64 and x86_64, signed with Loupe's key.
- PublishAttached to the rolling Nightly release, served at loupe.mx/download.
Contributing
Report a bug
Say what you did, what you expected and what happened, with your Android version and mail provider.
Suggest a feature
Start with the problem you want solved. The plan favours a few things done well.
Send a pull request
Keep dart analyze clean and add tests. Contract changes go in their own commit.
Report a vulnerability
Privately, by mail to security@loupe.mx, rather than in a public issue.
Contributions are accepted under the MPL-2.0, the project's licence. The OpenPGP library inthird_party/ keeps its own licence.
Engineering principles
Contracts first
Packages meet only through mail_model, which holds types and interfaces and does no I/O. A contract change is its own commit, and every user is fixed in the same change.
Shaped like JMAP
The data model follows JMAP: mailboxes with roles, emails with keywords, threads. IMAP is an adapter onto it, so JMAP was a new transport, not a rewrite.
Deterministic ids
Transports produce final local ids (mailbox path, UIDVALIDITY and UID for IMAP), so the store needs no mapping table.
State on your server
Snooze, Smart Mailboxes and rules use documented conventions on the mail server (a Snoozed folder, METADATA, Sieve), never a server of ours.
Phase gates
Each phase has a finish line; nothing from the next phase starts early. It keeps a one-developer project shippable.
Batteries included
The features people install desktop add-ons for are built in. Internal extension points come before any plugin API.
How it's tested
- 1,900+ unit and widget tests across the workspace: the search parser and its four compilers, the sanitiser, the sync state machine, crypto, calendar.
- A corpus of real-world messages with golden tests for Readable mode: a sanitiser change ships only if it improves the corpus without regressions.
- Integration tests against real servers: GreenMail, Dovecot and Stalwart (IMAP, JMAP, ManageSieve), from
tool/test-servers/. - Every push runs
dart analyzeand the tests in GitHub Actions before a signed build is published.
Licensing
- MPL-2.0, the same licence as Thunderbird: per-file copyleft, friendly to contributors and to app stores.
- A clean-room search parser: the language follows the Expression Search Reloaded add-on, but the Dart implementation is new, sharing only behaviour-level test cases.
- No borrowed GPL or AGPL code from other mail apps: ideas yes, code no.
- The OpenPGP library in
third_party/keeps its own licence.
Known risks, and the plan for each
| Risk | How it's handled |
|---|---|
| enough_mail, the IMAP library, has essentially one maintainer | It sits behind Loupe's own MailTransport interface; fixes go upstream, with budget for a fork. JMAP lowers the dependency. |
| iOS allows no persistent connection, so no instant notifications | Background refresh, said plainly during setup; an optional content-free push relay is on the roadmap. |
| Google verification for Gmail sign-in is slow or rejected | The narrowest scope, a clear privacy policy, and no server ever touching mail data. App passwords work meanwhile. |
| Readable mode mangles some messages | A corpus of real messages with golden tests, a one-tap Original view, and a hint when Original may look better. |
| Scope creep and burnout | Strict phase gates and a public roadmap with a "not planned" list. |