Sean Carpenter

iOS Push Notifications With Amazon SNS

Amazon SNS provides a cost-effective way to send push notifications to mobile devices. This is especially true for the relatively low push volume generated by something like the Roku sleep timer in Romote. Using SNS for mobile push is also supported well by the Ruby aws-sdk.

To get started using SNS, you first need to create an app in SNS to represent your application. The Amazon documentation is pretty easy to follow - you will need the APNS certificate and private key from Apple for setting up push to iOS devices.

Registering a Device

Once you have an app created, you can start registering devices with it. This is accomplished using the CreatePlatformEndpoint API and requires the device token for the iOS device. I make the registration call from the application:didRegisterforRemoteNotificationsWithDeviceToken: callback in my application’s delegate.

Aside

I proxy all of the calls to SNS through a server that I run, so devices never communicate directly with SNS. This allows me to not expose any SNS credentials in my iOS apps as well as gives me the flexibility to change the communication with SNS (or remove SNS completely) without resubmitting the iOS apps.

Registering a device is easy using the Ruby aws-sdk:

Register a Device
1
2
3
4
5
6
client = AWS::SNS.new.client
response = client.create_platform_endpoint(
  platform_application_arn: "application-id-from-sns",
  token: params[:deviceToken]
)
endpoint_arn = response[:endpoint_arn]

The endpoint_arn can then be sent back to the device to use in future push notifications (or stored for later use at the server).

Sending a Push Notification

Once a device is registered, you can send direct push notifications to that device. This is done using the Publish API. The Publish API supports sending a different message to each supported SNS transport as well as specifying a default message; when using device-specific mobile push notifications this isn’t necessary as each device will only be using a single transport (APNS or APNS_SANDBOX is the case of iOS).

SNS supports specifying all of the keys that Apple expects in a push notification using transport-specific messages. This requires setting the MessageStructure parameter of the Publish call to “json” and supplying a JSON formatted object in the Message parameter. One thing the documentation doesn’t make clear is that these need to be JSON-encoded strings and not raw JSON. An example will make this clearer:

Sending a Push
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
message = {
  "APNS" => {
    "aps" => { "content-available" => true },
    "other_data" => "some_data"
  }.to_json,
  "APNS_SANDBOX" => {
    "aps" => { "content-available" => true },
    "other_data" => "some_data"
  }.to_json
}
push_parameters = {
  target_arn: "endpoint_arn_from_registration"
  message_structure: "json",
  message: message.to_json
}

client = AWS::SNS.new.client
response = client.publish(push_parameters)

Notice the to_json calls on lines 5, 9, and 14. Each transport key needs to be a JSON string inside the Message parameter. Additionally, the Message parameter itself needs to be a JSON string. Getting this wrong (by having actual JSON instead of a JSON string) leads to SNS not being able parse the Message parameter and generates an error of “InvalidParameter: Invalid parameter: JSON must contain an entry for ‘default’ or ‘APNS_SANDBOX’”.

The value of the other_data key is available in the userInfo dictionary provided to the application:didReceiveRemoteNotification: callback in your iOS application:

Handling the Notification
1
2
3
-(void) application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
    NSString* otherData = [userInfo objectForKey:@"other_data"];
}

Summary

Using just a few straightforward calls, it’s possible to send push notifications to your iOS apps. SNS also supports other mobile platforms (including Google Cloud Messaging and Amazon Device Messaging) as well as broadcasting messages to subscribed devices (using topics) instead of targeting individual devices.