●RORKMAX — Rork Max builds native Swift apps instead of React Native, targeting iPhone, iPad, Apple Watch, Apple TV, Vision Pro, and iMessage●NATIVE — Rork Max unlocks capabilities React Native can't reach: AR/LiDAR, Metal 3D, widgets, Dynamic Island, Live Activities, HealthKit, HomeKit, NFC, and Core ML●PUBLISH — Two-click App Store publishing cuts the steps between generating an app and shipping it●SIM — A browser-based streaming iOS simulator lets you test in a real Apple environment without Xcode or a Mac●STANDARD — Standard Rork turns a plain-English description into working React Native (Expo) code●PRICING — It's free to start, paid plans begin at $25/month, and Rork Max is $200/month●RORKMAX — Rork Max builds native Swift apps instead of React Native, targeting iPhone, iPad, Apple Watch, Apple TV, Vision Pro, and iMessage●NATIVE — Rork Max unlocks capabilities React Native can't reach: AR/LiDAR, Metal 3D, widgets, Dynamic Island, Live Activities, HealthKit, HomeKit, NFC, and Core ML●PUBLISH — Two-click App Store publishing cuts the steps between generating an app and shipping it●SIM — A browser-based streaming iOS simulator lets you test in a real Apple environment without Xcode or a Mac●STANDARD — Standard Rork turns a plain-English description into working React Native (Expo) code●PRICING — It's free to start, paid plans begin at $25/month, and Rork Max is $200/month
When Bumping Push to Three a Day Cut Next-Week Returns — Field Notes on Measuring Notification Frequency by D7 Retention and Opt-Out Rate
The assumption that heavier push means more returns was quietly eroding my D7 retention. These are field notes on re-measuring notification frequency by install-cohort D7 and opt-out rate, and walking from three a day back to one.
Returns on a study-log app I built with Rork had stalled, so I turned up the push notifications. What had been one a day became three — morning, noon, night. The logic was plain: more reminders, more comebacks.
For the first few days, push-driven opens did climb. Looking at that one number on the dashboard, I was satisfied for a while. Then two weeks later, when I lined up D7 retention (the share still returning seven days after install) week by week, the line was pointing down. The cohort from the week I raised frequency had slipped from 14.2% to 9.8% on D7.
I added notifications because I wanted people back — and those notifications were speeding their exit. These are my notes on unwinding that assumption with numbers. It's a small app I run as an indie developer, but deciding frequency by cohort instead of by feel should hold at any scale.
Push-driven opens only looked like they were rising
My first stumble was watching the wrong metric. "Times someone tapped a notification to open" naturally rises somewhat when you send three times as many. More sent means more taps in absolute terms — that isn't success.
What I actually wanted was whether a user was still coming back a week later, notification or not. I should have recorded push-driven opens and organic, self-initiated opens separately from the start.
// lib/analytics.js — record opens by their trigger// Without this split, you overrate the notifications.export function trackAppOpen({ source, userId }) { // source: 'push' | 'organic' | 'deeplink' analytics.capture('app_open', { userId, source, // ← the lifeline for later analysis ts: Date.now(), });}Notifications.addNotificationResponseReceivedListener(() => { trackAppOpen({ source: 'push', userId: currentUser.id });});if (!launchedFromNotification) { trackAppOpen({ source: 'organic', userId: currentUser.id });}
Once I split by source, the picture sharpened. Push-driven opens rose about 1.9x per week, while organic opens actually dropped 12%. Users I dragged back with notifications were losing the habit of opening on their own. Summed together it wasn't a gain — it was eroding D7.
Cut cohorts by install week and line up the metrics
To claim "more push made it worse," you have to compare before and after on the same ruler. I grouped users by their install week and, per cohort, lined up D1, D7, D30, and opt-out rate.
Install week
Push frequency
D1
D7
D30
Opt-out rate
Week 1 (baseline)
1/day
31.4%
14.2%
6.1%
4.3%
Week 2
3/day
30.9%
11.6%
—
9.7%
Week 3
3/day
31.1%
9.8%
—
13.1%
D1 barely moved. Frequency doesn't touch the first-day experience, which makes sense. What moved was D7 and opt-out rate. The two heavy weeks kept sliding on D7, and the share of users who revoked notification permission jumped from 4.3% to 13.1%. Once a user opts out, they never get a notification again. I was trading immediate returns for the future return channel itself.
The cohort SQL isn't hard. As long as the event log carries source, the rest is aggregation.
-- D7 retention by install week-- installs: user_id, install_date-- opens: user_id, ts, sourceSELECT date_trunc('week', i.install_date) AS cohort_week, count(distinct i.user_id) AS installs, count(distinct case when o.ts::date = i.install_date + interval '7 day' then o.user_id end) AS d7_returned, round(100.0 * count(distinct case when o.ts::date = i.install_date + interval '7 day' then o.user_id end) / count(distinct i.user_id), 1) AS d7_rateFROM installs iLEFT JOIN opens o ON o.user_id = i.user_idGROUP BY 1ORDER BY 1;
✦
Thank you for reading this far.
Continue Reading
What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.
WHAT YOU'LL LEARN
✦How to slice install cohorts by the week you changed frequency and line up D1/D7/D30 against opt-out rate
✦How to break the illusion that push-driven opens are rising while total D7 falls, by separating push opens from organic ones
✦The measured recovery from cutting three-a-day to one, and where to draw the line on which users can take more
Secure payment via Stripe · Cancel anytime
✦
Unlock This Article
Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.
Use opt-out rate as an alarm that rings earlier than D7
The weakness of cohorts is that the answer arrives late. Confirming D7 means waiting seven days from install, so a bad change runs for a week. I placed opt-out rate — which moves faster — as a leading indicator.
The day after I raised frequency, the opt-out rate spiked. It stirred before D7 fell. In other words, users revoked permission the moment they felt pestered, and drifted away quietly afterward.
// Detect and log permission revocation immediately// A leading signal so you don't wait for D7 to see failure.import * as Notifications from 'expo-notifications';export async function checkOptOut(userId) { const { status } = await Notifications.getPermissionsAsync(); const optedOut = status !== 'granted'; if (optedOut) { analytics.capture('push_opt_out', { userId, ts: Date.now() }); } return optedOut;}
In practice I set one alert on the daily opt-out rate. The baseline week ran around 0.6% a day, so my simple threshold was: if it exceeds twice that, revisit the notification plan. In week 2 the alert fired the next day, and a call I'd otherwise wait seven days for, I could make in one: this is bad.
Back from three to one, then split off who can take more
With the numbers in hand, I returned push to one a day. That alone recovered the next cohort's D7 to 13.6%, and opt-out rate settled near 5%. Short of the 14.2% baseline, but the wound stayed shallow.
Still, I didn't want to stop at "cut across the board." Some users genuinely welcome notifications. So I varied frequency between users with many organic opens over the last 14 days and those without.
The crux is that I pushed less to the users who were slipping. It was the opposite of intuition. Bombarding low-response users makes them more likely to revoke permission than to return. That group deserved fewer notifications, handled with care.
Content polish couldn't rescue the frequency itself
Before cutting, I held out once — "maybe better copy makes three a day survivable." I rewrote the generic "come open the app" into progress-linked "you're 3 points from today's goal." Open rate (opens after receiving a notification) did rise, from 8.1% to 12.7%.
But D7 and opt-out rate didn't budge. People opened, yet didn't stick, and kept revoking. Copy improves the reaction to a given notification; it can't cancel the fatigue of three a day. That's when I accepted the thing to tune was frequency, not wording.
The line I kept for operations
This episode shifted one ruler in my head. Push success can't be measured by push-driven open counts. What to measure is whether the cohort that received notifications is still returning a week later, and whether it still keeps permission on. The first is D7, the second is opt-out rate. With those two sheets, frequency decisions move from feel to numbers.
The other thing I kept: whenever a change adds more, write the option to subtract first. Ship the code that increases notifications alongside the branch that can reduce them per segment. Then when the alarm rings, you're not stuck choosing between all-off and all-on — you can ease off tier by tier.
The stronger the wish to reach someone, the heavier you want to make notifications. But since you're spending a finite resource — their permission — the heaviness itself is a cost. Count, split, and only to those who need it. When I kept that order, push finally went back to being an ally of returns. If it trims the detour for someone else wrestling with the same problem, I'll be glad. Thank you for reading.
Share
Thank You for Reading
Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.