Skip to content
Skip to content
Goodspeed

Push Notifications Without Being Annoying

How to use push notifications to drive engagement without driving users away.

GUIDE BODY

The Push Notification Paradox

Push notifications are the most powerful engagement tool in mobile apps. They are also the fastest way to get uninstalled. The difference between a helpful notification and an annoying one is not volume or timing. It is relevance.

Users who opt in to well-targeted notifications have 88% higher retention rates. But 71% of app uninstalls are triggered by annoying notifications. Getting this right matters more than almost any feature you could build.

Permission: The First Gate

Before you can send a single notification, users must grant permission. On iOS, you get one chance. If the user taps "Don't Allow," you cannot ask again (without sending them to system settings).

The Pre-Permission Strategy

Never show the system permission dialog on the first app open. Instead:

  1. Let users experience value first. Wait until they have completed at least one core action.
  2. Show a pre-permission screen explaining why notifications will help them. This screen uses your own UI, not the system dialog.
  3. If they tap "Enable Notifications," show the system dialog. At this point, they are primed to accept.
  4. If they tap "Not Now," respect it. Ask again later after they have used the app more.

Pre-Permission Screen Template

[Notification bell icon]

Stay on Track
Get reminders for your daily goals, alerts when
your habits are at risk, and weekly progress summaries.

[Enable Notifications]  (primary button)
[Not Now]               (text link)

Opt-In Rate Benchmarks

  • Without pre-permission screen: 40-50% opt-in
  • With pre-permission screen: 60-80% opt-in
  • With pre-permission after value moment: 70-85% opt-in

The pre-permission screen alone can double your effective notification audience.

Types of Notifications

Not all notifications are created equal. Each type serves a different purpose and has different user tolerance levels.

Transactional Notifications

Triggered by user actions. These have the highest tolerance and the highest open rates.

  • "Your export is ready to download"
  • "Payment received: $49.99"
  • "Your workout summary: 340 calories burned"

Rules: Always send these. They confirm actions the user took. Never suppress transactional notifications.

Reminder Notifications

Scheduled by the user or triggered by user-set goals. Moderate tolerance.

  • "Time for your evening meditation"
  • "You have 3 tasks due today"
  • "Daily budget check: you have $42 remaining"

Rules: Only send reminders the user explicitly opted into. Provide granular controls (which reminders, what time, which days).

Engagement Notifications

Sent to bring inactive users back. Low tolerance. This is where most apps go wrong.

  • "You haven't logged a workout in 3 days. Your streak is at risk!"
  • "New feature: AI-powered meal suggestions are here"
  • "Your weekly report is ready"

Rules: Be extremely selective. Only send when you have something genuinely useful to communicate. Never send generic "We miss you!" messages.

Social Notifications

Triggered by other users' actions. Tolerance varies by app type.

  • "Sarah liked your post"
  • "3 people commented on your recipe"
  • "Your friend John just joined"

Rules: Let users control which social events trigger notifications. Batch social notifications (show "5 people liked your post" instead of 5 separate notifications).

Marketing Notifications

Promotions, sales, and announcements. Very low tolerance.

  • "Flash sale: 50% off annual subscription"
  • "New content: 10 guided meditations added"

Rules: Use sparingly (max once per week). Always provide opt-out. Make them genuinely valuable, not just promotional.

Notification Design

The Anatomy of a Good Notification

A notification has limited space. Make every word count.

Title: Short, specific, and action-oriented.

  • Good: "Budget Alert: Dining Out"
  • Bad: "Hey there!"

Body: One sentence with context and a reason to open the app.

  • Good: "You have spent $85 of your $100 dining budget this week."
  • Bad: "Check your app for an update on your budget."

Deep link: Notifications should open the relevant screen, not the home screen. If the notification is about a budget alert, open the budget detail screen.

// When scheduling a notification
await Notifications.scheduleNotificationAsync({
  content: {
    title: 'Budget Alert: Dining Out',
    body: 'You\'ve spent $85 of your $100 dining budget this week.',
    data: { screen: 'budget-detail', categoryId: 'dining' },
  },
  trigger: null, // send immediately
});

// When handling the notification tap
Notifications.addNotificationResponseReceivedListener((response) => {
  const { screen, categoryId } = response.notification.request.content.data;
  router.push(`/${screen}?id=${categoryId}`);
});

Rich Notifications

iOS and Android support rich notifications with images, action buttons, and expanded content.

Images: Show a chart, a progress graph, or a relevant photo. Rich notifications get 56% higher engagement.

Action buttons: Add quick actions so users can respond without opening the app.

[Morning Check-in]
How did you sleep last night?

[Great 😊]  [Okay 😐]  [Poorly 😴]

Timing and Frequency

When to Send

  • Respect time zones. Never send notifications between 10 PM and 8 AM local time.
  • Learn user patterns. If a user always opens the app at 7 AM, schedule their notification for 6:50 AM.
  • Avoid peak notification hours. Between 8-9 AM and 5-6 PM, users receive the most notifications. Yours gets lost in the flood. Try mid-morning (10 AM) or early afternoon (2 PM).

How Often to Send

The optimal frequency depends on your app type:

| App Type | Recommended Max | Example | |----------|----------------|---------| | Social | 5-10/day | Activity notifications | | Productivity | 1-3/day | Task reminders | | Health/Fitness | 1-2/day | Workout/meal reminders | | Finance | 1/day | Daily summary | | News/Content | 2-3/day | Breaking news + digest | | Utility | 1-2/week | Actionable alerts only |

Frequency Capping

Implement a system-wide cap. Regardless of how many notifications are triggered, never send more than N per day. A good default cap is 3-5 per day for most apps.

async function shouldSendNotification(userId: string): Promise<boolean> {
  const today = new Date().toISOString().split('T')[0];
  const sentToday = await getNotificationCount(userId, today);
  const MAX_DAILY = 5;
  return sentToday < MAX_DAILY;
}

Personalization

Generic notifications perform poorly. Personalized notifications perform well.

Levels of Personalization

Level 1: Use the user's name.

  • "Sarah, your workout streak is at 7 days!"

Level 2: Reference their data.

  • "You saved $230 this month, $80 more than last month."

Level 3: Predict their needs.

  • "Based on your schedule, tomorrow is a good day for a rest day."

Level 4: Adapt to their behavior.

  • If a user always dismisses evening reminders, stop sending them.
  • If a user engages most with progress notifications, send more of those.

Notification Preferences Screen

Give users fine-grained control:

Notification Settings

Daily Reminders
  Morning check-in        [toggle]
  Evening review           [toggle]
  Reminder time           [10:00 AM]

Progress Updates
  Weekly summary           [toggle]
  Streak alerts            [toggle]
  Goal completion          [toggle]

Community
  Comments on your posts   [toggle]
  New followers            [toggle]

Marketing
  New features             [toggle]
  Special offers           [toggle]

Every notification category should have its own toggle. Users who can control their experience are less likely to disable notifications entirely.

Measuring Notification Performance

Key Metrics

  • Opt-in rate: Percentage of users who grant notification permission. Target: 60%+.
  • Delivery rate: Percentage of sent notifications actually delivered. Should be 95%+. Lower rates indicate token management issues.
  • Open rate: Percentage of delivered notifications that users tap. Benchmark: 5-15% for engagement notifications, 20-40% for transactional.
  • Dismiss rate: Percentage of notifications users swipe away without tapping. High dismiss rates signal irrelevant content.
  • Opt-out rate: Percentage of users who disable notifications. If this spikes, you are sending too many or the wrong ones.
  • Uninstall rate post-notification: If uninstalls spike after a notification blast, you crossed the line.

A/B Testing Notifications

Test one variable at a time:

  • Copy: Test different titles and body text
  • Timing: Test morning vs. afternoon delivery
  • Frequency: Test 1/day vs. 3/day
  • Rich vs. plain: Test notifications with and without images

Run each test for at least one week with at least 1,000 users per variant.

Technical Implementation with Expo

Setting Up Push Notifications

npx expo install expo-notifications expo-device
import * as Notifications from 'expo-notifications';
import * as Device from 'expo-device';

// Request permission
async function requestPermission() {
  if (!Device.isDevice) return false;

  const { status: existing } = await Notifications.getPermissionsAsync();
  if (existing === 'granted') return true;

  const { status } = await Notifications.requestPermissionsAsync();
  return status === 'granted';
}

// Get push token
async function getPushToken() {
  const token = await Notifications.getExpoPushTokenAsync({
    projectId: 'your-project-id',
  });
  return token.data;
}

// Store token on your server
async function registerForPushNotifications() {
  const granted = await requestPermission();
  if (!granted) return;

  const token = await getPushToken();
  await supabase
    .from('push_tokens')
    .upsert({ user_id: userId, token, platform: Platform.OS });
}

Sending from Your Server

// Using Expo's push notification service
async function sendPushNotification(
  pushToken: string,
  title: string,
  body: string,
  data?: Record<string, any>
) {
  await fetch('https://exp.host/--/api/v2/push/send', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      to: pushToken,
      title,
      body,
      data,
      sound: 'default',
    }),
  });
}

Common Mistakes

Sending Too Many Notifications

The number one mistake. When in doubt, send fewer. You can always increase frequency later, but you cannot un-annoy a user who already disabled notifications.

Generic Content

"Check out what is new!" tells users nothing and teaches them to ignore your notifications. Every notification should contain specific, actionable information.

No Deep Linking

Tapping a notification and landing on the home screen is frustrating. Always deep link to the relevant content.

Not Handling Edge Cases

What happens when a user taps a notification for content that has been deleted? Or for a feature that requires login? Handle these gracefully with fallback screens.

Ignoring Time Zones

Sending a "Good morning!" notification at 3 AM is worse than not sending one at all. Always convert to the user's local time.

The Notification Strategy Checklist

  • [ ] Pre-permission screen implemented
  • [ ] Notifications categorized (transactional, reminder, engagement, social, marketing)
  • [ ] Deep linking configured for all notification types
  • [ ] Frequency cap implemented
  • [ ] User preferences screen with granular toggles
  • [ ] Time zone handling in place
  • [ ] Rich notification support (images, action buttons)
  • [ ] Analytics tracking (opt-in, open rate, dismiss rate)
  • [ ] A/B testing framework ready

Treat every notification as a request for the user's attention. If the notification is not worth their attention, do not send it.

Ready to start building?