push-notification-best-practices — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited push-notification-best-practices (Agent Skill) and scored it 92/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 2 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 2 flagged
The text {match} tells the agent to skip the normal "ask the user first" gate. Used adversarially it removes the human-in-the-loop check before destructive or sensitive actions, turning a normally-gated agent into a fire-and-forget executor.
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Comprehensive guide for implementing and troubleshooting push notifications in mobile applications. Covers iOS (APNS), Android (FCM), React Native, Expo, and Flutter platforms with platform-specific configurations, token management, message handling, and deep linking patterns.
| Platform | Offline Push | Notification Bar | Click Navigation | In-App Toast | Background Handler |
|---|---|---|---|---|---|
| iOS | APNS | System | Deep Link | Custom | data-only payload |
| Android | FCM | System | Intent | Custom | data-only payload |
| React Native | APNS/FCM | System | React Navigation | Custom | setBackgroundMessageHandler |
| Expo | Expo Push | System | Linking | Custom | TaskManager |
| Flutter | APNS/FCM | System | Navigator | Custom | onBackgroundMessage |
When to request notification permission?
User installing app
|
v
[First launch?] ──Yes──> [Show value proposition first]
| |
No v
| [User action triggers need?]
v |
[Already granted?] Yes
| |
Yes v
| [Request permission] ──> 70-80% acceptance rate
v
[Notifications ready]| Timing | Acceptance Rate | Use Case |
|---|---|---|
| Immediate (app launch) | 15-20% | Low engagement apps |
| After onboarding | 40-50% | Standard apps |
| User-initiated action | 70-80% | High engagement apps |
Recommendation: Request after explaining value or when user enables a related feature.
Which payload type should I use?
[What's the purpose?]
|
+──> Time-sensitive user alert ──> Visible (notification payload)
|
+──> Background data sync ──> Silent (data-only payload)
|
+──> Custom UI required ──> Silent (data-only payload)
|
+──> Need background processing ──> Silent (data-only payload)| Scenario | Payload Type | Reason |
|---|---|---|
| New message alert | Visible | User needs immediate attention |
| Order status update | Visible | Time-sensitive information |
| Background sync | Silent | No user interruption needed |
| Custom notification UI | Silent | Full control over display |
| Update badge count | Silent | Background processing needed |
When to use Notification Service Extension?
When to use Notification Content Extension?
| NEVER | Why | Instead |
|---|---|---|
| Request permission on first launch without context | 15-20% acceptance rate | Explain value first, then request |
| Re-ask after user denies | System ignores repeated requests | Show settings redirect |
| Ignore provisional authorization | Misses iOS 12+ quiet delivery | Use .provisional option |
| NEVER | Why | Instead |
|---|---|---|
| Cache tokens long-term | Tokens can change on reinstall/restore | Always use fresh token from callback |
| Assume token format | Format varies by platform/SDK | Treat as opaque string |
| Send token without user association | Can't target notifications | Associate with userId on backend |
| Store tokens without device ID | Duplicate tokens per user | Use deviceId as unique key |
| NEVER | Why | Instead |
|---|---|---|
Use notification payload for background processing | onMessageReceived not called in background | Use data-only payload |
| Rely on silent notifications for time-critical delivery | Delivery not guaranteed | Use visible notifications |
| Execute heavy operations in background handler | System kills app after ~30 seconds | Queue work, process quickly |
| Forget to handle both payload types | Missing notifications | Handle notification + data payloads |
| NEVER | Why | Instead |
|---|---|---|
| Register for notifications before delegate setup | Delegate methods not called | Set delegate before registerForRemoteNotifications() |
Skip serviceExtensionTimeWillExpire() implementation | Content modification fails | Always implement fallback |
| Use .p12 certificates | Expires yearly, deprecated | Use .p8 authentication key |
| NEVER | Why | Instead |
|---|---|---|
| Skip NotificationChannel creation | Notifications don't appear on Android 8.0+ | Create channel at app start |
Use priority normal for background handlers | Doze mode blocks delivery | Use priority high |
| Use colored notification icons | Android ignores colors, shows white square | Use white-on-transparent icons |
Each rule file follows a hybrid format for fast lookup and deep understanding:
Impact ratings: CRITICAL (fix immediately), HIGH (significant improvement), MEDIUM (worthwhile optimization)
Reference these guidelines when:
| Priority | Category | Impact | Prefix |
|---|---|---|---|
| 1 | iOS Setup | CRITICAL | ios- |
| 2 | Android Setup | CRITICAL | android- |
| 3 | Token Management | HIGH | token- |
| 4 | Message Handling | HIGH | message- |
| 5 | Deep Linking | MEDIUM | deeplink- |
| 6 | Infrastructure | MEDIUM | infra- |
Setup checklist:
Setup checklist:
Token lifecycle:
// 1. Register token with server
const token = await getToken();
await registerToken(token, userId);
// 2. Handle token refresh
onTokenRefresh((newToken) => {
updateTokenOnServer(newToken, userId);
});
// 3. Handle invalidation
if (error.code === 'DeviceNotRegistered') {
deleteTokenFromDatabase(token);
}Payload types:
Priority settings:
priority: 'high' for time-sensitive notificationsFull documentation with code examples in rules/:
ios-*)| File | Impact | Description |
|---|---|---|
ios-apns-auth-key.md | CRITICAL | APNs authentication key setup |
ios-delegate-setup.md | CRITICAL | UNUserNotificationCenter delegate configuration |
ios-permission-request.md | CRITICAL | Notification permission request flow |
ios-token-registration.md | HIGH | APNS token registration and handling |
ios-foreground-display.md | HIGH | Display notifications in foreground |
ios-method-swizzling.md | MEDIUM | Method Swizzling management |
ios-extension-build.md | MEDIUM | Notification Service Extension setup |
android-*)| File | Impact | Description |
|---|---|---|
android-notification-channel.md | CRITICAL | Notification channel creation (Android 8.0+) |
android-permission.md | CRITICAL | POST_NOTIFICATIONS runtime permission (Android 13+) |
android-google-services.md | CRITICAL | Firebase project configuration |
android-messaging-service.md | HIGH | FirebaseMessagingService implementation |
android-notification-icon.md | MEDIUM | Notification icon asset requirements |
android-priority-high.md | HIGH | Priority 'high' for Doze mode |
token-*)| File | Impact | Description |
|---|---|---|
token-registration.md | HIGH | Token registration with backend |
token-refresh.md | HIGH | Token refresh handling |
token-invalidation.md | MEDIUM | DeviceNotRegistered error handling |
message-*)| File | Impact | Description |
|---|---|---|
message-data-vs-notification.md | CRITICAL | data vs notification payload differences |
message-background-handler.md | HIGH | Background message processing |
message-foreground-handler.md | HIGH | Foreground notification display |
deeplink-*)| File | Impact | Description |
|---|---|---|
deeplink-navigation-conflict.md | MEDIUM | React Navigation conflict resolution |
deeplink-terminated-state.md | MEDIUM | Deep link handling when app is terminated |
infra-*)| File | Impact | Description |
|---|---|---|
infra-firewall-ports.md | MEDIUM | Network firewall configuration |
infra-backup-fid.md | MEDIUM | Firebase Installation ID backup exclusion |
infra-rate-limiting.md | MEDIUM | Push notification rate limiting |
| File | Impact | Description |
|---|---|---|
permission-timing.md | HIGH | Permission request timing optimization (70-80% acceptance) |
testing-debugging.md | HIGH | Testing tools, debugging techniques, payload validation |
# Find rules by keyword
grep -l "apns" rules/
grep -l "fcm" rules/
grep -l "token" rules/
grep -l "background" rules/
grep -l "permission" rules/
grep -l "deeplink" rules/| Problem | Start With |
|---|---|
| iOS not receiving push | ios-apns-auth-key → ios-delegate-setup |
| Android not receiving push | android-google-services → android-notification-channel |
| Push not working in background | android-priority-high → message-background-handler |
| Push not visible in foreground | ios-foreground-display or message-foreground-handler |
| Token missing/expired | token-registration → token-refresh |
| Deep link conflict error | deeplink-navigation-conflict |
| Notification icon broken (Android) | android-notification-icon |
| Failed on corporate network | infra-firewall-ports |
| Background handler not called (Android) | android-priority-high → message-data-vs-notification |
| userNotificationCenter not called (iOS) | ios-delegate-setup |
| 404 error after backup restore | infra-backup-fid |
| Send failed (429 error) | infra-rate-limiting |
| Low permission acceptance rate | permission-timing → ios-permission-request |
| How to debug/test push | testing-debugging |
| Cannot test locally | testing-debugging → ios-apns-auth-key |
priority: 'high' for background handlersgcm.message_id required for foreground notificationsExponentPushToken[...]eas credentials for APNs key managementFor the complete guide with all rules expanded: AGENTS.md
Based on push notification troubleshooting guides and Firebase Cloud Messaging official documentation.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.