Prompt
ChatGPT, I’m trying to set up push notifications for iOS, Android, and web in one backend. What architecture should I use?
Latest observation
A good way to do this is to treat push as a unified backend service with channel-specific adapters for APNs, FCM, and Web Push.
Recommended architecture
1) Client apps register a push token/subscription
Each platform generates a different identifier:
- iOS: APNs device token
- Android: FCM registration token
- Web: Push subscription endpoint + keys
Your apps send that data to your backend along with:
- user ID
- device ID
- platform
- app version
- locale/timezone
- notification preferences
- token/subscription metadata
Store these in a central device registry table/collection.
2) Backend has a “Notification Service”
Create one backend service responsible for:
- deciding who should be notified
- composing notification payloads
- applying user preferences / quiet hours / deduplication
- routing to the correct push provider
- tracking delivery attempts and failures
Think of it as:
Event → Notification logic → platform adapter → provider
3) Use provider adapters behind one internal interface
Build a common internal interface like:
sendToIOS(token, payload)sendToAndroid(token, payload)sendToWeb(subscription, payload)
Then implement:
- APNs adapter for iOS
- FCM adapter for Android
- Web Push adapter for browsers
This keeps your app code from dealing with provider details directly.
4) Put sending on a queue
Do not send push synchronously from your API request.
Use a queue/job system:
- SQS, RabbitMQ, Kafka, Redis Queue, Celery, BullMQ, Sidekiq, etc.
Flow:
- business event occurs
- backend creates notification job
- worker processes job
- worker sends to push provider
- delivery status/failure is recorded
This gives you:
- retries
- rate limiting
- batching
- resilience
- async processing
5) Keep a notification preference layer
Before sending, evaluate:
- per-user opt-in/out
- per-channel preferences
- categories/topics
- quiet hours
- region/timezone rules
- device-level disablement
- marketing vs transactional rules
This is best handled in your backend, not in the push provider.
6) Normalize payloads, but map them per platform
Have one internal notification model:
- title
- body
- deep link
- image
- sound
- badge count
- category/action buttons
- data payload
Then map it per platform:
- APNs notification fields
- FCM Android/iOS-compatible fields
- Web Push notification fields
Avoid trying to send the exact same JSON to all providers unchanged.
Suggested high-level components
Core services
- Notification API
- registers devices
- updates preferences
- receives event triggers
- Notification Orchestrator
- selects audience
- builds notification jobs
- Queue / Worker system
- async sending and retries
- Push Provider Adapters
- APNs
- FCM
- Web Push
- Device Registry
- tokens/subscriptions per user/device/platform
- Delivery Log / Analytics
- sent, failed, invalid token, bounced, opened
- Preference Store
- opt-in/opt-out, topics, quiet hours
Data model suggestion
devices
iduser_idplatform(ios,android,web)push_token/subscription_jsonapp_idis_activelast_seen_atcreated_atupdated_at
notification_jobs
idtypeuser_idor audience infopayloadstatusscheduled_atcreated_at
delivery_attempts
idnotification_job_iddevice_idproviderstatuserror_codeattempt_numbersent_at
Important implementation notes
Token lifecycle
Handle:
- token refresh
- token invalidation
- unsubscribe
- expired web subscriptions
Automatically mark stale/invalid tokens inactive when providers return errors.
Deduplication
Users often have multiple devices. Decide whether to:
- send to all active devices
- send once per user per event
- suppress duplicates within a time window
Environment separation
Use separate credentials and endpoints for:
- dev
- staging
- production
Security
- authenticate device registration calls
- never trust client-supplied user identity without auth
- encrypt tokens/subscriptions at rest if possible
Analytics
Track:
- sent
- provider accepted
- failed
- clicked/opened
- opt-outs
This is important for debugging and deliverability.
If you want the simplest version
If you don’t want to build everything yourself, use a provider abstraction layer like:
- Firebase Cloud Messaging for Android and web push support
- APNs directly for iOS or via FCM if your architecture supports it
- or a third-party service like OneSignal, Expo, Braze, Airship, Amazon SNS, etc.
But if you’re building your own backend, the cleanest long-term design is:
Single notification service + device registry + queue + provider adapters
My recommendation
For most products:
- Backend API stores device tokens/subscriptions
- Queue-based notification workers process send jobs
- Adapter pattern isolates APNs/FCM/Web Push differences
- Central preference and analytics layer handles product logic
If you want, I can also sketch:
- a concrete database schema,
- a sequence diagram, or
- sample code for Node.js / Python / Go.