notifly
Every ping, checked.
A personal finance tracker that turns your bank and e-wallet notifications into a ledger, and never counts a cent until you confirm it.
Features · How it works · Privacy · Installation · Building · Contributing
Warning
notifly is pre-release software under active development. Cloud sync is not configured yet, and the app has not been published to the Play Store. Keep your own records while you try it out.
Why notifly?
You already get a notification for every GCash send, Maya top-up and bank transfer. notifly reads those pings on your phone and turns each one into a draft transaction. You glance, confirm, and your balance is up to date. You don't have to type anything.
- No typing. The ledger builds itself from notifications you already get.
- You confirm everything. Parsed transactions start as Needs review. They never change your headline balance until you confirm them.
- Private by design. Notification text is parsed on-device and never leaves the phone.
- Works offline. No account, no sign-up, no server needed.
Features
| Automatic capture | Reads notifications only from apps you allow-list. Built-in support for GCash, Maya and BPI, plus a bundled on-device classifier for other apps. |
| Review queue | Drafts wait in Needs review, shown as a separate pending line that never mixes with the confirmed balance. |
| Full ledger | Add, edit, search and filter transactions. Bulk actions with long-press selection and swipe actions. |
| Accounts & transfers | Per-account balances, transfer detection that excludes transfers from spending, and transfer fees. |
| Budgets & bills | Inline budgets per category and bill reminders parsed from notifications. |
| Insights | Spending breakdowns and trends over time. |
| Notification log | See every captured notification, how it was parsed and why it was ignored. Clear it whenever you want. |
| CSV import / export | Move your ledger in and out as plain CSV. Imported rows also go to review first. See docs/TRANSACTION_CSV.md. |
| App lock | Protect the app with a PIN or biometrics. |
| Theming | Four palettes in light and dark, plus Material You themed launcher icons. |
| Adaptive layouts | Phone and tablet layouts. |
| Accessible | State is never shown by colour alone, swipe actions have accessibility alternatives, and the app follows system font scale and reduced motion settings. |
How it works
flowchart LR
A[Bank / e-wallet<br>notification] --> B{Allow-listed<br>app?}
B -- no --> X[Ignored<br>package + reason only]
B -- yes --> C[On-device parser<br>+ classifier]
C -- no amount --> U[Unrecognized<br>no transaction]
C -- amount found --> D[Draft<br>NEEDS_REVIEW]
D -- you confirm --> E[Confirmed<br>moves balance]
- Grant notification access. Android treats this as special access, so you turn it on in system settings instead of answering a permission dialog. notifly takes you there and shows whether the listener is actually connected.
- Pick your apps. notifly only reads notification text from apps you allow-list.
- Review and confirm. Each captured payment shows up as a draft. Confirm it, edit it, or delete it.
Apps often update one notification in place, so capture dedupes on notification key and content hash. If notifly can't find an amount, it doesn't create a transaction. It never guesses one.
Privacy
Privacy is part of the architecture. You can't switch it off in settings.
- Notification text is parsed on-device and is never sent over the network.
- Notification content is never written to logs.
- Text from apps that aren't allow-listed is never read.
- Keeping raw notification text is off by default. If you turn it on, the text stays on the device for troubleshooting only.
- Crash reporting (Sentry) is opt-in, off by default, and strips exception messages, screenshots and transaction data.
- Android backup and device transfer exclude the ledger and notification data.
The full draft policy is in docs/PRIVACY.md.
Installation
notifly is not on the Play Store yet.
- Releases: signed builds are attached to GitHub Releases.
- Nightly / PR builds: every pull request uploads a debug APK as a CI artifact.
Requirements: Android 8.0 (API 26) or newer.
Note
iOS isn't supported, and that won't change with more work: iOS has no API for reading other apps' notifications. The iOS targets exist only to keep the shared code platform-neutral.
Building from source
Prerequisites
- JDK 17
- Android SDK (compile SDK 37)
- Android Studio (latest stable or newer), or just the command line
git clone https://github.com/kryoware/notifly.git
cd notifly
# Run the test suite (includes the parser regression suite)
./gradlew :shared:allTests
# Build and install a debug APK on a connected device
./gradlew :app:installDebug
Useful tasks:
| Task | What it does |
|---|---|
./gradlew lint |
Android lint |
./gradlew test |
All unit tests |
./gradlew :app:assembleDebug |
Debug APK at app/build/outputs/apk/debug/ |
./gradlew :app:bundleRelease |
Release AAB (needs signing variables, see below) |
Release signing
Release builds read each value from a Gradle property (for example in ~/.gradle/gradle.properties) or, failing that, an environment variable:
| Gradle property | Environment variable | Purpose |
|---|---|---|
notiflyKeystoreFile |
KEYSTORE_FILE |
Path to the keystore |
notiflyKeystorePassword |
KEYSTORE_PASSWORD |
Keystore password |
notiflyKeyAlias |
KEY_ALIAS |
Signing key alias |
notiflyKeyPassword |
KEY_PASSWORD |
Signing key password |
sentryDsn |
SENTRY_DSN |
Optional crash reporting |
SENTRY_AUTH_TOKEN, SENTRY_ORG, SENTRY_PROJECT |
Optional mapping upload |
If the signing variables aren't set, the local release build is unsigned. Pushing a v*.*.* tag runs release.yml, which builds a signed AAB and creates a draft GitHub Release.
Tagged builds use the tag as their displayed version. Other source builds read
VERSION. When you publish the draft, release-metadata.yml commits the newest
published version and CHANGELOG.md to master, so pulling
master also updates the version shown in Settings. Release notes opens the
GitHub release for that version. With protected master, configure the
RELEASE_METADATA_TOKEN Actions secret with a token that has Contents write
permission and can bypass the required PR review (for example, a repository
admin token when admin enforcement is disabled). Otherwise the workflow uses
GITHUB_TOKEN, which requires branch rules to allow the Actions bot to push.
Commits made with GITHUB_TOKEN do not trigger another CI run.
Tech stack
| Layer | Technology |
|---|---|
| Language | Kotlin Multiplatform |
| UI | Compose Multiplatform, Material 3 (adaptive navigation) |
| Storage | Room, DataStore |
| DI | Koin |
| Async | Coroutines, kotlinx-datetime |
| Charts | Koala Plot |
| Crash reporting | Sentry (opt-in) |
| Testing | kotlin.test, Robolectric |
Project layout
app/ Android entry point + NotificationCaptureService
shared/ commonMain domain, data, ui (Compose), di
androidMain notification source, classifier, DB builder
iosMain unavailable source, DB builder
commonTest parser regression suite
docs/ design, privacy, brand and implementation notes
tools/ palette and icon generators
The code is layered Data → Domain → Presentation. The domain layer is pure Kotlin with no platform imports, and all capture goes through the TransactionSource interface.
Documentation
| Doc | Contents |
|---|---|
DESIGN.md |
Design system: colour, type, components |
PLAN.md |
Phased build plan |
docs/IMPLEMENTATION_STATUS.md |
What's done and what still needs verification |
docs/CLASSIFIER_MODEL.md |
The bundled on-device notification classifier |
docs/PRIVACY.md |
Privacy policy (draft) |
docs/TRANSACTION_CSV.md |
CSV import/export format |
docs/MD_ICONS.md · docs/MD3_LIST.md |
Icon and list UI guides |
Roadmap
- [ ] Opt-in cloud sync of confirmed transactions only
- [ ] Play Store release
- [ ] More bank and e-wallet parsers, tuned on real samples
- [ ] iOS story: bank aggregator, file import, or manual only (undecided)
Contributing
Contributions are welcome, especially parser samples for banks and e-wallets that aren't supported yet.
Before you open a PR:
- Run
./gradlew lint testand make sure it passes. - Follow the project rules in
CLAUDE.md. The ones that matter most: - Money is
Longminor units (centavos). NeverDoubleorFloat. - Never log notification content, not even at debug level.
- Parsed transactions always start as
NEEDS_REVIEW. - All colour comes from
MaterialTheme. No hardcoded hex. - Never paste real notification text into issues or PRs. Redact names, account numbers and reference numbers first.
License
notifly is licensed under the GNU Affero General Public License v3.0.