Expense Buddy is a privacy-first expense tracker built with React Native and Expo, designed around one core idea: turn transaction SMS messages into draft expenses automatically, let you review them on-device, and keep the confirmed data synced to a GitHub repository you control.
Manual entry, analytics, multi-currency support, and GitHub sync are all there, but the product's differentiator is the import pipeline: scan recent Android SMS messages, extract likely expenses, stage them locally, and import only what you approve.
- Auto-detect likely expenses from Android transaction SMS messages
- Surface new matched transactions with local Android notifications that reopen the review flow
- Keep raw SMS content on-device and out of GitHub sync
- Review, edit, reject, or dismiss imported items before they become expenses
- Sync confirmed expenses across devices using your own private GitHub repository
- Track expenses manually too, with custom categories, payment methods, and saved instruments
- Explore spending with charts, filters, and multi-currency analytics
Expense Buddy ships a hybrid SMS import pipeline for Android. Transaction detection and field extraction stay deterministic and explainable, while category suggestion can be upgraded by a bundled on-device LiteRT model. The flow remains intentionally review-first: the app finds likely transactions, stages them locally, and waits for your approval before anything is added to your expense history.
Today, the import system:
- scans a recent SMS window on Android
- can detect newly received transaction SMS messages in the background on Android
- uses deterministic rules for transaction extraction
- runs a bundled native LiteRT classifier for default-category suggestions
- falls back to the regex category suggestion when model confidence is low
- keeps raw SMS data local to the device
Planned direction:
- keep the import flow fully on-device
- broaden parser coverage beyond the initial regex packs
- replace the seed LiteRT model with a model trained on more independent labels
- keep ML limited to suggestion-quality improvements while review-first import remains the guardrail
The current ML model is a first integration milestone, not a final quality claim. It is trained on a small heuristic-shaped seed set and is intentionally gated behind conservative fallback behavior.
For the model-workspace architecture diagrams and the current Android-ready model contract, see tools/sms-ml/docs/model-architectures.md.
- Android-only SMS import using the native
expense-buddy-sms-parserparsing engine (pure Kotlin, no JS bridge) - Android-only background transaction alerts using the native
expense-buddy-sms-modulemodule - Zero-hop native sync: a single
syncInboxAsyncbridge call reads, parses, classifies, deduplicates, and queues items in native Room — JS only receives a count - Inline
READ_SMSpermission handling through the native module - Optional Settings toggle for background SMS alerts with
RECEIVE_SMSandPOST_NOTIFICATIONSgating - Review queue where each staged item can be accepted, edited, rejected, or dismissed — surfaced through a React Context provider subscribed to native events
- Local notifications route back into the review queue instead of silently creating expenses
- Native LiteRT category suggestion with regex fallback for low-confidence predictions
- Conservative category suggestion resolver built around shipped default categories
- Local-only processing with no backend parsing
- Quick add flow with amount, category, date, notes, and payment method
- Optional math expression entry like
120+30 - Custom categories with icons, colors, editing, and reordering
- Saved payment instruments for cards and UPI handles
- Full create, edit, delete, and soft-delete sync behavior
- Day-level detail view and searchable history
- Private repository sync using a fetch-merge-push workflow
- Daily CSV files in
expenses-YYYY-MM-DD.csvformat - Optional settings sync for non-sensitive app settings
- Dirty-day tracking so only changed dates are re-uploaded
- Differential fetch and upload using remote blob SHA caching
- Timestamp-based auto-resolution with true conflict detection when needed
- Manual sync controls plus optional auto-sync on launch or on change
- Daily trend charts and category or payment-method breakdowns
- Multi-currency grouping and filtering
- Advanced filters for date range, amount, search text, categories, methods, and saved instruments
- Shared filter state between History and Analytics
- Works on Android, iOS, and Web for core expense tracking
- SMS import is Android-only and requires a native build
- Play-installed Android builds support native in-app update checks and standard full-screen Play update flows
- In-app review prompts are Play-only, lightly rate-limited locally, and still subject to Google Play eligibility decisions
- Dynamic locale loading for English (US, UK, IN), Hindi, and Japanese
- Dark mode, changelog gating, update notifications, and reusable Tamagui UI primitives
- Open Settings and scan recent messages, or enable background SMS alerts on Android.
- Expense Buddy reads recent Android transaction SMS messages on-device or parses newly received SMS messages locally in the background.
- A single
syncInboxAsyncnative bridge call reads, parses, classifies, deduplicates, and inserts items into a native Room queue — no JS orchestration. - The parser extracts likely transaction details, generates a deterministic SHA-256 fingerprint for dedup, and produces a regex fallback category suggestion.
- The native module scores the same messages with a bundled LiteRT category model, replacing the regex suggestion only when confidence is high enough.
- If a new background match arrives while the app is not foregrounded, a local notification opens the review flow.
- The review UI (backed by a React Context provider subscribing to native
onReviewQueueUpdatedevents) shows pending candidates. - You review each candidate and decide whether to accept, edit, reject, or dismiss it.
- Only accepted items become normal expense records and participate in optional GitHub sync.
This review-first model is deliberate. The app aims to reduce manual entry without hiding how an import decision was made.
- Node.js 24.x or higher
- Yarn 4.5.0
- Expo CLI
- For iOS: Xcode and CocoaPods
- For Android: Android Studio and SDK
- For SMS import development: an Android development build or release build, because Expo Go cannot load the custom native SMS module
git clone https://github.com/sudokoi/expense-buddy.git
cd expense-buddy
yarn install
yarn start- iOS:
yarn ios - Android:
yarn android - Web:
yarn web
- Go to Settings > GitHub Sync.
- Tap Sign in with GitHub.
- Approve the device-flow code in the browser.
- Pick a personal repository you own and can write to.
- Save the branch and test the connection.
- Create a GitHub Personal Access Token.
- Grant
Contents: Read and writefor a fine-grained token, or use the classicreposcope for a private repository. - In the app, enter the token, repository in
owner/repoformat, and branch. - Save the config and test the connection.
- Sync on app launch
- Sync after every add, edit, or delete
- Manual sync with upload and download controls
Expense Buddy uses Expo Router for navigation, Tamagui for UI, XState stores for app state, and a service layer for sync, storage, analytics, and SMS import logic.
Key implementation details:
- file-based routing under
app/ - XState stores for expenses, settings, filters, notifications, and UI state
- React Context provider for SMS review queue (native-owned Room persistence, not an XState store)
- service-layer sync engine for GitHub fetch, merge, conflict handling, and uploads
- single native Android SMS module (
expense-buddy-sms-module) handling permissions, inbox scanning, ML classification, and queue persistence —expense-buddy-sms-parseris a pure Kotlin library with no JS bridge - property-based and unit tests across storage, sync, parsing, and store behavior
- tracked Expo native modules for background SMS alerts and Play Core integrations
For the deeper architecture write-up, see ARCHITECTURE.md.
expense-buddy/
├── app/
│ ├── (tabs)/
│ │ ├── index.tsx # analytics (default tab, charts and breakdowns)
│ │ ├── add.tsx # manual expense entry
│ │ ├── history.tsx # history and filters
│ │ ├── settings.tsx # sync, import, and app settings
│ │ └── _layout.tsx
│ ├── +html.tsx
│ ├── +not-found.tsx
│ ├── github/repo-picker.tsx # GitHub repository selection flow
│ ├── modal.tsx
│ └── _layout.tsx
├── components/
│ ├── analytics/ # charts, filters, and analytics UI
│ ├── history/ # history list and related UI
│ ├── ui/ # shared styled components
│ ├── NotificationStack.tsx
│ ├── Provider.tsx
│ └── SyncIndicator.tsx
├── hooks/ # analytics, auth, sync, changelog, update, and SMS import action hooks
├── services/
│ ├── sms-import/
│ │ └── suggestion-resolver.ts
│ ├── background-sms/
│ │ ├── android-background-sms-module.ts
│ │ └── background-sms-permissions.ts
│ ├── github-sync.ts # GitHub API client
│ ├── sync-machine.ts # sync state machine
│ ├── sync-manager.ts # sync orchestration
│ ├── merge-engine.ts # conflict resolution and merge logic
│ ├── expense-storage.ts # local expense persistence
│ ├── settings-manager.ts # settings persistence and sync support
│ ├── secure-storage.ts # encrypted secure storage
│ ├── storage.ts # MMKV adapter with AsyncStorage fallback
│ ├── update-checker.ts
│ └── auto-sync-service.ts
├── providers/ # React Context providers
│ └── sms-import-review-provider.tsx
├── stores/
│ ├── expense-store.ts
│ ├── settings-store.ts
│ ├── filter-store.ts
│ ├── notification-store.ts
│ ├── ui-state-store.ts
│ └── store-provider.tsx
├── modules/
│ ├── expense-buddy-sms-module/ # Single SMS bridge module (permissions, scan, ML, queue)
│ ├── expense-buddy-sms-parser/ # Pure Kotlin parsing library (no JS bridge)
│ ├── expense-buddy-play-core/ # Play Store update and review native module
│ └── expense-buddy-logger/ # On-device structured logging
├── locales/ # en-US, en-GB, en-IN, hi, ja
├── decisions/ # ADRs and technical decisions
├── assets/
├── constants/
├── types/
├── utils/
└── scripts/
yarn test
yarn test:watchThe test suite includes unit tests and property-based tests for sync, storage, parsing, and state-management behavior.
The app does not require user-provided environment variables.
The GitHub OAuth Client ID is configured in app.config.js and can be overridden for local Expo development with EXPO_PUBLIC_GITHUB_OAUTH_CLIENT_ID.
Build profiles are defined in eas.json:
developmentfor dev client buildspreviewfor internal testingproductionfor store-ready buildsinternalfor direct APK distribution
yarn start# iOS
eas build --platform ios --profile production
# Android
eas build --platform android --profile production
# APK for testing
eas build --platform android --profile internalRelease automation is documented in .github/RELEASE.md.
Expense Buddy does not collect user data. Expense data lives on-device by default, and optional sync writes only to the GitHub repository you configure. Raw SMS import data is processed locally and is not uploaded as part of sync.
See PRIVACY.md for the full privacy policy.
- In the app: Settings > App Information > Report an Issue
- On GitHub: Issue templates
Read CONTRIBUTING.md before opening a pull request. Please follow our Code of Conduct. For security vulnerabilities, see our Security Policy.
This project uses a strict issue-first contribution process. If you want to propose a fix, feature, refactor, or documentation change, open an issue first and discuss the problem and proposed solution before sending code.
Expense Buddy is available under the AGPL-3.0 License.

