Design a push notification strategy that earns opt-in (permission priming, contextual ask), converts re-engagement without spamming, and respects the one-shot system prompt. Use when the user says 'push notifications', 'notification strategy', 'push opt-in', 'permission priming', 're-engagement', 'notification copy', or 'reminders'.
Installs into .claude/skills of the current project.
Are you the author of Design Push Strategy?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/khadinakbarlabs-design-push-strategy)
---
name: "design-push-strategy"
description: "Design a push notification strategy that earns opt-in (permission priming, contextual ask), converts re-engagement without spamming, and respects the one-shot system prompt. Use when the user says 'push notifications', 'notification strategy', 'push opt-in', 'permission priming', 're-engagement', 'notification copy', or 'reminders'."
---
# Design a push strategy
Push is one of the strongest retention levers — apps that use it well see 3–5× higher 90-day retention. But the system prompt is **one-shot**: if denied, you can't re-trigger it without sending the user to Settings. And **up to 60% of users opt out** once it became opt-in. The strategy is almost entirely about *earning* the permission and then *not wasting* it.
## The blunt truth first
Research-validated: "Push notifications don't create retention. They reveal whether retention already exists. If users only come back because you ping them, you don't have retention." ([r/expo](https://www.reddit.com/r/expo/comments/1q6m6gr/over_90_of_app_users_churn_in_the_first_30_days/)) Build the retention loop first (`design-retention-loop`); use push as the *trigger* in that loop, not as a substitute for it.
## Earning the permission (the opt-in is the whole game)
Expected opt-in rate by strategy:
| Strategy | Opt-in rate |
|---|---|
| Immediate prompt on launch (discouraged) | ~20–30% |
| Timed/contextual prompt (no pre-prompt) | ~40–50% |
| **Pre-prompt (soft ask) + timed** | **~55–65% (gold standard)** |
| Provisional authorization (iOS) | ~100% delivery (quiet) |
([Apple: Asking permission to use notifications](https://developer.apple.com/documentation/usernotifications/asking-permission-to-use-notifications))
### The iOS playbook
1. **Pre-prompt (your custom UI)** explaining the value *before* triggering the system prompt. "Allow" → fire system prompt; "Not now" → save preference, **don't burn the system prompt**.
2. **Timing**: ask after a "moment of value" (finished setup, completed first action, earned a streak), never cold on launch.
3. **Value-first copy**: "Get instant alerts when your streak is at risk and exclusive daily challenges" beats "Can we send you notifications?"
4. **Provisional authorization** (iOS 12+, `.provisional`): sends quiet notifications to Notification Center with no prompt. Proves value; users can promote to prominent or turn off. Good for transactional/utility notifications.
**Critical rejection gotcha** (Reddit-validated): Apple rejects custom pre-permission screens whose CTA copy "encourages" the permission. Use **neutral CTAs ("Continue" / "Next")** — never "Allow", "Enable", "Turn On." ([r/iOSProgramming](https://www.reddit.com/r/iOSProgramming/comments/1t57gda/app_rejected_because_my_microphone_permission/)) This applies to notification pre-prompts too.
### Android
Android 13+ requires the runtime POST_NOTIFICATIONS permission. Same principle: prime with value, ask at the contextual moment, don't ask cold on launch. Channel-based: let users opt into the *kinds* of notifications they want (e.g., streak reminders yes, marketing no) — this materially improves opt-in and reduces mutes.
## Implementation (Expo)
```bash
npm exec --no -- expo install expo-notifications
```
Requires a **development build** (not Expo Go) for real push on SDK 54+, and `UIBackgroundModes: ["remote-notification"]` in app.json. Simulators can't fully register. See `add-expo-notifications`.
```ts
import * as Notifications from 'expo-notifications';
// 1. Prime with your custom UI first (neutral CTA), THEN:
async function requestPermission() {
const { status: existing } = await Notifications.getPermissionsAsync();
let finalStatus = existing;
if (existing !== 'granted') {
const { status } = await Notifications.requestPermissionsAsync();
finalStatus = status;
}
if (finalStatus !== 'granted') return null; // denied — don't ask again programmatically
const token = (await Notifications.getDevicePushTokenAsync()).data;
return token; // send to your server / provider
}
```
## Content that re-engages (without being spam)
- **Personalized** — name + past behavior ("Sara, your Spanish lesson is ready"). Not "Reminder."
- **Lifecycle-triggered** — streak-break risk, abandoned action, daily reward, trial expiring. Not broadcast blasts.
- **Rich media** — `UNNotificationAttachment` with an image.
- **Actionable buttons** — `UNNotificationCategory` (Reply, Snooze, Claim). One-tap actions convert better than tap-to-open.
- **Deep-linked** — tapping opens the specific in-app area, not the home screen. See `add-deep-links`.
- **Time-zone aware** — a 3am push is an uninstall.
## Frequency and coordination
- **~2–3 pushes per week** for a typical consumer app, unless inherently time-sensitive.
- **Coordinate with email/in-app** — never send the same message across channels the same day. See `design-lifecycle-messaging` for the cross-channel cadence and frequency capping.
- **Segment**: send relevant pushes to relevant segments, not blanket broadcasts. Segmentation is the gap most apps never close. ([r/reactnative](https://www.reddit.com/r/reactnative/comments/prf7lc/push_notifications_user_segments/))
## The re-engagement ladder (inactive users)
```text
3 days inactive → gentle nudge (push): specific value reason
7 days inactive → direct message (push + email): incentive or content
14 days inactive → escalated offer (email): discount / unlock / "what's new"
30 days inactive → final win-back (email), then suppress
```
Suppress after sustained non-response. See `build-win-back-flow` and `design-lifecycle-messaging`.
## Measure
```text
push_permission_requested
push_permission_granted // the opt-in rate — the #1 metric here
push_received / push_delivered // delivery is unreliable; track both
push_opened {campaign}
push_converted {goal}
push_unsubscribed
uninstall_after_push // the spam canary
```
`uninstall_after_push` per campaign is the early warning that a message (or cadence) is too aggressive.
## What not to do
- **Don't prompt cold on launch.** Earn it.
- **Don't use persuasive CTAs** in the pre-prompt (Apple rejection risk).
- **Don't broadcast generic "we miss you" pushes.**
- **Don't send at 3am local time.**
- **Don't re-prompt after denial** programmatically (it's a no-op on iOS; route to Settings instead via a deep link).
- **Don't fire paywall re-prompts** in re-engagement pushes to users who just rejected the paywall.
- **Don't rely on push alone** to retain — it reveals retention, it doesn't create it.
## Pair with
- `add-expo-notifications` — implementation.
- `design-retention-loop` — push is the trigger in the Hook Model.
- `design-lifecycle-messaging` — cross-channel coordination and frequency capping.
- `build-win-back-flow` — the inactive-user ladder.
- `instrument-growth-funnel` — measuring opt-in and push performance.