Hi all,
I'm the developer of OV Message, an end-to-end encrypted SMS messaging app already shipped on Google Play (Android, where it natively encrypts SMS content). The iOS port aims to be the default carrier-messaging app, handling SMS, MMS, and RCS through TelephonyMessagingKit with the com.apple.developer.carrier-messaging-app entitlement under the EU programme. While testing the cold-launch flow on iOS 26.x, I've hit a reproducible bug that silently drops the first SMS/MMS/RCS that wakes the app, and I'd like to confirm whether other devs working with this API see the same.
The bug
When a default carrier-messaging app is force-killed and a message arrives, iOS correctly:
Routes the message via CommCenter (IMS in my case — SFR France)
Wakes the app in background (state = .background at didFinishLaunchingWithOptions)
Acquires a TelephonyMessaging runningboard assertion on the app
But CommCenter then pushes the pending message via XPC before the client TMK library has finished registering its messageHandlersByID dictionary. Result: client responds Received unhandled request, server logs TMKXPCError Code=2, message is dropped, never delivered to for await in incomingMessageNotifications. Subsequent messages (with the app warm) work fine.
Native log sequence (from idevicesyslog with the Telephony logging profile)
T+0.000 CommCenter: SMS arrives via IMS (k3GPP)
T+0.003 CommCenter: Default app is set to com.example.app
T+0.004 CommCenter: Attempting to launch and acquire process assertion
T+0.083 CommCenter: Notifying SMS message received, target: bundleID=...
T+0.085 CommCenter(TMK): There are no client connections matching, pending message
[~125 ms — app boots]
T+0.128 App(TMK): Configuring connection
T+0.128 App(TMK): Pinging remote end
T+0.130 CommCenter(TMK): Received new connection from PID
T+0.130 CommCenter(TMK): New incoming connection, flushing pending messages (1) ← server flushes
T+0.130 App(TMK): Received unhandled request ← client not ready
T+0.131 CommCenter(TMK): Failed to send pending message: TMKXPCError Code=2
T+0.132 App(TMK): Registered for IncomingMessageNotification (smsReceived) ← ~2 ms too late
The race window between Pinging remote end (client) and Registered for IncomingMessageNotification (client) is 2–7 ms across my measurements. CommCenter considers the connection ready as soon as the ping completes, but the client library populates messageHandlersByID slightly after, so the dispatch fails.
Minimal reproduction
I built a ~50-line Swift app to confirm this isn't specific to OV Message. UIKit AppDelegate, single for await in TelephonyMessagingSession.shared.smsService.incomingMessageNotifications started in didFinishLaunchingWithOptions. No SwiftUI, no other modules, no Darwin notifications. Just TMK.
Steps:
Build & install on iPhone iOS 26.x with carrier-messaging-app entitlement (auto-provisioned in iOS 26)
Settings → Apps → Default Messaging → select the test app
Force-kill, then send 2 SMS in rapid succession from another phone
Wait 30 s, open the app — log shows only the 2nd SMS
Same result: the 1st SMS is gone. I've reproduced this consistently dozens of times.
Source code (Swift + xcodegen project.yml): https://gist.github.com/ovmessage/fbc529292a65222191bec6ce5e5a4275
What I've tried
Task.detached(priority: .userInitiated) to decouple the for await from main thread scheduling — no effect (race is internal to TMK lib, before our scheduling)
Pre-fetching cellularServices synchronously — no effect
Subscribing MMS + RCS in parallel — no effect
Direct XPCSession/xpc_connection_create_mach_service to com.apple.commcenter.tmk.xpc — Apple has marked these unavailable on iOS for 3rd-party apps (no public way to bypass the lib)
I've also done runtime introspection of the TMK framework via Mirror, which confirms the architecture: a single XPCConnection.messageHandlersByID dict shared by smsReceived, mmsReceived, rcsReceivedNotification — all four entries (incl. serviceStatusNotification) are populated after the XPC ping. So the same race affects SMS, MMS, and RCS equally.
Suggested fixes (Apple-side)
Either:
Server (CommCenter): defer flushing pending messages until the client confirms its handlers are registered (extra XPC handshake message)
Client (TelephonyMessagingKit): register messageHandlersByID entries before sending Pinging remote end, so they exist when the server starts flushing
Buffer client-side: cache messages received before handler registration completes, dispatch on attach
Filed in Feedback Assistant
FB[YOUR_FB_NUMBER_HERE]
Question for fellow devs
If you're also building with carrier-messaging-app entitlement (Beeper, Google Messages on iOS, anyone in the EU programme), can you confirm whether you see the same race? Especially interested in whether:
It happens with non-IMS carriers (mine is SFR France, IMS-routed via SIP)
iOS 26.1 / 26.2 changed the timing
Anyone has found a workaround I haven't tried
Thanks.