● 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.
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
| Error | Action |
|---|---|
DeviceNotRegisteredError | Deactivate token in DB, do not retry |
MessageTooBigError | Reduce payload, do not retry |
MessageRateExceededError | Retry with backoff |
PushServerError | Retry 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-deviceinstalled in frontend - Android notification channel configured
- iOS
remote-notificationbackground mode in app.json - EAS credentials configured (
eas credentials -p iosand-p android) -
usePushNotificationshook created and used in app root - Token registered with backend after login
- Token deactivated on logout
-
exponent_server_sdkinstalled on backend (uv add exponent_server_sdk) - Token format validated before sending
-
DeviceNotRegisteredErrordeactivates tokens - Retry logic for transient errors
- Receipt polling scheduled (every 15 min)
- Tested on physical device (push does not work on simulators)