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

PackageWhat it does
appThe Flutter app: screens, design system, demo mailbox, wiring (Riverpod, go_router).
mail_modelEvery shared type and interface. No I/O, no dependencies: the contract between packages.
mail_syncSync engine, offline queue, device rules, and the live MailRepository.
mail_storeEncrypted local store: drift, SQLite FTS5 for search, sqlite3mc for encryption.
mail_imapIMAP and SMTP on enough_mail, MIME composing, account discovery.
mail_jmapA JMAP client and transport: EmailSubmission, discovery, push, Sieve over JMAP.
mail_sieveRules as Sieve: script generation, ManageSieve client, the rule runner.
expr_searchThe search language: parser, formatter, local matcher, IMAP/Gmail/JMAP compilers.
readableThe Readable HTML reader, Original and Plain views, the image gallery, link analysis.
mail_cryptoOpenPGP (dart_pg) with Autocrypt, and S/MIME in pure Dart on pointycastle.
mail_calendariCalendar reading and writing, time zones, recurrences, iMIP replies.
mail_platformKeychain 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 emulator

Sign 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

  1. Push to mainEvery change is a commit on GitHub.
  2. Analyse and testdart analyze and 1,900+ tests across the workspace.
  3. Build and signRelease APKs for arm64 and x86_64, signed with Loupe's key.
  4. PublishAttached to the rolling Nightly release, served at loupe.mx/download.

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 analyze and 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

RiskHow it's handled
enough_mail, the IMAP library, has essentially one maintainerIt 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 notificationsBackground refresh, said plainly during setup; an optional content-free push relay is on the roadmap.
Google verification for Gmail sign-in is slow or rejectedThe narrowest scope, a clear privacy policy, and no server ever touching mail data. App passwords work meanwhile.
Readable mode mangles some messagesA corpus of real messages with golden tests, a one-tap Original view, and a hint when Original may look better.
Scope creep and burnoutStrict phase gates and a public roadmap with a "not planned" list.