RORK LABJP
BUILD — Rork Max runs real Macs in the cloud loaded with Xcode and the iOS SDK, writing SwiftUI, compiling, reading the errors and building again. That loop, not the code generation, is what lifts the outputNATIVE — What comes out is pure Swift and SwiftUI, not React Native. Reaching AR, Metal graphics and widgets that React Native cannot touch is the real gap between this and other buildersPLATFORMS — Coverage spans iPhone, iPad, Apple Watch, Apple TV and Vision Pro, plus iMessage. Worth a look if you want to start from a watch app or an extension rather than a phone screenCOMPANION — The Rork Companion app lets you check a generated build on a real iPhone without a paid Apple Developer account, lowering the bar for trying a first project end to endPRICING — Free to start, paid plans from $25 a month, and Rork Max on the $200 Max plan. Worth working out up front how many projects it takes to earn that backDEADLINE — From August 31, 2026, Google Play requires target API level 36 or higher for new apps and updates alike. Ten days out, and the targetSdkVersion of what you generate is yours to verifyBUILD — Rork Max runs real Macs in the cloud loaded with Xcode and the iOS SDK, writing SwiftUI, compiling, reading the errors and building again. That loop, not the code generation, is what lifts the outputNATIVE — What comes out is pure Swift and SwiftUI, not React Native. Reaching AR, Metal graphics and widgets that React Native cannot touch is the real gap between this and other buildersPLATFORMS — Coverage spans iPhone, iPad, Apple Watch, Apple TV and Vision Pro, plus iMessage. Worth a look if you want to start from a watch app or an extension rather than a phone screenCOMPANION — The Rork Companion app lets you check a generated build on a real iPhone without a paid Apple Developer account, lowering the bar for trying a first project end to endPRICING — Free to start, paid plans from $25 a month, and Rork Max on the $200 Max plan. Worth working out up front how many projects it takes to earn that backDEADLINE — From August 31, 2026, Google Play requires target API level 36 or higher for new apps and updates alike. Ten days out, and the targetSdkVersion of what you generate is yours to verify
Articles/Dev Tools
Dev Tools/2026-06-22Advanced

Your Daily Reminder Stops Firing After a Couple of Weeks — iOS's Invisible 64-Notification Cap

When a daily reminder built with Rork (Expo) goes silent after a while, the cause is usually iOS's 64 pending-notification limit. Design a repeating calendar trigger for fixed messages and a rolling reschedule for daily-changing content, with working code that survives DST and multiple reminders.

Rork540expo-notifications7Local Notifications2React Native227iOS110

Premium Article

"Deliver one line every morning at 8." When I added that to a daily affirmation app, I naively scheduled thirty days' worth in one shot. It worked perfectly in the simulator. Then, a while after release, users started writing in: "It fired for the first few days, then stopped."

It wasn't a bug in my code. It was a limit the official tutorials barely mention: iOS only holds up to 64 pending local notifications per app. Thirty days is far over that — and if you run a morning-and-evening pair, you can't even bank a full month. Anything past the cap is dropped silently, with no error.

This article walks through rebuilding a daily reminder so it keeps firing, using working expo-notifications code. I'll leave the basic setup to the local notifications setup guide and focus only on living within the cap.

iOS keeps only 64 pending notifications — the rest are dropped silently

Apple keeps at most 64 un-fired (pending) local notifications per app. If you try to schedule a 65th with scheduleNotificationAsync, no exception is thrown. iOS keeps the 64 with the soonest fire times and quietly drops the rest from the pending list.

The subtle part: a repeats: true notification counts as one slot, no matter how many times it will fire. "Every day at 8" built as a repeating trigger costs a single slot forever. But the moment you want "a different body every day," repeats is off the table, and you start banking one notification per date — which hits the 64-slot wall fast.

So the decision is simple:

  • The text can be the same every day → one repeating trigger. The cap is basically a non-issue.
  • You want the text to change daily → individual scheduling, and you must budget the 64 slots.

I lost time early on by not making this call up front. Classifying each reminder as "fixed text" or "daily-changing" before writing anything makes the whole design fall into place.

Measure first — how many are you actually banking?

Before changing the design, measure. getAllScheduledNotificationsAsync returns the current pending array, so a single log line at launch shows how many slots you're using.

import * as Notifications from 'expo-notifications';
 
// Call once at launch. Guard with __DEV__ in production to keep logs quiet.
export async function inspectPendingNotifications(): Promise<number> {
  const pending = await Notifications.getAllScheduledNotificationsAsync();
  if (__DEV__) {
    console.log(`[notif] pending=${pending.length}/64`);
    pending.forEach((n) => {
      // Trigger shape differs by platform, so log it loosely.
      console.log('  -', n.identifier, JSON.stringify(n.trigger));
    });
  }
  return pending.length;
}

If pending.length ever crosses 60, you're already in the danger zone. In my production apps I ship this number to internal metrics on every launch, so I never discover after the fact that I've been pinned at the cap. Banking notifications without watching the count is like topping up fuel without looking at the gauge.

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
You'll understand why a reminder that delivers a different line each morning suddenly goes quiet — iOS's invisible 64 pending-notification cap — and how to design around it
You'll be able to decide, from working code, when a single repeating calendar trigger is enough and when daily-changing content needs a rolling reschedule
You'll be able to rebuild your own app so the fire time never drifts across DST, time-zone moves, or a morning-plus-evening reminder pair
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.

or
Unlock all articles with Membership →
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.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

Related Articles

Dev Tools2026-06-25
Why Your 9 AM Reminder Stops Arriving Abroad — Making Expo Local Notifications Survive Time Zones and DST
Daily reminders built with Rork (Expo) can drift to the wrong local time when users travel or DST flips. Here is the timeInterval trap, and a design that reschedules against local wall-clock time, with working code.
Dev Tools2026-07-27
When to Raise Your Minimum iOS Version — Count Leftover Branches, Not User Percentages
Judging a minimum OS bump by usage share produces the same answer every year, so the decision never happens. Here is the annotation convention, the sweep script that counts how many branches each candidate floor would retire, and what to watch for 30 days after.
Dev Tools2026-05-28
Tracking Down BGTaskScheduler.submit Error Code=1 (Unavailable) in Rork iOS Apps
When BGTaskScheduler.submit returns Error Code=1, the cause space is finite — six of them. Includes the identifier trap specific to Expo-based Rork apps and a getPendingTaskRequests self-check, ordered the way I actually hit them.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →