relate #2511
Summary
Enable iOS Critical Alerts for incident-triggered push notifications sent to on-call responders when the incident severity is explicitly marked as Critical Alert eligible by the project.
This requires:
- Apple entitlement approval for
com.apple.developer.usernotifications.critical-alerts.
- iOS app entitlement/config updates in
MobileApp.
- Server-side Expo push payload changes to send
interruptionLevel: "critical" only when the incident severity is configured by the project as Critical Alert eligible.
- Narrow scoping so only severity-approved incident-driven on-call notifications use Critical Alerts.
Problem
Today the iOS app requests notification permissions and already asks for allowCriticalAlerts, but the app and push pipeline do not complete the full critical-alert path.
Observed current state:
MobileApp/src/notifications/setup.ts requests iOS permission with allowCriticalAlerts: true.
MobileApp/app.json configures expo-notifications and UIBackgroundModes, but does not declare the iOS critical alerts entitlement.
Common/Server/Services/PushNotificationService.ts sends Expo messages with:
sound: "default"
priority: "high"
- Android
channelId
- no
interruptionLevel
- OneUptime mobile push is routed through Expo Push (
MobileApp/README.md, App/FeatureSet/Docs/Content/en/self-hosted/push-notifications.md).
Result: on-call notifications cannot reliably break through iOS silent / DND / Focus modes the way dedicated incident-response apps do.
Goals
- Deliver incident-created notifications to on-call responders as elevated notifications when the incident severity is marked Critical Alert eligible by the project:
- iOS uses Critical Alerts
- Android uses the
oncall_critical notification channel
- Keep non-critical-severity mobile pushes on the existing high-priority path.
- Preserve Expo Push as the transport.
Non-Goals
- Replacing Expo Push with direct APNs.
- Making every mobile notification critical.
- Reworking notification routing, persistence, or token registration.
- Solving app-store / Apple review process in code alone.
Constraints
Apple entitlement requirement
Apple requires the com.apple.developer.usernotifications.critical-alerts entitlement. Without it, the app may request critical permissions but cannot legally/functionally deliver true Critical Alerts.
Reference: Apple docs say the entitlement permits an app to receive critical alert notifications and must be requested from Apple.
Expo transport requirement
Expo Push supports iOS interruptionLevel, including critical, in the push payload format. Expo is therefore not the blocker by itself; the missing piece is entitlement + payload selection + deployment.
Current Architecture
Mobile app
Relevant files:
MobileApp/app.json
MobileApp/src/notifications/setup.ts
MobileApp/src/hooks/usePushNotifications.ts
Current behavior:
- Registers Expo push token on login.
- Requests notification permissions.
- Creates Android notification channels only.
- Does not define iOS entitlements for critical alerts in app config.
Push send path
Relevant files:
Common/Server/Services/PushNotificationService.ts
App/FeatureSet/Notification/API/PushRelay.ts
Common/Server/Services/UserNotificationRuleService.ts
Current behavior:
- On-call alerts/incidents/pages are sent through
PushNotificationService.sendPushNotification(...).
- Expo payloads are assembled centrally in
PushNotificationService.ts.
- Relay path mirrors the same payload shape.
- There is currently no way to mark a push as iOS-critical.
Proposal
1. Add iOS critical alerts entitlement to the app config
Update MobileApp/app.json to declare the entitlement for iOS builds.
Proposed config shape:
expo.ios.entitlements["com.apple.developer.usernotifications.critical-alerts"] = true
This must ship only after Apple approves the entitlement for the production app identifier.
2. Keep permission request logic, but verify/observe it
MobileApp/src/notifications/setup.ts already requests:
allowAlert: true
allowBadge: true
allowSound: true
allowCriticalAlerts: true
No major structural change is required here. Optional improvement:
- add explicit logging / telemetry for permission result so support can distinguish:
- standard notification approval
- critical-alert approval denied
3. Extend server push payloads with platform-specific critical elevation
Add a first-class way for the server to express that a push should be elevated for critical incident delivery.
Proposed approach:
- extend
PushNotificationOptions in Common/Server/Services/PushNotificationService.ts with something like:
isCriticalAlert?: boolean
Then in payload assembly (sendExpoPushNotification, sendViaRelay, sendRelayPushNotification):
- when
isCriticalAlert === true:
- iOS includes
interruptionLevel: "critical"
- Android uses
channelId: "oncall_critical"
- when not critical:
- preserve the existing high-priority path
- keep current
sound handling
4. Propagate the flag through relay API
Update App/FeatureSet/Notification/API/PushRelay.ts to accept and forward the new field to PushNotificationService.sendRelayPushNotification(...).
This keeps both direct Expo and relay-driven paths consistent.
5. Mark only severity-approved incident-driven on-call notifications as critical
Do not mark all pushes as critical.
Initial scope should be limited to the push calls made from on-call escalation flows in:
Common/Server/Services/UserNotificationRuleService.ts
The project must explicitly decide which incident severities are Critical Alert eligible. Proposed implementation:
- add a boolean field to
IncidentSeverity, for example isCriticalSeverity
- set
isCriticalAlert: true only when:
- the notification is incident-driven, and
incident.incidentSeverity?.isCriticalSeverity === true
This avoids guessing from names like P1 or Critical, and avoids inferring from order.
For the first release, boring/default behavior is:
- keep the existing high-priority path as-is for all current mobile notifications
- elevate only incident-driven notifications sent to on-call users with Critical Alert eligible severities:
- iOS =>
interruptionLevel: "critical"
- Android =>
channelId: "oncall_critical"
- do not introduce a broader normal/high/critical redesign in this change
Implementation Plan
Mobile app changes
- Update
MobileApp/app.json with iOS entitlement.
- Rebuild iOS app with approved Apple capability.
- Verify the generated iOS entitlements in prebuild/native output.
Web UI changes
- Extend
IncidentSeverity settings UI so project admins can mark a severity as Critical Alert eligible.
- Update the Incident Severity form/table in
App/FeatureSet/Dashboard/src/Pages/Incidents/Settings/IncidentSeverity.tsx.
- Surface the new boolean field from
Common/Models/DatabaseModels/IncidentSeverity.ts.
Server changes
- Extend
IncidentSeverity with a Critical Alert eligibility flag.
- Add critical-alert option to push send API.
- Add
interruptionLevel: "critical" support for iOS payloads.
- Add
channelId: "oncall_critical" support for Android payloads.
- Add relay request/response support.
- Set the elevated path only when the incident severity is flagged as Critical Alert eligible.
- Preserve the existing high-priority path for all non-critical notifications.
Upstream Release Dependencies
The following items are outside the implementation scope of this RFC and must be handled by the OneUptime mobile app release owner:
- Request and obtain Apple approval for the
com.apple.developer.usernotifications.critical-alerts entitlement.
- Build a new iOS app binary with the approved entitlement.
- Release the updated mobile app through the normal OneUptime distribution path.
Testing Strategy
Code-level
- Unit tests for payload construction in
PushNotificationService.ts:
- iOS critical =>
interruptionLevel: "critical"
- iOS non-critical => no critical interruption level
- Android critical =>
channelId: "oncall_critical"
- Android non-critical => existing high-priority
channelId behavior preserved
- Relay tests for forwarding the new field in
PushRelay.ts.
Release validation
Test on physical devices using the released entitlement-enabled build:
- Install the updated mobile build.
- Grant notification + critical alert permissions on iPhone.
- Trigger an incident-created notification to an on-call responder whose incident severity is marked Critical Alert eligible.
- Verify iOS audible delivery during:
- silent switch enabled
- Do Not Disturb / Focus enabled
- Verify Android delivery uses the
oncall_critical channel for the same incident severity.
- Verify incidents with non-eligible severities remain on the existing high-priority path.
Docs to Update After Shipping
MobileApp/README.md
App/FeatureSet/Docs/Content/en/self-hosted/push-notifications.md
App/FeatureSet/Docs/Content/en/mobile-desktop-apps/ios-installation.md
Add guidance for:
- entitlement dependency
- critical alert permission prompt
- expected behavior for incident-created on-call notifications only when the incident severity is marked Critical Alert eligible
Minimal File List
Primary implementation files:
MobileApp/app.json
MobileApp/src/notifications/setup.ts (likely small/no-op code change; verify logging only)
Common/Models/DatabaseModels/IncidentSeverity.ts
App/FeatureSet/Dashboard/src/Pages/Incidents/Settings/IncidentSeverity.tsx
Common/Server/Services/PushNotificationService.ts
App/FeatureSet/Notification/API/PushRelay.ts
Common/Server/Services/UserNotificationRuleService.ts
Optional/supporting files:
- tests near
PushNotificationService.ts
- docs listed above
relate #2511
Summary
Enable iOS Critical Alerts for incident-triggered push notifications sent to on-call responders when the incident severity is explicitly marked as Critical Alert eligible by the project.
This requires:
com.apple.developer.usernotifications.critical-alerts.MobileApp.interruptionLevel: "critical"only when the incident severity is configured by the project as Critical Alert eligible.Problem
Today the iOS app requests notification permissions and already asks for
allowCriticalAlerts, but the app and push pipeline do not complete the full critical-alert path.Observed current state:
MobileApp/src/notifications/setup.tsrequests iOS permission withallowCriticalAlerts: true.MobileApp/app.jsonconfiguresexpo-notificationsandUIBackgroundModes, but does not declare the iOS critical alerts entitlement.Common/Server/Services/PushNotificationService.tssends Expo messages with:sound: "default"priority: "high"channelIdinterruptionLevelMobileApp/README.md,App/FeatureSet/Docs/Content/en/self-hosted/push-notifications.md).Result: on-call notifications cannot reliably break through iOS silent / DND / Focus modes the way dedicated incident-response apps do.
Goals
oncall_criticalnotification channelNon-Goals
Constraints
Apple entitlement requirement
Apple requires the
com.apple.developer.usernotifications.critical-alertsentitlement. Without it, the app may request critical permissions but cannot legally/functionally deliver true Critical Alerts.Reference: Apple docs say the entitlement permits an app to receive critical alert notifications and must be requested from Apple.
Expo transport requirement
Expo Push supports iOS
interruptionLevel, includingcritical, in the push payload format. Expo is therefore not the blocker by itself; the missing piece is entitlement + payload selection + deployment.Current Architecture
Mobile app
Relevant files:
MobileApp/app.jsonMobileApp/src/notifications/setup.tsMobileApp/src/hooks/usePushNotifications.tsCurrent behavior:
Push send path
Relevant files:
Common/Server/Services/PushNotificationService.tsApp/FeatureSet/Notification/API/PushRelay.tsCommon/Server/Services/UserNotificationRuleService.tsCurrent behavior:
PushNotificationService.sendPushNotification(...).PushNotificationService.ts.Proposal
1. Add iOS critical alerts entitlement to the app config
Update
MobileApp/app.jsonto declare the entitlement for iOS builds.Proposed config shape:
expo.ios.entitlements["com.apple.developer.usernotifications.critical-alerts"] = trueThis must ship only after Apple approves the entitlement for the production app identifier.
2. Keep permission request logic, but verify/observe it
MobileApp/src/notifications/setup.tsalready requests:allowAlert: trueallowBadge: trueallowSound: trueallowCriticalAlerts: trueNo major structural change is required here. Optional improvement:
3. Extend server push payloads with platform-specific critical elevation
Add a first-class way for the server to express that a push should be elevated for critical incident delivery.
Proposed approach:
PushNotificationOptionsinCommon/Server/Services/PushNotificationService.tswith something like:isCriticalAlert?: booleanThen in payload assembly (
sendExpoPushNotification,sendViaRelay,sendRelayPushNotification):isCriticalAlert === true:interruptionLevel: "critical"channelId: "oncall_critical"soundhandling4. Propagate the flag through relay API
Update
App/FeatureSet/Notification/API/PushRelay.tsto accept and forward the new field toPushNotificationService.sendRelayPushNotification(...).This keeps both direct Expo and relay-driven paths consistent.
5. Mark only severity-approved incident-driven on-call notifications as critical
Do not mark all pushes as critical.
Initial scope should be limited to the push calls made from on-call escalation flows in:
Common/Server/Services/UserNotificationRuleService.tsThe project must explicitly decide which incident severities are Critical Alert eligible. Proposed implementation:
IncidentSeverity, for exampleisCriticalSeverityisCriticalAlert: trueonly when:incident.incidentSeverity?.isCriticalSeverity === trueThis avoids guessing from names like
P1orCritical, and avoids inferring fromorder.For the first release, boring/default behavior is:
interruptionLevel: "critical"channelId: "oncall_critical"Implementation Plan
Mobile app changes
MobileApp/app.jsonwith iOS entitlement.Web UI changes
IncidentSeveritysettings UI so project admins can mark a severity as Critical Alert eligible.App/FeatureSet/Dashboard/src/Pages/Incidents/Settings/IncidentSeverity.tsx.Common/Models/DatabaseModels/IncidentSeverity.ts.Server changes
IncidentSeveritywith a Critical Alert eligibility flag.interruptionLevel: "critical"support for iOS payloads.channelId: "oncall_critical"support for Android payloads.Upstream Release Dependencies
The following items are outside the implementation scope of this RFC and must be handled by the OneUptime mobile app release owner:
com.apple.developer.usernotifications.critical-alertsentitlement.Testing Strategy
Code-level
PushNotificationService.ts:interruptionLevel: "critical"channelId: "oncall_critical"channelIdbehavior preservedPushRelay.ts.Release validation
Test on physical devices using the released entitlement-enabled build:
oncall_criticalchannel for the same incident severity.Docs to Update After Shipping
MobileApp/README.mdApp/FeatureSet/Docs/Content/en/self-hosted/push-notifications.mdApp/FeatureSet/Docs/Content/en/mobile-desktop-apps/ios-installation.mdAdd guidance for:
Minimal File List
Primary implementation files:
MobileApp/app.jsonMobileApp/src/notifications/setup.ts(likely small/no-op code change; verify logging only)Common/Models/DatabaseModels/IncidentSeverity.tsApp/FeatureSet/Dashboard/src/Pages/Incidents/Settings/IncidentSeverity.tsxCommon/Server/Services/PushNotificationService.tsApp/FeatureSet/Notification/API/PushRelay.tsCommon/Server/Services/UserNotificationRuleService.tsOptional/supporting files:
PushNotificationService.ts