Merge branch 'main' into feat-play-compliance
This commit is contained in:
@@ -154,6 +154,18 @@ The UI observes Room via `Flow`; a sync engine (Angus Mail over IMAP/SMTP, plus
|
||||
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
|
||||
|
||||
@@ -96,6 +96,16 @@ android {
|
||||
buildConfig = true
|
||||
}
|
||||
|
||||
// F-Droid compliance (issue #16): by default AGP embeds a "dependency info block" in the APK
|
||||
// signing block — a list of every dependency, encrypted so that ONLY Google Play can read it.
|
||||
// F-Droid's inclusion policy treats that opaque, Google-only blob as a blocker (it cannot be
|
||||
// verified from source and breaks reproducible builds), so keep it out of APKs and bundles.
|
||||
// See docs/fdroid-compliance.md.
|
||||
dependenciesInfo {
|
||||
includeInApk = false
|
||||
includeInBundle = false
|
||||
}
|
||||
|
||||
packaging {
|
||||
resources {
|
||||
// Angus Mail / Jakarta Activation (added later) ship duplicate META-INF entries.
|
||||
|
||||
@@ -256,9 +256,16 @@ class MailRepositoryImpl @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
/** Resolves the full name of an account's folder for [role], refreshing the cache once if needed. */
|
||||
/**
|
||||
* Resolves the full name of an account's folder for [role], refreshing the cache once if needed.
|
||||
* Among same-role selectable folders (e.g. `[Gmail]/Spam` via RFC 6154 `\Junk` plus a user label
|
||||
* "Spam" matched by name), the server-advertised special-use folder wins regardless of LIST order,
|
||||
* so mail reaches the provider's built-in mailbox; absent one, the earliest LISTed folder is kept
|
||||
* (`maxByOrNull` returns the first max).
|
||||
*/
|
||||
private suspend fun resolveRoleFolder(accountId: String, role: FolderRole): String? {
|
||||
fun pick(folders: List<FolderEntity>) = folders.firstOrNull { it.role == role.name && it.selectable }?.fullName
|
||||
fun pick(folders: List<FolderEntity>) =
|
||||
folders.filter { it.role == role.name && it.selectable }.maxByOrNull { it.specialUse }?.fullName
|
||||
pick(folderDao.getForAccountOnce(accountId))?.let { return it }
|
||||
// The folder cache can be cold (the user may not have opened the drawer yet); refresh and retry.
|
||||
runCatching { refreshFolders(accountId) }
|
||||
|
||||
@@ -399,8 +399,73 @@ class MailRepositoryImplTest {
|
||||
coVerify { imapClient.moveMessages(any(), "INBOX", listOf("14"), "Archive") }
|
||||
}
|
||||
|
||||
private fun folderEntity(fullName: String, role: String) =
|
||||
FolderEntity("acct", fullName, fullName.substringAfterLast('/'), role, selectable = true, sortOrder = 0)
|
||||
@Test
|
||||
fun `reportSpam prefers the special-use spam folder when the user folder is listed first`() = runTest {
|
||||
val id = "acct:INBOX:16"
|
||||
coEvery { messageDao.getById(id) } returns messageEntity(id, "INBOX")
|
||||
coEvery { messageDao.deleteByIds(any()) } just Runs
|
||||
coEvery { accountDao.getById("acct") } returns accountEntity()
|
||||
coEvery { connectionFactory.imapParamsFor(any()) } returns imapParams()
|
||||
// A user label "Spam" (role from its name) is LISTed before Gmail's built-in \Junk folder.
|
||||
coEvery { folderDao.getForAccountOnce("acct") } returns listOf(
|
||||
folderEntity("Spam", "SPAM"),
|
||||
folderEntity("[Gmail]/Spam", "SPAM", specialUse = true),
|
||||
)
|
||||
|
||||
val result = repository.reportSpam(listOf(id))
|
||||
|
||||
assertTrue(result.isSuccess)
|
||||
coVerify { imapClient.moveMessages(any(), "INBOX", listOf("16"), "[Gmail]/Spam") }
|
||||
coVerify(exactly = 0) { imapClient.moveMessages(any(), any(), any(), "Spam") }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `reportSpam prefers the special-use spam folder when it is listed first`() = runTest {
|
||||
val id = "acct:INBOX:18"
|
||||
coEvery { messageDao.getById(id) } returns messageEntity(id, "INBOX")
|
||||
coEvery { messageDao.deleteByIds(any()) } just Runs
|
||||
coEvery { accountDao.getById("acct") } returns accountEntity()
|
||||
coEvery { connectionFactory.imapParamsFor(any()) } returns imapParams()
|
||||
coEvery { folderDao.getForAccountOnce("acct") } returns listOf(
|
||||
folderEntity("[Gmail]/Spam", "SPAM", specialUse = true),
|
||||
folderEntity("Spam", "SPAM"),
|
||||
)
|
||||
|
||||
val result = repository.reportSpam(listOf(id))
|
||||
|
||||
assertTrue(result.isSuccess)
|
||||
coVerify { imapClient.moveMessages(any(), "INBOX", listOf("18"), "[Gmail]/Spam") }
|
||||
coVerify(exactly = 0) { imapClient.moveMessages(any(), any(), any(), "Spam") }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `reportSpam keeps the first listed folder when no special-use folder holds the role`() = runTest {
|
||||
val id = "acct:INBOX:20"
|
||||
coEvery { messageDao.getById(id) } returns messageEntity(id, "INBOX")
|
||||
coEvery { messageDao.deleteByIds(any()) } just Runs
|
||||
coEvery { accountDao.getById("acct") } returns accountEntity()
|
||||
coEvery { connectionFactory.imapParamsFor(any()) } returns imapParams()
|
||||
// No SPECIAL-USE advertised (common outside the big providers): LIST order still decides.
|
||||
coEvery { folderDao.getForAccountOnce("acct") } returns listOf(
|
||||
folderEntity("Junk", "SPAM"),
|
||||
folderEntity("Spam", "SPAM"),
|
||||
)
|
||||
|
||||
val result = repository.reportSpam(listOf(id))
|
||||
|
||||
assertTrue(result.isSuccess)
|
||||
coVerify { imapClient.moveMessages(any(), "INBOX", listOf("20"), "Junk") }
|
||||
}
|
||||
|
||||
private fun folderEntity(fullName: String, role: String, specialUse: Boolean = false) = FolderEntity(
|
||||
accountId = "acct",
|
||||
fullName = fullName,
|
||||
displayName = fullName.substringAfterLast('/'),
|
||||
role = role,
|
||||
selectable = true,
|
||||
sortOrder = 0,
|
||||
specialUse = specialUse,
|
||||
)
|
||||
|
||||
private fun attachmentEntity(messageId: String, partIndex: Int, filename: String) =
|
||||
AttachmentEntity(messageId, partIndex, filename, "application/octet-stream", 10L)
|
||||
|
||||
@@ -0,0 +1,236 @@
|
||||
<!-- SPDX-License-Identifier: GPL-3.0-or-later -->
|
||||
# F-Droid compliance
|
||||
|
||||
Audit for issue #16, performed 2026-07-01 against `main` (versionName 0.1.0 /
|
||||
versionCode 1). **Verdict: LibreMail meets F-Droid's inclusion criteria with no
|
||||
anti-features to declare.** Every runtime dependency is FOSS-licensed and
|
||||
GPL-3.0-or-later-compatible, there are no Google Play Services / Firebase /
|
||||
proprietary artifacts, no non-free Gradle plugins, and a clean-room build (no
|
||||
`secrets.properties`, no proprietary keys) produces an installable APK.
|
||||
|
||||
Companion deliverables:
|
||||
|
||||
- `fastlane/metadata/android/en-US/` — the store listing F-Droid reads from this repo.
|
||||
- `docs/fdroid/org.libremail.app.yml` — template + instructions for the build recipe
|
||||
that goes into [fdroiddata](https://gitlab.com/fdroid/fdroiddata).
|
||||
|
||||
## 1. Dependency license audit
|
||||
|
||||
### 1.1 Runtime dependencies (what ships in the APK)
|
||||
|
||||
Enumerated with `./gradlew :app:dependencies --configuration releaseRuntimeClasspath`.
|
||||
Direct dependencies, with the resolved versions at audit time:
|
||||
|
||||
| Dependency | Version | License | Role |
|
||||
|---|---|---|---|
|
||||
| `androidx.core:core-ktx` | 1.17.0 | Apache-2.0 | AndroidX core |
|
||||
| `androidx.lifecycle:lifecycle-runtime-ktx` / `-runtime-compose` / `-viewmodel-compose` | 2.9.4 | Apache-2.0 | Lifecycle/MVVM |
|
||||
| `androidx.activity:activity-compose` | 1.12.4 | Apache-2.0 | Compose host activity |
|
||||
| `androidx.navigation:navigation-compose` | 2.9.8 | Apache-2.0 | Navigation |
|
||||
| `androidx.compose.*` (BOM 2026.06.00: ui, ui-graphics, material3, material-icons-core, …) | 1.11.3 / m3 1.4.0 | Apache-2.0 | UI toolkit |
|
||||
| `androidx.webkit:webkit` | 1.12.1 | Apache-2.0 | Hardened WebView compat |
|
||||
| `androidx.work:work-runtime-ktx` | 2.11.2 | Apache-2.0 | Background sync/outbox |
|
||||
| `androidx.datastore:datastore-preferences` | 1.2.1 | Apache-2.0 | Settings store |
|
||||
| `androidx.room:room-runtime` / `room-ktx` | 2.8.4 | Apache-2.0 | Local mail cache |
|
||||
| `androidx.hilt:hilt-navigation-compose` / `hilt-work` | 1.3.0 | Apache-2.0 | Hilt integrations |
|
||||
| `org.jetbrains.kotlin:kotlin-stdlib` | 2.4.0 | Apache-2.0 | Kotlin runtime |
|
||||
| `org.jetbrains.kotlinx:kotlinx-coroutines-android` | 1.10.2 | Apache-2.0 | Coroutines |
|
||||
| `com.google.dagger:hilt-android` (Dagger/Hilt) | 2.60 | Apache-2.0 | Dependency injection |
|
||||
| `org.eclipse.angus:angus-mail` (+ `angus-activation`) | 2.0.5 / 2.0.3 | EPL-2.0 OR GPL-2.0 w/ Classpath-exception OR EDL-1.0 (BSD-3-Clause) | IMAP/SMTP transport |
|
||||
| `net.openid:appauth` | 0.11.1 | Apache-2.0 | OAuth 2.0 + PKCE (Outlook) |
|
||||
| `net.zetetic:sqlcipher-android` | 4.16.0 | BSD-3-Clause-style (SQLCipher Community Edition) | Opt-in cache encryption |
|
||||
|
||||
Transitive dependencies, grouped (full tree available from the Gradle command above):
|
||||
|
||||
| Group | License | Notes |
|
||||
|---|---|---|
|
||||
| `androidx.*` (~60 artifacts: appcompat, browser, collection, emoji2, fragment, savedstate, sqlite, startup, tracing, window, …) | Apache-2.0 | AndroidX |
|
||||
| `org.jetbrains.*` (kotlin-stdlib, kotlinx-coroutines, kotlinx-serialization, annotations) | Apache-2.0 | JetBrains |
|
||||
| `com.google.dagger:*` (dagger, hilt-core, dagger-lint-aar) | Apache-2.0 | via Hilt |
|
||||
| `com.google.code.findbugs:jsr305` 3.0.2 | Apache-2.0 | annotations only |
|
||||
| `com.google.guava:listenablefuture` 1.0 | Apache-2.0 | empty stub artifact (not Guava) |
|
||||
| `com.squareup.okio:okio` 3.9.1 | Apache-2.0 | via DataStore |
|
||||
| `jakarta.mail:jakarta.mail-api` 2.1.5 | EPL-2.0 OR GPL-2.0 w/ CPE OR EDL-1.0 | via Angus Mail |
|
||||
| `jakarta.activation:jakarta.activation-api` 2.1.4 | EDL-1.0 (BSD-3-Clause) | via Angus Mail |
|
||||
| `jakarta.inject:jakarta.inject-api` 2.0.1, `javax.inject:javax.inject` 1 | Apache-2.0 | DI annotations |
|
||||
| `org.jspecify:jspecify` 1.0.0 | Apache-2.0 | nullness annotations |
|
||||
|
||||
**GPL compatibility.** Everything is Apache-2.0 or BSD-3-Clause except the
|
||||
Jakarta/Angus mail stack, which is tri-licensed; LibreMail uses it under the
|
||||
EDL-1.0 (BSD-3-Clause) / GPL-2.0-with-Classpath-exception options, both of which are
|
||||
GPL-3.0-or-later-compatible. **No proprietary, source-unavailable, or
|
||||
"free for open source use only" artifact appears anywhere in the tree.** In
|
||||
particular there is **no** `com.google.android.gms:*` (Play Services), **no**
|
||||
`com.google.firebase:*`, no Play Billing/Install Referrer, and no analytics or
|
||||
crash-reporting SDK.
|
||||
|
||||
Native libraries in the release APK — all from the audited dependencies above:
|
||||
`libsqlcipher.so` (SQLCipher), `libdatastore_shared_counter.so` (AndroidX DataStore),
|
||||
`libandroidx.graphics.path.so` (AndroidX, via Compose).
|
||||
|
||||
### 1.2 Build-time dependencies (never ship in the APK)
|
||||
|
||||
Enumerated with `./gradlew buildEnvironment :app:buildEnvironment`:
|
||||
|
||||
| Plugin / tool | License |
|
||||
|---|---|
|
||||
| Android Gradle Plugin 9.2.x (`com.android.tools.*`) | Apache-2.0 |
|
||||
| Kotlin Gradle plugin + Compose compiler 2.4.0 | Apache-2.0 |
|
||||
| KSP 2.3.9 | Apache-2.0 |
|
||||
| Hilt Gradle plugin 2.60 | Apache-2.0 |
|
||||
| ktlint-gradle 14.2.0 (`org.jlleitschuh.gradle`) | MIT |
|
||||
| detekt 2.0.0-alpha.5 (`dev.detekt`) | Apache-2.0 |
|
||||
|
||||
Their transitive tooling deps (protobuf, Tink, flatbuffers, bouncycastle,
|
||||
juniversalchardet, jose4j, …) are Apache-2.0/MIT/MPL — all FOSS. **No non-free
|
||||
Gradle plugin is used** (no Play Publisher, no Crashlytics/Google Services plugin,
|
||||
no proprietary obfuscator; R8 ships with AGP and is Apache-2.0).
|
||||
|
||||
Artifacts resolve exclusively from open repositories: `google()` and
|
||||
`mavenCentral()` (plus `gradlePluginPortal()` for the lint/format plugins).
|
||||
`gradle-wrapper.jar` is the standard Gradle 9.6 wrapper (Apache-2.0), verifiable
|
||||
against the official distribution.
|
||||
|
||||
### 1.3 APK payload hygiene
|
||||
|
||||
AGP by default embeds a *dependency info block* in the APK signing block: a list of
|
||||
every dependency **encrypted with a Google Play public key**, readable only by
|
||||
Google. That opaque blob is a known F-Droid blocker (it cannot be verified from
|
||||
source and breaks reproducible-build verification), so this repo disables it in
|
||||
`app/build.gradle.kts`:
|
||||
|
||||
```kotlin
|
||||
dependenciesInfo {
|
||||
includeInApk = false
|
||||
includeInBundle = false
|
||||
}
|
||||
```
|
||||
|
||||
## 2. Anti-feature review
|
||||
|
||||
Reviewed against the [F-Droid anti-feature list](https://f-droid.org/docs/Anti-Features/),
|
||||
based on the manifest and source at audit time. **Declared anti-features: none.**
|
||||
|
||||
| Anti-feature | Verdict | Reasoning |
|
||||
|---|---|---|
|
||||
| `Ads` | Clear | No advertising of any kind. |
|
||||
| `Tracking` | Clear | No analytics/telemetry SDK; no identifiers are collected. Debug reporting is off by default, local-only, user-reviewed, and user-submitted (§2.1). |
|
||||
| `NonFreeNet` | Clear | Generic IMAP/SMTP client, fully functional against free-software mail servers; the Microsoft integration is optional and user-chosen (§2.2). |
|
||||
| `NonFreeAdd` | Clear | No add-ons; nothing is upsold. |
|
||||
| `NonFreeDep` | Clear | Dependency audit in §1: every dependency is FOSS. |
|
||||
| `NonFreeAssets` | Clear | All assets are first-party vector drawables carrying the repo's GPL SPDX headers; no bundled proprietary art, fonts, or blobs. |
|
||||
| `NSFW` | Clear | N/A. |
|
||||
| `UpstreamNonFree` | Clear | This repo is the upstream and is wholly GPL-3.0-or-later. |
|
||||
| `KnownVuln` | Clear | No dependency with a known security vulnerability is pinned at audit time (all on current stable lines). |
|
||||
| `ApplicationDebuggable` | Clear | Release builds are non-debuggable (AGP default) and R8-minified. |
|
||||
| `TetheredNet` | Clear | No tethered/proprietary backend; the app talks to the user's own mail servers. |
|
||||
| `NoSourceSince` | Clear | N/A — source is published. |
|
||||
|
||||
### 2.1 Debug reporting is not `Tracking`
|
||||
|
||||
The crash/debug-report pipeline (`org.libremail.reporting.*`) is designed to stay on
|
||||
the right side of F-Droid's Tracking definition ("reports user activity ... without
|
||||
consent"):
|
||||
|
||||
- **Off by default.** Nothing is captured until the user enables debug reporting.
|
||||
- **Local capture only.** Reports (app/OS version, device model, stack trace, a
|
||||
non-PII settings summary, recent in-app log lines) are stored on-device.
|
||||
`DiagnosticsCollector` deliberately collects no account emails, server names,
|
||||
message content, or hardware/advertising identifiers.
|
||||
- **User-initiated, reviewed submission.** A report leaves the device only when the
|
||||
user opens it, sees the full contents plus a PII disclaimer, and taps Submit
|
||||
(`ReportSubmitter` is the single egress seam).
|
||||
- **No endpoint in F-Droid builds.** The ingest URL comes from
|
||||
`BuildConfig.DEBUG_REPORT_ENDPOINT`, default **empty** (settable only via the
|
||||
git-ignored `secrets.properties`). An F-Droid build therefore *cannot* transmit a
|
||||
report anywhere; the UI steers users to copy/save the report instead.
|
||||
|
||||
### 2.2 Outlook OAuth / Microsoft Graph is not `NonFreeNet`
|
||||
|
||||
`NonFreeNet` applies to apps that *promote or depend entirely on* a non-free network
|
||||
service. LibreMail is a general-purpose email client: it works fully against any
|
||||
IMAP/SMTP server, including self-hosted free-software stacks (Dovecot/Postfix, …),
|
||||
and no Microsoft endpoint is ever contacted unless the user adds an
|
||||
Outlook/Microsoft account. For transparency:
|
||||
|
||||
- Adding an Outlook account uses OAuth 2.0 + PKCE against
|
||||
`login.microsoftonline.com` and sends via Microsoft Graph
|
||||
(`graph.microsoft.com`), with SMTP/XOAUTH2 fallback — proprietary services, but
|
||||
the *user's own mailbox provider*, exactly like connecting to any other mail host.
|
||||
- The build bundles a default Azure **public client id** (`OUTLOOK_OAUTH_CLIENT_ID`
|
||||
in `app/build.gradle.kts`). A public-client id is an identifier, not a secret or a
|
||||
key, and is overridable via `secrets.properties`. This mirrors what established
|
||||
F-Droid mail clients (K-9 Mail / Thunderbird) ship for Gmail/Outlook OAuth without
|
||||
a `NonFreeNet` flag.
|
||||
- Gmail/Yahoo/iCloud accounts use plain app-password IMAP/SMTP — no proprietary
|
||||
SDK. Onboarding links to each vendor's app-password page open in the system
|
||||
browser only on an explicit tap.
|
||||
|
||||
Should F-Droid reviewers read the built-in Outlook convenience differently,
|
||||
declaring `NonFreeNet` on the fdroiddata side is the documented fallback; nothing
|
||||
in the app needs to change.
|
||||
|
||||
### 2.3 Android Backup (Google transport) is opt-in
|
||||
|
||||
`android:allowBackup="true"` is required at the manifest level, but
|
||||
`LibreMailBackupAgent` enforces the runtime preference: **backup is off by
|
||||
default**, and with it disabled the agent ships nothing. When the user opts in, the
|
||||
allowlist in `res/xml/data_extraction_rules.xml` / `backup_rules.xml` backs up
|
||||
*only* the settings DataStore — never credentials, the mail cache, or the
|
||||
Keystore-sealed cache passphrase. Because Android Auto Backup can route through
|
||||
Google's transport, the feature stays disabled unless explicitly chosen; F-Droid
|
||||
has no anti-feature for opt-in platform backup.
|
||||
|
||||
### 2.4 Complete network surface
|
||||
|
||||
| Destination | When | Consent |
|
||||
|---|---|---|
|
||||
| User-configured IMAP/SMTP servers | Mail sync/send | Inherent (user adds the account) |
|
||||
| `login.microsoftonline.com` | Outlook sign-in / token refresh | Only if an Outlook account is added |
|
||||
| `graph.microsoft.com` (`sendMail`) | Outlook send | Only if an Outlook account is added |
|
||||
| Vendor app-password help pages (Google/Yahoo/Apple) | Opened in the system browser | Explicit tap during setup |
|
||||
| Remote images in HTML mail | Blocked by default | Per-user opt-in (tracking-pixel protection) |
|
||||
| `DEBUG_REPORT_ENDPOINT` | Debug-report submission | Empty by default → impossible; otherwise explicit Submit tap |
|
||||
|
||||
No other endpoint exists in the code; there is no update checker, no push relay
|
||||
(new-mail notifications are generated on-device; instant push is a direct IMAP IDLE
|
||||
connection to the user's server), and no font/asset CDN.
|
||||
|
||||
## 3. Clean-room build verification
|
||||
|
||||
Verified 2026-07-01 on this branch, in a checkout containing **no
|
||||
`secrets.properties`** (and no other proprietary keys — the file is optional by
|
||||
design; `OUTLOOK_OAUTH_CLIENT_ID` has an in-tree default and
|
||||
`DEBUG_REPORT_ENDPOINT` defaults to empty):
|
||||
|
||||
```
|
||||
$ JAVA_HOME=<JDK 21> ./gradlew :app:assembleRelease
|
||||
BUILD SUCCESSFUL
|
||||
app/build/outputs/apk/release/app-release.apk (~12.6 MiB)
|
||||
```
|
||||
|
||||
The APK is installable: with no release keystore configured, release builds are
|
||||
signed with the debug key (see `signingConfigs` in `app/build.gradle.kts`) — fine
|
||||
for local testing and irrelevant to F-Droid, which builds from source and signs
|
||||
with its own key. APK contents were inspected: single `classes.dex`, resources,
|
||||
and the three native libraries listed in §1.1 — no bundled binaries of unknown
|
||||
origin. `./gradlew :app:assembleDebug`, the unit tests, static analysis
|
||||
(ktlint/detekt), and the emulator E2E suites run on every PR in CI, likewise
|
||||
without any secrets configured.
|
||||
|
||||
## 4. Publishing checklist (for the maintainer)
|
||||
|
||||
1. Tag releases `v<versionName>` **and bump `versionCode`** in
|
||||
`app/build.gradle.kts` in the same commit. (At audit time tags `v0.1.0` and
|
||||
`v0.2.0` both point at versionCode 1 / versionName 0.1.0 — F-Droid's
|
||||
`UpdateCheckMode: Tags` needs the code to increase per release tag.)
|
||||
2. Copy `docs/fdroid/org.libremail.app.yml` into a fork of fdroiddata as
|
||||
`metadata/org.libremail.app.yml`, run `fdroid lint org.libremail.app` and a test
|
||||
`fdroid build`, then open the merge request.
|
||||
3. The store listing (title/short/full description, per-release changelogs) is read
|
||||
from `fastlane/metadata/android/en-US/` in this repo — add a
|
||||
`changelogs/<versionCode>.txt` for each release, and optionally
|
||||
`images/phoneScreenshots/`.
|
||||
4. Keep this document current when dependencies or network behaviors change; if a
|
||||
future feature genuinely trips an anti-feature, declare it in the fdroiddata
|
||||
metadata rather than hiding it.
|
||||
@@ -0,0 +1,49 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# TEMPLATE for LibreMail's F-Droid build recipe ("app metadata").
|
||||
#
|
||||
# The real recipe does NOT live in this repository: F-Droid builds every app from a
|
||||
# metadata file kept in the fdroiddata repo (https://gitlab.com/fdroid/fdroiddata) at
|
||||
# metadata/org.libremail.app.yml. To submit LibreMail, fork fdroiddata, copy this file
|
||||
# there (dropping this comment block — `fdroid rewritemeta` strips comments anyway),
|
||||
# validate it, and open a merge request:
|
||||
#
|
||||
# fdroid readmeta # parse check
|
||||
# fdroid lint org.libremail.app # style/policy check
|
||||
# fdroid build -v -l org.libremail.app # test build (needs the Android SDK)
|
||||
#
|
||||
# Notes for the submitter:
|
||||
# - Summary/Description/changelogs are deliberately NOT set here: F-Droid pulls the
|
||||
# localized store listing from this repo's fastlane/metadata/android/ tree.
|
||||
# - Each release must be git-tagged (v<versionName>) AND bump versionCode in
|
||||
# app/build.gradle.kts; UpdateCheckMode: Tags matches tags against versionCode.
|
||||
# - The build needs no secrets.properties: the Outlook OAuth client id (a public,
|
||||
# non-secret GUID) has an in-tree default, and the debug-report endpoint defaults
|
||||
# to empty (reports then cannot be submitted anywhere). See docs/fdroid-compliance.md.
|
||||
# - JDK: 17-21 (AGP 9.x does not support JDK 25).
|
||||
|
||||
Categories:
|
||||
- Internet
|
||||
License: GPL-3.0-or-later
|
||||
AuthorName: Jason Ross
|
||||
SourceCode: https://github.com/JMR-dev/LibreMail
|
||||
IssueTracker: https://github.com/JMR-dev/LibreMail/issues
|
||||
Changelog: https://github.com/JMR-dev/LibreMail/releases
|
||||
|
||||
AutoName: LibreMail
|
||||
|
||||
RepoType: git
|
||||
Repo: https://github.com/JMR-dev/LibreMail.git
|
||||
|
||||
Builds:
|
||||
- versionName: 0.1.0
|
||||
versionCode: 1
|
||||
commit: v0.1.0
|
||||
subdir: app
|
||||
gradle:
|
||||
- yes
|
||||
|
||||
AutoUpdateMode: Version
|
||||
UpdateCheckMode: Tags ^v[0-9]+\.[0-9]+\.[0-9]+$
|
||||
CurrentVersion: 0.1.0
|
||||
CurrentVersionCode: 1
|
||||
@@ -0,0 +1,8 @@
|
||||
First release of LibreMail.
|
||||
|
||||
* Outlook/Microsoft (OAuth 2.0), Gmail/Yahoo/iCloud (app password), and generic IMAP/SMTP accounts
|
||||
* Offline-first mail cache with full-history backfill and background sync
|
||||
* Rich-text compose, drafts, signatures, attachments, and a reliable outbox
|
||||
* Unified inbox across multiple accounts, local + server search
|
||||
* New-mail notifications and optional IMAP IDLE instant push
|
||||
* Hardened HTML reader: JavaScript off, remote images blocked by default
|
||||
@@ -0,0 +1,23 @@
|
||||
LibreMail is a free and open-source email client for Android, built with Kotlin, Jetpack Compose and Material 3 (Material You). It aims for a friendly default experience with power-user features tucked under an Advanced Settings group.
|
||||
|
||||
Features:
|
||||
|
||||
* 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, then straight to your inbox
|
||||
* Offline-first: a local mail cache with full-history backfill (resumable) and an optional device-only retention limit
|
||||
* Multiple accounts with a unified inbox and per-account filtering
|
||||
* Rich-text compose with a formatting toolbar, per-account signatures, drafts, and a reliable background outbox
|
||||
* Message bodies rendered in a hardened WebView: JavaScript off, remote images blocked by default (tracking-pixel protection)
|
||||
* On-device new-mail notifications (no push service), with optional instant push via IMAP IDLE
|
||||
* Search across cached mail and the server (IMAP SEARCH)
|
||||
* Attachments: download on demand, open in a system viewer, and attach files when composing
|
||||
* mailto: link handling and share-to-email
|
||||
* Material You dynamic theming, light/dark, edge-to-edge
|
||||
|
||||
Privacy and security:
|
||||
|
||||
* No ads, no analytics SDK, no tracking. By default network traffic goes only to your mail providers.
|
||||
* Credentials are encrypted with the Android Keystore; OAuth uses Authorization Code + PKCE.
|
||||
* Optional (opt-in) extras: SQLCipher cache encryption, a biometric/device-credential app lock, Android Backup of settings only, and local-only debug reports that are sent nowhere unless you explicitly submit one.
|
||||
|
||||
LibreMail is licensed under the GNU General Public License v3.0 or later.
|
||||
@@ -0,0 +1 @@
|
||||
Privacy-respecting, offline-first email for any IMAP/SMTP provider
|
||||
@@ -0,0 +1 @@
|
||||
LibreMail
|
||||
Reference in New Issue
Block a user