Files
JMR-devandClaude Opus 4.8 bae25b20f3 feat(onboarding): require GPL-3.0 license agreement as the first screen
Inserts a new LicenseScreen ahead of OnboardingWelcomeScreen as the
onboarding graph's start destination: the user must scroll the full
GPL-3.0 text and tap Agree before reaching anything else, or Decline
to exit the app outright. Acceptance is persisted
(SettingsRepository.licenseAccepted) so a user who agrees but exits
before adding an account isn't asked again, and the
NotificationPermissionEffect() request (#151) stays scoped to
OnboardingWelcomeScreen so it never fires on the license screen.

Closes #172

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 22:56:34 -05:00

181 lines
10 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!-- SPDX-License-Identifier: GPL-3.0-or-later -->
# LibreMail
A free and open-source email client for Android, built with Kotlin, Jetpack
Compose and Material 3 (Material You). LibreMail aims for a friendly default
experience with power-user features tucked under an **Advanced Settings** group.
> Status: **in development.** Material You shell; **onboarding** — a first-run flow from a
> welcome screen through a vendor picker (Outlook/Hotmail, Gmail, Yahoo, iCloud, or Other) and
> per-vendor setup to your first account's inbox; **account setup** — Outlook/Microsoft via
> OAuth 2.0 (AppAuth/PKCE), Gmail/Yahoo/iCloud via app password, and generic IMAP/SMTP, all
> with a live connection test and Keystore-encrypted credentials; **IMAP receive** — background
> sync (WorkManager) into a local Room cache with pull-to-refresh, backfilling your **entire**
> mail history (resumable) with an optional device-only retention cap; **reading** — message
> bodies fetched on open and rendered in a hardened WebView (JavaScript off, remote images
> blocked by default), with mark-read, star, and delete; **composing** — a rich-text HTML
> editor with a formatting toolbar and per-account signatures that sends
> `multipart/alternative` (HTML with a plaintext fallback) through a reliable background
> **outbox** (WorkManager-queued and retried, with a viewable outbox folder), plus
> device-contacts autocomplete, reply, and **drafts**; **on-device new-mail notifications** (no
> push service) with persisted settings; **instant push** via a foreground **IMAP IDLE**
> service; **attachments** — downloaded on demand and opened in a system viewer, and attach
> files when composing; **multiple accounts** — a unified inbox with per-account filtering;
> **search** across cached mail and the server (IMAP SEARCH); Outlook/Microsoft send via
> Microsoft **Graph** with an SMTP/XOAUTH2 fallback; an opt-in **app lock**
> (biometric/device-credential) that binds the encrypted cache key to your unlock; **mailto:**
> link handling with optional default-mail-app registration; and opt-in, F-Droid-safe **debug
> reporting** — local crash/error capture that you review (with a PII disclaimer) and submit
> only on an explicit action.
## Features (target MVP)
- Send and receive email with **Outlook/Microsoft** (OAuth 2.0), **Gmail, Yahoo and iCloud** (app password), and **any other IMAP/SMTP** provider.
- Guided first-run onboarding: welcome → vendor picker → per-vendor setup → your inbox.
- Material You dynamic theming, light/dark, edge-to-edge.
- Rich-text compose with a formatting toolbar, per-account signatures, and phone/account contacts integration.
- Offline-first: a local Room cache with full-history backfill and an optional device-only retention limit.
- Modern, opt-in security: OAuth 2.0 (Authorization Code + PKCE) for Outlook, Keystore-encrypted credentials, optional SQLCipher cache encryption, and a biometric/device-credential app lock.
## Tech stack
| Area | Choice |
|---|---|
| Language / UI | Kotlin, Jetpack Compose, Material 3 |
| Min / target SDK | 33 (Android 13) / 37 (Android 17) |
| Build | Gradle 9.6, AGP 9.2, Kotlin 2.4.0 (KSP, no KAPT) |
| DI | Hilt |
| Local cache | Room (single source of truth) + DataStore |
| Async / sync | Coroutines + Flow, WorkManager |
| Email transport | Jakarta / Angus Mail (IMAP + SMTP, XOAUTH2) |
| OAuth | AppAuth-Android |
## Building
### Prerequisites
- **Android Studio** (latest) or the command-line Android SDK.
- **JDK 17–21** for the Gradle daemon. AGP 9.2 does not yet support JDK 25, so if
your `JAVA_HOME` points at JDK 25, run Gradle with a 17–21 JDK (Android Studio's
bundled JBR is fine; from the CLI, set `JAVA_HOME` to a JDK 21 install).
- **Android SDK Platform 37** (`platforms;android-37.0`) and **Build-Tools 37**.
```bash
# Install the required SDK packages (accept licenses when prompted):
sdkmanager "platforms;android-37.0" "build-tools;37.0.0"
# Build, test and lint the debug variant:
./gradlew :app:assembleDebug
./gradlew :app:testDebugUnitTest
./gradlew :app:lintDebug
# Install on a connected device / emulator:
./gradlew :app:installDebug
```
`local.properties` (git-ignored) must point `sdk.dir` at your Android SDK; Android
Studio creates it automatically.
## Accounts and onboarding
On first launch LibreMail runs a short onboarding flow: a welcome screen, a **vendor picker**
(Outlook/Hotmail, Gmail, Yahoo, iCloud, or **Other**), per-vendor setup, and an "add another
account?" prompt before it drops you on your first account's inbox. You can add more accounts
later from settings; a unified inbox merges them with per-account filtering.
LibreMail supports three kinds of account:
- **Outlook / Hotmail** — signs in with **OAuth 2.0** through Microsoft (AppAuth); no password
is stored. See [Outlook / Microsoft account setup](#outlook--microsoft-account-setup-oauth-client)
below.
- **Gmail, Yahoo and iCloud** — preconfigured IMAP/SMTP that authenticate with a provider
**app password** (not your normal account password), preferring STARTTLS where the provider
supports it. Onboarding links you to each vendor's app-password page. **Gmail requires
2-Step Verification to be enabled** before Google will issue an app password.
- **Other** — a manual IMAP/SMTP form (host, port, security, and credentials) for any other
provider.
App passwords and OAuth tokens are held in a credential store encrypted with the Android
Keystore, and every account runs a live connection test before it is saved.
## Outlook / Microsoft account setup (OAuth client)
Outlook uses the Microsoft identity platform with OAuth 2.0 + PKCE (no client secret). Send
goes through Microsoft **Graph** (`sendMail`, their preferred API) with SMTP/XOAUTH2 as a
fallback; receive is **IMAP**. Graph and Exchange Online are separate resources, so one
consent grants every scope and per-resource access tokens are minted from the one refresh
token. A working client ID ships with the build; to use your own Azure app registration:
1. [Azure portal](https://portal.azure.com/) → **App registrations → New registration.**
Supported account types: *Accounts in any organizational directory and personal Microsoft
accounts*.
2. **Authentication → Add a platform → Mobile and desktop applications**; add the redirect
URI `org.libremail.outlook://oauth2redirect` and enable **Allow public client flows**.
3. **API permissions** (delegated): **Microsoft Graph → `Mail.Send`** (primary send), plus
**Office 365 Exchange Online → `IMAP.AccessAsUser.All` and `SMTP.Send`** (receive + SMTP
fallback). `openid`/`email`/`offline_access` come from OIDC.
4. Copy the **Application (client) ID** into `secrets.properties` as
`OUTLOOK_OAUTH_CLIENT_ID` (it overrides the built-in default).
## Privacy and data flow
LibreMail is offline-first: your mail lives in a local cache, and by default network traffic
goes only to your mail providers (IMAP/SMTP, plus Microsoft's OAuth and Graph endpoints for
Outlook). There is no analytics SDK and no always-on telemetry. The full privacy policy lives in
[`PRIVACY.md`](PRIVACY.md); Google Play compliance notes (data-safety mapping, permissions
justification) are under [`docs/`](docs/). Because Gmail uses an app password (no Google OAuth
scopes), no Google restricted-scope verification or CASA assessment applies; Outlook's OAuth
client is governed by Microsoft's Azure rules. The privacy-sensitive extras are all **opt-in**:
- **Cache encryption** — the Room cache can be encrypted at rest with **SQLCipher**. With the
optional **app lock** (biometric or device credential) enabled, the cache key is bound to
your authentication, so the database is only decrypted after you unlock the app.
- **Debug reporting** — **off by default.** When enabled, crashes and errors are captured
**locally**; you review the full report — shown with a plain-language **PII disclaimer** —
and it is sent only when you explicitly submit it, to a configurable (optional) endpoint.
There is no hosted crash pipeline collecting reports in the background.
- **Android Backup** — **off by default.** When you turn it on, only safe app settings are
backed up; the encrypted-database key, account credentials, and the mail cache are
**excluded**. Because Android's backup transport can route data through Google, it stays
disabled unless you opt in — the kind of optional behavior F-Droid lists as an anti-feature.
## Architecture
Offline-first, unidirectional, layered:
```
ui/ Compose screens + ViewModels (MVVM), Navigation Compose, Material You theme
domain/ Models + repository interfaces
data/ Room (entities, DAOs, database) + repository implementation (source of truth)
di/ Hilt modules
```
The UI observes Room via `Flow`; a sync engine (Angus Mail over IMAP/SMTP, plus Microsoft
Graph for Outlook send) writes into Room, and an auth layer (AppAuth for OAuth and an Android
Keystore-backed credential store for app passwords) handles sign-in.
## F-Droid
LibreMail is built to meet F-Droid's inclusion criteria: every dependency is
FOSS-licensed, there are no Google Play Services / Firebase / proprietary SDKs, the
build needs no `secrets.properties`, and there are **no anti-features to declare**
(the privacy-sensitive extras above are all opt-in). The full dependency license
audit, anti-feature review, and clean-room build verification live in
[`docs/fdroid-compliance.md`](docs/fdroid-compliance.md); the store listing is under
[`fastlane/metadata/android/`](fastlane/metadata/android/en-US), and
[`docs/fdroid/org.libremail.app.yml`](docs/fdroid/org.libremail.app.yml) is the
template for the eventual fdroiddata build recipe.
## License
LibreMail is licensed under the **GNU General Public License v3.0** — see
[`LICENSE`](LICENSE). SPDX identifier: `GPL-3.0-or-later`.
Onboarding requires agreeing to this license before anything else (#172); the screen that shows it
(`ui/onboarding/LicenseScreen.kt`) reads a bundled runtime copy at
`app/src/main/res/raw/license.txt` rather than this file directly. That copy is a byte-for-byte
duplicate, kept in sync by hand rather than generated at build time or annotated in place (the
in-app screen renders it verbatim, so any header/footer added to the copy would show up to the user
as if it were part of the license) — **if you edit `LICENSE`, copy the change into `license.txt`
too**, and vice versa.