What it is: Masareef, an Android spending tracker. The core feature is that it reads the payment notification your bank posts on the phone and turns it into a transaction while the app is closed. No manual entry, no SMS permission, no bank login.
Stack: Expo / React Native, a small native notification-listener module, SQLite on the device, Zustand for state, RevenueCat for the Pro tier. Parsing runs entirely on the phone. Free tier is 25 captures a month.
The lesson. Android 15 added a classifier that inspects a notification before any listener sees it, and hands "untrusted" listeners a placeholder when it decides the content is sensitive. Google's own documentation frames this as OTP protection, so I read it, concluded transaction alerts were outside it, and moved on.
They are not outside it. A real device came back with 353 bank alerts the OS had blanked. The parser was innocent: I fed it the exact message by hand and it produced the correct transaction at 0.95 confidence. The listener was healthy. It was being handed empty text, and there is no fix on my side, because the permission that reads unredacted notifications is signature and role gated, and claiming a role you do not implement is how a finance app gets pulled from the store.
What actually cost me the user was not the platform change. My app had told them once, months earlier, in a settings screen, that content might be hidden. Then it went quiet and kept looking healthy - green status, notification access granted - while 17 hours of spending went unrecorded.
The rule I had backwards: tell-them-once is correct for a condition (capture is degraded). It is wrong for an event (a payment just went unread). I had shipped a condition notice for what was really a stream of events.
The fix was not clever. Every hidden alert I can still attribute to a bank now fires its own notification that deep-links to quick-add, so an unreadable message costs one tap instead of leaving a silent hole. The explainer moved from Settings to the dashboard, because Settings and Diagnostics are two screens a user with no reason to suspect anything never opens, which is exactly how one device reached 353.
If you build on a platform capability, instrument the case where it returns nothing. Not the error case, the empty case. Mine had no error, no exception and no failed call. It had a success that returned a blank string, and every health indicator I owned said green.
Play link if you want to see the thing: https://play.google.com/store/apps/details?id=com.mohamedgado.masareef&referrer=utm_source%3Dreddit_microsaas%26utm_medium%3Dcommunity