Enabling Smart Notifications with Pusher and SendGrid

sendgrid_header_blog1.png

This article is part of Building Realtime Apps Tutorials series, updated on a regular basis. Notifications can be very useful, but they can also be annoying. Often I can be on a chat app, somebody sends me a message, and I get two notifications: one in the window, and another by email. The application has \[…\]

Introduction

This article is part of Building Realtime Apps Tutorials series, updated on a regular basis.

Notifications can be very useful, but they can also be annoying. Often I can be on a chat app, somebody sends me a message, and I get two notifications: one in the window, and another by email. The application has believed me to be offline when I’m actually online, and has gone ahead and sent me an email saying I was idle and I missed an important message.

No doubt it’s a common problem that damages many people’s user-experience, not to mention their mailbox. However, there is a very simple solution, and it can be implemented in no time at all.

Here it is: SendGrid‘s API makes it super-simple to send emails. Pusher’s API makes it super-simple to send realtime in-app notifications and see who’s online at a given time. By mixing the two, you can send a notification with Pusher if and only if they are online, otherwise you send an email using SendGrid.

Below is a visual representation of the solution:

sendgrid-pusher-flow.gif

This quick tutorial will show you how to achieve this three simple steps. To see it in action beforehand, check out our demo.

The Code

Step One: Setting Up

Taking Ruby and Sinatra as a simple example, we’re going to show you how to determine how to notify your users based on whether or not they are online. This works in other languages and frameworks too, but we’re just going to demonstrate the same high-level concept.

If you don’t already have an account, you can sign up to Pusher for free using this link. Likewise, you can sign up for a SendGrid account here. Do keep your Pusher and SendGrid application credentials to hand.

Let’s firstly install Pusher and SendGrid:

$ gem install pusher sendgrid-ruby

In our application, we can instantiate our Pusher and SendGrid instances:

1# For Pusher:
2Pusher.app_id = 'your-pusher-app-id'
3Pusher.key = 'your-pusher-key'
4Pusher.secret = 'your-pusher-secret'
5
6# For SendGrid:
7sendgrid_client = SendGrid::Client.new(api_user: "your-sendgrid-username", api_key: "your-sendgrid-password")

Step Two: Using User-Specific Channels

For those who are new to Pusher, channels are how we organize the realtime events we send. In this demonstration, we will name a Pusher channel unique to every user. This is firstly to demonstrate how we can send messages to a particular user alone, and secondly so that we can later use Pusher’s HTTP API to query a user’s online state. Note that on all of our plans, you can have an unlimited number of channels.

In the example of Ruby, ERB (embedded Ruby) makes it simple to embed the user’s ID into our client-side code, where we have the user subscribe to their own unique channel.

1var pusher = new Pusher('your_app_key');
2var channel = pusher.subscribe('private-' + '<%= current_user.id %>');

If a user with an id of 1 comes online, they will be subscribed to a channel called 'private-1'.

Note that we prefixed the channel’s name with 'private'. This is because we will be using private channels to ensure security. Private channels allow you to control who has access to the information in that channel by requiring authentication from your server. Upon attempting to subscribe, a POST request will be sent to /pusher/auth with the channel_name and socket_id as parameters. Using one of our libraries, this is simple to handle:

1post '/pusher/auth' do 
2  channel_name = params[:channel_name]
3  if channel_name == "private-#{current_user.id}"
4    Pusher[channel_name].authenticate(params[:socket_id]).to_json
5  else
6    halt 401
7  end
8end

This simply checks that the name of the channel asking for authentication is indeed the current_user‘s id prefixed with 'private'. If so, it sends back an authentication signature meaning all is OK. Otherwise, it sends a 401 error.

Step Three: Sending The Notification

Let’s take an example of sending a notification once your server receives a POST request – perhaps from a client or another origin – with a message key and its value, the notification message. With a given channel name, named after the current_user’s ID, we can use Pusher’s HTTP API to determine whether that channel is occupied or not; that is, if that user is in your application right now. If they are, we can just send the notification to that channel and that user will receive it.

The Pusher#channel_info method in this case will query our API and return {occupied: true} or {occupied: false} for a given channel. If the value of the occupied key is true, they are online and we send a Pusher notification. Otherwise we send a SendGrid email.

1post '/notification' do 
2  message = params[:message]
3
4  channel_name = "private-#{current_user.id}" 
5  channel = Pusher.channel_info(channel_name)
6
7    if channel[:occupied]
8      # send Pusher message
9      Pusher[channel_name].trigger('new_notification', {message: message})
10    else
11      # send SendGrid email
12      email = SendGrid::Mail.new do |m|
13          m.to      = current_user.email
14          m.from    = 'no-reply@example.com'
15          m.subject = "New Notification"
16          m.html    = message
17      end
18      sendgrid_client.send email
19    end
20    "sent!"
21end

And it’s as simple as that! Using user-specific channels and checking if the user is on them, you can easily escape that pesky overload that too often floods users with repetitive notifications.

Taking It From Here

Hopefully we managed to show you a simple case about how realtime can make important and significant improvements to user experience. There wasn’t much code nor business logic to it, meaning this can be easily added to any existing app at scale. Although I have demonstrated this in Ruby, we have a ton of libraries that make this integration painless, as do SendGrid. Of course, using Pusher means that you can scale this functionality to millions of users.

As for improvements, you could try:

  • Detecting if users have interacted with the notifications within a particular amount of time. If not, still send an email.
  • Managing the online/offline state using Pusher’s webhooks.
  • Sending an SMS via Twilio or native mobile push notifications via Pushwoosh if users are not in the application.

Feel free to let us know if this solution answers any issues in your apps and the experience it provides to your users, or indeed how you yourself would improve it!