In order to increase our engagement we decided to implement a set of push notifications to help bring users back into our app. Up until now, we had not created a comprehensive push notification strategy. We had a single push notification job that runs everyday that tells users of an upcoming charge within the app (e.g. ‘you will be charged in the next few days. Open up the app to make changes to your plan.’). In practice this notification only hits a small segment of our userbase.
This original notification job was created on Heroku using the Heroku scheduler and a one-off dyno (Heroku parlance for an ephemeral computing resource) — similar to a cron job in a traditional server environment. The scheduler creates the dyno, runs your code, then shuts it down. For the actual sending of the push notifications we use Parse platform (which we originally built our app on).
When looking at our new push notification strategy, I wanted some flexibility in selecting how frequently our push jobs run. Unfortunately the Heroku Scheduler can only run jobs daily, hourly or every 10 minutes. For our new notification strategy we want the ability to send out notifications at many different intervals including the 1st of the month, the 14th of the month, every 4 hours. While we could have hacked it by running our job every day and killing it if it didn’t meet our criteria:
Another requirement we had was the ability to send a push notification to our entire userbase. While I think this could be achieved with Heroku and Parse, it requires issuing a query to retrieve all device tokens, iterating through the results and creating a push notification for each device. As our userbase increases, this strategy continues to eat up more and more resources on our db, server, etc. I haven’t done any benchmarking but I suspect that I would need to throttle our push notification requests — I think our server would be overwhelmed if we tried to send 50K+ push notification requests in the span of a few seconds.
With AWS Lambda and Amazon Simple Notification Service (SNS) both of these limitations can be overcome. Lambda jobs can be scheduled using Cron expressions which offer an extraordinary amount of flexibility (you can configure pretty much any interval you can think of). Additionally, SNS has the concept of a topic which devices can subscribe to. With topics you can send out a notification to all subscribers in one fell swoop. Furthermore, the actual push notifications are submitted through SNS infrastructure rather than my own EC2 instances running our server. Thus I wouldn’t have to worry about overloading our production server and potentially affecting users currently using the app.
How to Do It
- You have a development or production push certificate in P12 format. There are plenty of resources out there that show you how to create a push certificate at the Apple developer portal then export it as a P12 from Keychain Access (here, for instance).
- Familiarity with npm for installing and managing node.js modules.
1. Create Platform Application:
Navigate to the Simple Notifications Service home and click Applications in the left menu
Click on Create Platform Application when the page appears:
Next, you’ll need to create a name for your Application, select Apple production or Apple development for your Push notification platform and then upload the info from your certificate that you already have created (see prereqs). Select your certificate in the finder, enter the password if you exported it with one, then click Load credentials from file. Your certificate and private key should auto-populate.
Click create platform application. Once your application is created make a note of the Application ARN (Amazon Resource Name). We will use it in step 3.
2. Create SNS Topic:
Navigate to Topics in the left navigation menu, click create new topic. Fill out topic name and display name. In my case, I just want to create a push notification that goes to my entire userbase, so I created a topic named
all_app_users. Once you’ve created your topic make a note of the ARN. We will use it in step 3.
3. Configure Programmatic access
In our node.js code, we will need to configure the aws-sdk with a user that has proper permissions to both create an SNS endpoint and subscribe to our
all_app_users topic. Back in the AWS console navigate to IAM, then click on Users. Create a new user, choose a name and check off Programmatic access for the Access type, click Next: Permissions:
On the permissions screen click Attach existing policies, then Create policy:
On the IAM screen that pops up, select the Policy Generator. Then in Edit Permissions, choose Amazon SNS for AWS Service, check CreatePlatformEndpoint under the Actions field, then plug in the application ARN from step 1:
Click Add Statement, then repeat the steps above to create the policy that allows this user to create a subscription to the
all_app_users topic. Select Amazon SNS again for the AWS Service, select Subscribe for the Action, then plug in the SNS Topic ARN from step 2. Click Next Step, change the Policy Name to sns-programmatic-access then click Create Policy.
Back in the window where you’re creating the user, search for sns-programmatic-access (you may need to hit the refresh button), then click the checkbox next to it to select it. Click Next: Review, then click the button to finish creating the user. On the next screen you are shown your Access Key ID and Secret access key. Make a note of these credentials now as you will not be able to access the secret key again*. We will plug these credentials into our node.js code to configure our AWS client.
*If you end up losing your secret key you can create a new key via the IAM Management Console.
4. Register Users with Platform:
This part depends a bit on how you’re currently handling registering your device tokens. In our case, we are currently sending the device tokens to our server to store them in our db. To implement SNS we will need to create a platform endpoint on our SNS application that contains this device token, and then subscribe this device to our
To do this we will need the aws-sdk npm module:
npm install -save aws-sdk
In our code, let’s configure our AWS client with the credentials we created in the previous step:
When we receive a device token on our server first we need to create a platform endpoint on our application (be sure to plug in your application ARN from step 1).
And then create a subscription with the result returned from the above function (be sure to plug in your topic ARN from step 2):
Tying these two functions together we have a
With these bits of code, we should now be able to register all of our device tokens with SNS (i.e. as application endpoints) and subscribe them to our
5. Create and Configure Lambda Function to Publish Message
In our AWS console, navigate to the Lambda dashboard and click on Create Function. On the Select blueprint screen, click Author from scratch:
On Configure triggers screen, click Next. We will set up a trigger later after we verify that the function is working.
On the Configure function screen, we’ll need to set up a role with SNS:Publish permissions on our
all_app_users topic. Scroll down to Lambda function handler and role section. Under Role, select Create a custom role:
In the IAM window that opens, change the Role Name to whatever you want (I used notificationsRole). Then click Allow. Your Lambda role should now be set to the role that your just created. Finally, click Next then finish creating the function.
Now we just need to add SNS:Publish permissions using the Policy Generator. Navigate to IAM Management Console, click Roles in the left navigation menu and select the role that we just created (notificationsRole if you used that name). On the summary page for this role, select Create Role Policy under Inline Policies header:
Select Policy Generator on the next screen. This part should look familiar by now. Here we want to select Amazon SNS as the AWS Service, Publish as the Action and the topic ARN from step 2:
Click Add Statement, then click Next. Finally, add a name (if you want) and click Apply Policy. Now our role should be all set up to publish a message to our topic .
6. Write Lambda Function Code
The final bit, or course, is writing the function that publishes a message to our topic and it’s actually pretty simple. First, we’ll need the AWS SDK. In a local directory install it:
npm install -save aws-sdk
Next create a file called index.js. When Lambda runs our function, it looks for a function in index.js called handler. In our handler function all we need to do is publish our message to the topic ARN from before. My code looks like this:
Now upload to AWS: select our index.js file and the node_modules directory. Zip up those two files. Make sure that you don’t zip the directory containing these files — it’ll cause problems. On the dashboard for our Lambda function, under the Code tab switch the dropdown to Upload a .ZIP file. Upload the zip file that we just created.
Your Lambda function should now be able to send push notifications. Go ahead and test it from the function dashboard. Assuming you’ve registered at least one device, you should receive the push message on your device.
Bonus: Schedule the Job
On the Home page for the lambda job, click on the Triggers tab, then Add Trigger. Click the gray, dashed outline in the window to bring up the available triggers, select CloudWatch Events:
In the next screen, select Create a new rule in the drop-down, fill out name and optional rule description. For rule type, we’ll want to select Schedule expression and the cron expression (which I’ll describe below) then save the trigger (and enable it).
Cron expressions allow you to specify a schedule using a space-delimited string of values (or subexpressions):
cron(Minutes Hours Day-of-month Month Day-of-week Year)
I won’t go into too much detail on cron expressions as there’s plenty of good documentation provided by Amazon. My particular cron expression:
cron(0 17 1 * ? *) is saying run at 17:00 (UTC) on the first day of every month.
There you have it! With this code we were able to create new push notification functionality that provides a way for us to send notifications to our entire userbase and should be able to scale with us as we grow to a million users (and beyond). Although SNS is the ideal solution for this use case, we will still send some of our other new push notifications via our existing push service because we want finer-grained visibility into the user associated with this device.