Saturday, December 1, 2012

Push Notifications to Apple, made... almost reliable!

For one of my projects I need to push informations to iOS devices. Apple has made a great doc on how to implement push notifications but as a matter of fact the ideal world of the documentation is not the one most developers experiment when dealing with push notifications to Apple's servers.

The thing that bothers most of us is the lack, or at least the manner, of feedback when a notification is sent. That is asynchro-loosly ;)


I understand that it is better from Apple's point of view to manage the feedbacks asynchronously but it can be a pain in the *** when you have to deal with it in order to provide a reliable service to your customers. But Hey! Apple stated it's not a reliable communication stream... Ye be warned!

The thing that puzzles me most is not that the communication between Apple and the device is a best effort kind of deal. Fine! What is really unpleasant is that the communication between the service provider and Apple is also very un-reliable with regards to the current spec.

When one pushes to Apple a valid payload, at least from the service provider point of view, it cannot be sure that the notification would even pass the first sanity filter at Apple. One has to keep in mind that this notification payload could be rejected later. And as stated by Apple's spec, in case of a bad payload the connection will be silently closed after sending a error-response. Since this response is asynchronous, you may have sent other payloads between the bad payload and the arrival of the error response. Moreover, Apple does not provide any max delay between a received payload and the error-response. So basically they say they could respond later on that the payload sent before (a long time ago?) was indeed invalid and that they trashed all other payloads since then.

From a communication point of view, this is a lack of bandwidth. From the service provider point of view it's a lack of consistency.

EDIT (July 2013):
As a matter of fact, it seams to be stated by Apple on it's doc that this kind of behavior is general to socket services and that this async behavior is designed for best performances. However, I must confess that my understanding of this topic has evolved a bit and I am now convinced that you really can deal with it without too much trouble. See the end of the document.

Plan A: "Apple, please..!" 

Plan B.

Ok let's now focus on one possible solution at the service provider level.

It assumes all notifications are enhanced notifications (with UID) and also that notifications' UID are incremental (1, 2, 3,...etc..)

Connector pseudo-code

(This code is responsible for managing the connection to Apple and sending notifications)
When a notification has to be sent
- make a connection to Apple if needed
- send the notification

When an error is reported by Apple
- emit a 'payload-error' with the UID of the notification

Consumer pseudo-code

While notifications have to be sent 
- send notification to Apple (via Connector)
- emit a 'sent' message with the notification as argument

Producer pseudo-code

- maintain a unconfirmed notifications list (default: empty list)

When a 'sent' is emitted
- add the notification to the unconfirmed list

While allowed to submit AND new notifications have to be sent 
- check notification validity
- submit notification to the Consumer queue

When an 'payload-error' is emitted
- Disallow Producer's submissions
- Process the error (black list tokens etc... for validity check in Producer submission loop)
- Remove the unconfirmed notifications with UID lesser or equal to the erroneous UID 
- Re-submit to the Producer unconfirmed notifications with UID greater that the erroneous UID so that they will be re-submitted to the Consumer after being checked for validity
- Allow Producer's submissions

If no more notifications need to be submitted by the Producer for a PERIOD seconds
- clear unconfirmed list
- report to the customer that it's all done!

How does it work? 

When a notification needs to be sent it follows this workflow:

Producer -> Consumer -> Connector

The Producer submits a valid notification to the Consumer.
When the Consumer did send the notification via the Connector it sends a 'sent' message, the Producer adds the notification to its unconfirmed list.

When an 'payload-error' message happens, the notifications prior to the erroneous notification are removed from the unconfirmed list, the remaining notifications are re-processed by the Producer.

When all of the Producer's notifications have been sent and enough time has passed (?) then, consider that Apple has accepted all the notifications, clear the unconfirmed list.

Rem: there may be more than one Producer, each of which will maintain its own set of unconfirmed notifications.

Final word

I have no open source code implementing this solution to show you yet, but, I plan to release a nodejs example. Know that it uses node_apns a node package of mine handling On demand Push notification connections. You may find it here on may github profile
Although it may not be the best method it has proven to be reliable for me. Anyways, I'm open to any suggestions. Feel free to comment.


Edited (July 2013)

I have now a slightly reworked open source code to show you.
You may find it on my github page here.
It has been added to the node_apns module as a service. The key object is services.Notifier. It takes care of everything and makes notifications delivery very robust and efficient.

One thing to note though, you should always set the expiry of notifications (services.Notifier uses enhanced notifications to make reliable deliveries) to an appropriate value so that the devices that do not have working persistent connexion to the push service at the moment of the send will get the chance to cache it later when they rebuild their connexion.

Cheers


4 comments:

  1. Nice post with great details. I really appreciate your info. Thanks for sharing.

    emergency mass notification software

    ReplyDelete
    Replies
    1. Thanks for the feedback James. However, I have now released a slightly reworked implementation for this process to make reliable notifications based on Apple's best practices. You may find it on github here: https://github.com/Orion98MC/node_apns
      The key object is services.Notifier which takes care of all the heavy lifting and stuff to make notifications a breeze. So far I have it in production with no delivery problems. It it worth to note also that when one makes enhanced notifications, one should always set the expiry to an epoch greater than zero. Zero is the default and it means no storage on Apple's servers, so if a notification cannot find it's way to the device at the time it is sent by the provider, then it is lost forever for the particular device. On contrary when the expiry is set at the right threshold the device will probably have a second chance to cache it later when it rebuilds it's persistent connexion to the push service.

      Delete
  2. Your post for push notification to apple made.. almost reliable are nice.In this post Apple does not provide any max delay between a received payload and the error-response.
    Thanks for this.
    For more information please visit here apple push notification

    ReplyDelete
  3. hi! catapush does it the reliable way using retry mechanism and expiration dates. ceers!

    ReplyDelete