Use Expo's push tickets & receipts to enqueue retries for each failed push ticket.
Use cases
User has revoked push notification permissions
Expo has failed to deliver a push notification (server is down or something..)
Workflow:
Send some push notifications assuming everything's okay:
Batch requests for push notifications
Send push notifications
Store response of push tickets / status in firebase for the batch, starting the retry count at 0
Retry and handle errors at intervals (30 mins, 1 hour?):
Retrieve persisted response of push tickets / status from firebase (note that this is per-batch, so at most 100 tickets)
Do a check against Expo's API for the current status of the push
Enumerate and re-batch the push notifications
Update the response of push tickets / status in firebase, incrementing the retry count by 1 - ensure that successful pushes are removed from the batch
For any failed pushes that have been retried more than N times, remove the user's push token from their profile, assuming that they have not received the push
Notes
During the retry phase, is it wise to check whether the user has turned off notifications?
If we support multiple push intervals per day, how will we handle this?
Do we definitely want to remove the users push token from their profile if they fail to receive a push? Might be that their phone is turned off etc. Should check what the failure actually represents
Should we throw away batch data, or event drive it using something like firebase-queue?
Alternative architecture
Event-driven using a queue persisted in Firebase with cloud functions. Pseudo API for this:
type PushyOptions = {
/**
* Name of the firebase table to use to persist event state
*/
tableName: 'push-notifications-events',
/**
* Number of times to retry sending before stopping.
*/
retryAttempts: 3,
/**
* Callback fired when any push attempt contains failures.
*/
onAttemptFail: (failures, req, res) => {},
/**
* Callback fired when any push attempt is successful.
*/
onAttemptSuccess: (successes, req, res) => {},
/**
* Specific event handler callback for when an entire job has failed to be
* sent.
* NB: can be used to remove user's PN token
*/
onJobFail: (failures, req, res) => {},
/**
* Specific event handler callback for when an entire job has successfully
* been sent.
*/
onJobSuccess: (successes, req, res) => {},
/**
* Low level callback fired when any event handling starts.
* Can be used to intercept the event to modify it before it's handled.
* Must return the event.
*/
onEventStarted: (event) => {},
/**
* Low level callback fired when any event has been handled.
* Can be used to log or fire other events.
* Must return void
*/
onEventFinished: (event) => {},
}
type PushyAPI = {
/**
* Creates a push notification job and sends the initial batch of
* notifications.
*/
push: (notifications: any[]) => Promise<void>;
/**
* Method to retry any unsuccessful push notifications. If a job is already at
* the maximum retry count, it will dispatch a failed event
*/
retry: () => Promise<void>;
}
/**
* Factory function to make an instance of Pushy
*/
type Pushy = (options: PushyOptions) => PushyAPI
Use Expo's push tickets & receipts to enqueue retries for each failed push ticket.
Use cases
Workflow:
Send some push notifications assuming everything's okay:
Retry and handle errors at intervals (30 mins, 1 hour?):
Notes
Alternative architecture
Event-driven using a queue persisted in Firebase with cloud functions. Pseudo API for this: