All skills

Frontend & Apps

expo-push-notifications

Implement Expo Push Notifications for React Native (Expo) frontend and Python backend. Use when adding push notifications to an Expo app, registering device tokens, sending push from a Python backend (Django, FastAPI), setting up expo-notifications on the frontend, handling foreground/background notification listeners, or debugging push delivery. Triggers on "push notifications", "expo notifications", "expo push token", "send push notification", "notification listener", "expo-notifications", "expo-server-sdk", "ExponentPushToken", "device token registration", "notification channel", "badge count", or "push receipt". Covers both iOS (APNs) and Android (FCM) via Expo's unified push service. No Firebase Admin SDK or raw APNs setup needed.

View raw .md →skills.sh →101 lines

Expo Push Notifications

Expo Push provides unified iOS + Android push delivery. One token format (ExponentPushToken[...]), one API, automatic APNs/FCM routing.

Architecture

Frontend (Expo)                    Backend (Python)                 Expo Push Service
─────────────────                  ────────────────                 ─────────────────
1. Request permission
2. Get ExponentPushToken ──POST──> 3. Store token in DB
                                   4. Send push via expo-server-sdk ──> 5. Route to APNs/FCM
6. Receive notification <──────────────────────────────────────────────
7. Handle tap → navigate
                                   8. Poll receipts (15 min later)  <── 9. Delivery confirmation

Quick Start

Frontend

npx expo install expo-notifications expo-device expo-constants

Create usePushNotifications hook, register token after login, handle notification taps.

See references/frontend-expo.md for complete hook, registration, logout cleanup, cold start handling, badge management, and local notifications.

Backend

uv add exponent_server_sdk

Create push utility module, validate tokens, send via PushClient, handle errors, poll receipts.

See references/backend-python.md for complete push module, Django/FastAPI integration, device model, registration endpoint, Celery delivery task, and tests.

Key Patterns

Token Validation

Use the SDK's built-in classmethod — reject raw FCM/APNs tokens:

from exponent_server_sdk import PushClient

PushClient.is_exponent_push_token("ExponentPushToken[xxx]")  # True
PushClient.is_exponent_push_token("raw-fcm-token")           # False

Error Handling

ErrorAction
DeviceNotRegisteredErrorDeactivate token in DB, do not retry
MessageTooBigErrorReduce payload, do not retry
MessageRateExceededErrorRetry with backoff
PushServerErrorRetry with backoff

Dead Token Cleanup

Deactivate tokens on DEVICE_NOT_REGISTERED errors (from send or receipts):

if error_code == 'DEVICE_NOT_REGISTERED':
    UserDevice.objects.filter(expo_push_token=token).update(is_active=False)

Receipt Polling

Poll 15+ minutes after sending to confirm delivery:

from push import check_receipts
receipts = check_receipts(ticket_ids)
# Deactivate devices where receipt says DeviceNotRegistered

Checklist

  • expo-notifications + expo-device installed in frontend
  • Android notification channel configured
  • iOS remote-notification background mode in app.json
  • EAS credentials configured (eas credentials -p ios and -p android)
  • usePushNotifications hook created and used in app root
  • Token registered with backend after login
  • Token deactivated on logout
  • exponent_server_sdk installed on backend (uv add exponent_server_sdk)
  • Token format validated before sending
  • DeviceNotRegisteredError deactivates tokens
  • Retry logic for transient errors
  • Receipt polling scheduled (every 15 min)
  • Tested on physical device (push does not work on simulators)