How to send iOS and Android notifications from your Rails backend

Written for a project. I developed the backend for this social wellness mobile app - WellWith.me

One of the most common uses for a backend connected to a mobile application is to use it to send push notifications to users. Once you've wrapped your head around it, it's pretty straightforward, but it's also a common source of confusion for developers new to the topic. This frequent confusion is also the reason I've decided to cover it for my introductory technical story for WellWithMe, where I'm in charge of backend development.

Before I continue, you should know that there are plug-n-play services that will provide you with a notification-sending backend, if you're willing to pay for it (Parse, mobDB, Pushwoosh, Urban Airship, etc.), but since that's not the way of the warrior, let's see how you do it from scratch (and for free).

"If you wish to make an apple pie from scratch, you must first invent the universe." - Carl Sagan

There are several components in WellWithMe that play various roles in the notification sending feature:

  1. API endpoint for getting tokens from mobile devices

  2. Resque worker which remains connected to Apple/Google notification servers and feeds off a queue of notifications in Redis

  3. The code that does the actual sending and feedback processing in the worker

Before anything else, you need to ask the user if they'd like to receive push notifications (iOS notifications, Google Cloud Messaging) and if they say yes, get their device token and send it from the mobile device to the backend server. We store these tokens in a simple ActiveRecord model called Device:

# Schema Information
# Table name: devices
#  id         :integer          not null, primary key
#  user_id    :integer
#  token      :string(255)
#  enabled    :boolean          default(TRUE)
#  created_at :datetime         not null
#  updated_at :datetime         not null
#  platform   :string(255)

class Device < ActiveRecord::Base
  attr_accessible :enabled, :token, :user, :platform
  belongs_to :user
  validates_uniqueness_of :token, :scope => :user_id
end

Instances of Device get created when the mobile app calls an API endpoint, which looks something like this (we're using https://github.com/intridea/grape gem for our API needs):

resource :devices do
  post do
    @device = Device.create(user: current_user, token: params[:token], platform: params[:platform])
    present @device, with: WellWithMe::Entities::Device
  end
end

With our mobile app user having a stored device token, we're now ready to queue notifications for her, which we do through a simple Redis list backed Notification model, which ensures data validity among other things. If a user has multiple devices, the Notification model also ensures they get sent to all of them. Queuing notifications is then as easy as:

notification = Notification.new(user,
  "#{activity.user.name} just started the challenge!",
  'status_change'
 )

In an essence, the Notification model is a Redis list, which serves as the queue for a background worker (NotificationSender):

class NotificationSender
  @queue = :notifications

  def self.perform
    @list = Redis::List.new(Notification.key_name)
    while notification = @list.pop do
      notification_json = JSON.parse(notification)
      if notification_json['platform'] == 'iOS'
        note = Grocer::Notification.new(
          device_token: notification_json['token'],
          alert: notification_json['message'],
          sound: 'default',
          badge: 0
        )

        PUSHER.push(note)
      elsif notification_json['platform'] == 'Android'
        gcm = GCM.new(ENV['gcm_key'])
        registration_id = [notification_json['token']]
        options = {
          'data' => {
            'message' => notification_json['message']
          },
            'collapse_key' => 'updated_state'
        }
        response = gcm.send_notification(registration_id, options)
      end
    end
  end
end

Having NotificationSender be a queued job constantly running on a worker as opposed to a synchronous connection has the advantage of not trying to establish a connection to Apple's notification servers for every notification, which is something Apple actively discourages: Apple's note about notification servers connections.

Thus NotificationSender is a Resque job, which is run every minute and just gobbles up the Redis list of notifications, and sends them according to the platform. We're using the awesome grocer gem for iOS and the GCM gem for Android. Both are working perfectly and the documentation is very good. The only caveat is that you should watch out for certificates magic in iOS, as you need to have your Apple certificates on the server as well, and you need to export them in a certain way (including the private key) - just follow instructions for the grocer gem to the letter, and you'll be fine.

With this, you should have a working and easily extendible system for sending important notifications about in-app events to your users. Notifications can drastically increase user retention, just don't abuse them or they'll have an opposite effect.

WellWithMe is an awesome app that helps you stay healthy with a little help from your friends! Stay tuned and follow us @WellWithMeApp.

Mastodon