Android app BearerMail addition
A native Android client for your BearerMail server — forked from karatektus's plMail app, renamed, and extended with Drive, aliases, and a handful of quality-of-life features.
For upstream plMail, use karatektus's app. Report problems with the BearerMail additions to the BearerMail Android repo, not upstream.
Install
Get the APK from GitHub Releases. Two flavours:
| Flavour | Install ID | Pick it when… |
|---|---|---|
foss | com.bearermail.mail | You want no Google dependencies (default). From 0.0.33 pairing needs nothing from Google either. |
google | com.bearermail.mail.google | You need Google services (e.g. certain push paths). |
It installs next to a stock plMail install, not over it. The app shows as BearerMail (and "BearerMail Calendar" for the optional calendar icon).
On first install, Android asks you to allow BearerMail to install apps — that's for the built-in updater (below).
Up to 0.0.31, tapping "Scan the code" could close the app on some phones. The cause was inside Google's ML Kit barcode library (it failed during its own setup), so from 0.0.33 the scanner decodes with ZXing instead: pure Java, no Google Play services, no native library. 0.0.32 shows "The camera could not be started" with the reason instead of crashing. On any version, Enter the address instead pairs with the server address and an app password.
ZXing is a little slower to lock on than Google's scanner. Let the QR code fill most of the frame and hold steady for a second; when scanning off a monitor, lower its brightness a bit to cut glare.
Pairing
On the server: Settings → App passwords → create one, then either scan the QR code in the app or tap the plmail://pair?host=…&code=… link. The app talks JMAP to your server.
If your server sits behind an auth proxy (e.g. Authentik forward-auth), the app's host must bypass it — the app needs a direct, unauthenticated-proxy path to the JMAP endpoint, or pairing and sync fail.
What the fork adds
Drive on the phone
- Drawer → Drive (shown only when the server offers it): browse folders, upload from the phone, open, send or save files, rename, move, delete, and manage share links — with optional password and 1/7/30-day or never expiry. New links are copied to the clipboard.
- Share to "BearerMail Drive" — a second entry in every app's share sheet. Sharing there uploads files straight into Drive instead of opening the composer.
- Drive links in mail and events — the composer's link button and Add from Drive under an event's description pick a Drive file (or upload one) and insert a download link.
- Too big to attach → Drive link. Picking a file over 25 MB in the composer uploads it to Drive and inserts a download link with a short note, instead of failing at Send. (Phone uploads up to 300 MB.)
- Shared links open a page — recipients see the file with a Download button and a "Report this link" button, not a raw download.
On-the-spot aliases
Drawer → New alias, or the composer's From menu → New alias… / Random alias (one tap). Type a name or generate a random one (shop.k3v9x2ma@yourdomain.com). It's copied to the clipboard, receives mail within seconds, and works as a From address immediately. Delete it on the web under Settings → Aliases.
Plans in emails → calendar
When a message says something like "meet me at 218 Main St at 10am" or "lunch Friday at 12:30", the reader shows a Looks like a plan card. Add to calendar opens the event editor pre-filled (title, time, place, note) — nothing is saved until you press Save. Detection runs on the phone, on text already downloaded; nothing is sent anywhere.
Insert event in compose
Composer menu (⋯) → Insert event picks one of your upcoming events, writes its details into the message and attaches it as an .ics — the recipient adds it to their calendar in one tap.
Copy verification codes
When a mail carries a one-time code ("Your verification code is 482 913"), the reader shows a Verification code card with a Copy button, and the new-mail notification gets a Copy button too. The code goes on the clipboard marked sensitive (hidden in clipboard preview). Detection needs a code word next to the number, so order numbers, prices and dates are left alone — and it all runs on-device.
App lock (PIN)
Drawer → App lock → Set a PIN (4, 6 or 12 digits, entered twice). BearerMail then asks for it on every start and when you return to it (immediately, after 1 minute, or after 5 minutes — your choice). The first four wrong PINs in a row cost nothing; from the fifth, each try waits 30 seconds, doubling up to 15 minutes. The PIN is stored only as a salted PBKDF2 hash; on Android 13+ the Recents screen shows a blank card while locked. Forgot it? Clear BearerMail's storage in Android Settings → Apps and pair again.
Crash reports
If the app ever closes unexpectedly, the next launch shows BearerMail closed unexpectedly with the error, the app version and the device, plus Copy and Dismiss. Paste it into a GitHub issue. Nothing is sent anywhere; the report stays on the phone until you copy it.
Built-in updater
Release builds check this repo's GitHub Releases on launch (at most hourly); Drawer → Check for updates checks immediately. When a newer version is out you get its notes with Update, Later and Skip this version. Update downloads the APK for your flavour and opens Android's install screen. Every release is cut automatically from main by CI — and every APK must be signed with the same key, or Android refuses the update.
Your login survives updates. Installing a newer version over the old one (which is what the updater does) keeps your account, mail, settings and PIN. You are signed out only when the app's data is wiped: uninstalling, Clear storage, or replacing it with a differently signed build. The saved login is encrypted with a key Android deletes with the data, so it cannot be carried to a fresh install; pair again (an existing app password can be reused).
The app checks on start at most once an hour. If an update doesn't show up, use Drawer → Check for updates, or fully close the app (swipe it away) and reopen it.
Releases (for maintainers)
- Merge a pull request into
main. Auto release bumps the patch version (e.g. 0.0.32 → 0.0.33), commitschore(release): x.y.zand tags it. - Release then runs the tests, builds both flavours, signs them and publishes a GitHub Release with
plmail-x.y.z-foss.apk,plmail-x.y.z-google.apk, the.aabbundles and the R8mapping.txtfiles. About 15 minutes in all. - Phones offer the update on their next check.
One-time setup, under the repo's Settings → Secrets and variables → Actions:
| Secret | Value |
|---|---|
KEYSTORE_BASE64 | The .jks file as base64 (PowerShell: [Convert]::ToBase64String([IO.File]::ReadAllBytes("…\bearermail-release.jks")) | Set-Clipboard) |
KEYSTORE_PASSWORD | The keystore password |
KEY_ALIAS | Only the alias word, e.g. bearermail (see keytool -list -keystore …) |
KEY_PASSWORD | The key password (same as the keystore password for PKCS12 keystores) |
Also set Settings → Actions → General → Workflow permissions → Read and write, so the release job can push its version bump.
Back up the .jks file and its password. Lose them and installed apps can never be updated again; everyone would have to uninstall and reinstall.
Reading a crash from a release build: release builds rename classes (R8), so stack traces show names like v77.b. Download the matching plmail-x.y.z-*-mapping.txt from that release and search it for -> v77: to find the real class.
Screenshot tests: when a change alters a screen on purpose, put [record-screenshots] in the commit message on a pull-request branch; CI re-records the reference images, commits them and re-runs CI.
Cost: the repo is public, so GitHub Actions minutes are free (the billing page shows a "gross" amount, but Billed: $0). A merged change uses roughly 25–30 runner minutes.
Building it yourself
./gradlew :app:assembleFossDebug
# APK lands in app/build/outputs/apk/foss/debug/
Needs JDK 21 and the Android SDK (platform 37, build-tools 36). Full build docs are in the repo's BEARERMAIL.md.