Background worker to send Apple Push Notifications over a persistent TCP socket.
Ruby Other
Latest commit 56bb9c8 Oct 17, 2016 @arthurnn committed on GitHub Merge pull request #97 from lukeasrodgers/fix-sidekiq-compat
Update travis manifest so ruby <= 2.1 is tested with sidekiq 3.
Failed to load latest commit information.
contrib Tweak example god config - leave daemonizing to god Jan 11, 2012
generators Loading entire APN environment for sender daemon May 17, 2010
lib accept silent notifications as valid Mar 2, 2016
spec mark broken test as pending for now Jan 11, 2016
.document Initial commit to apple_push_notification. Apr 13, 2010
.gitignore Split Gemfiles [ci skip] Oct 16, 2013
.rspec removing test/* and add rspec May 4, 2013
.travis.yml Update travis manifest so ruby <= 2.1 is tested with sidekiq 3. Oct 13, 2016 Update changelog Jan 17, 2014
Gemfile Gemfile should use https Oct 16, 2013
Gemfile.resque Gemfile should use https Oct 16, 2013
Gemfile.sidekiq.3 Update travis manifest so ruby <= 2.1 is tested with sidekiq 3. Oct 13, 2016
Gemfile.sidekiq.3.1 Update travis manifest so ruby <= 2.1 is tested with sidekiq 3. Oct 13, 2016
LICENSE updating LICENSE Aug 26, 2013 Mention content-available in the README Jun 29, 2015
Rakefile Add `rake console` task to load the gem into pry. Apr 13, 2015
apn_sender.gemspec Remove date from gemspec Jan 11, 2016

Code Climate Build Status


Need to send background notifications to an iPhone application over a persistent connection in Ruby? Keep reading...

The Story

So you're building the server component of an iPhone application in Ruby and you want to send background notifications through the Apple Push Notification servers. This doesn't seem too bad at first, but then you read in the Apple Documentation that Apple's servers may treat non-persistent connections as a Denial of Service attack. Since Rails has no easy way to maintain a persistent connection internally, things start to look complicated.

This gem includes a background daemon which processes background messages from your application and sends them along to Apple over a single, persistent socket. It also includes the ability to query the Feedback service, helper methods for enqueueing your jobs, and a sample monit config to make sure the background worker is around when you need it.

Yet another ApplePushNotification interface?

Yup. There's some great code out there already, but we didn't like the idea of getting banned from the APN gateway for establishing a new connection each time we needed to send a batch of messages, and none of the libraries I found handled maintaining a persistent connection.

Current Status

This gem has been used in production, on 500px, sending hundreds of millions, if not, billions of notifications.


APN sender can use Resque or Sidekiq to send asynchronous messages, if none of them are installed it creates a new thread to send messages.

1. Use a background processor or not.

You can either use Resque or Sidekiq, I strongly advice using Sidekiq, as apn_sender uses a connection pool for the apple socks.

2. Queueing Messages From Your Application

To queue a message for sending through Apple's Push Notification service from your Rails application:

APN.notify_async(token, opts_hash)

Where token is the unique identifier of the iPhone to receive the notification and opts_hash can have any of the following keys:

  • :alert ## The alert to send
  • :badge ## The badge number to send
  • :sound ## The sound file to play on receipt, or true to play the default sound installed with your app
  • :content_available ## Lets the remote notification act as a "silent" notification.

If any other keys are present they'll be be passed along as custom data to your application.

3. Sending Queued Messages

Put your apn_development.pem and apn_production.pem certificates from Apple in your RAILS_ROOT/config/certs directory.

You also can configure some extra settings:

APN.root = 'RAILS_ROOT/config/certs' # root to certificates folder
APN.certificate_name = 'apn_production.pem' # certificate filename = 'apple host (on development sandbox url is used by default)'
APN.password = 'certificate_password'
APN.pool_size = 1 # number of connections on the pool
APN.pool_timeout = 5 # timeout in seconds for connection pool
APN.logger =, 'log', 'apn_sender.log'))
APN.truncate_alert = true # enable the truncation of the alert for notifications that exceed the max allowed bytes
APN.backend = :sidekiq # use sidekiq backend

Check logs/apn_sender.log for debugging output. In addition to logging any major errors there, apn_sender hooks into the Resque::Worker logging to display any verbose or very_verbose worker output in apn_sender.log file as well. On latest versions apn_sender will use Rails.logger as the default logger.

4. Checking Apple's Feedback Service

Since push notifications are a fire-and-forget sorta deal, where you get no indication if your message was received (or if the specified recipient even exists), Apple needed to come up with some other way to ensure their network isn't clogged with thousands of bogus messages (e.g. from developers sending messages to phones where their application used to be installed, but where the user has since removed it). Hence, the Feedback Service.

It's actually really simple - you connect to them periodically and they give you a big dump of tokens you shouldn't send to anymore. The gem wraps this up nicely -- just call:

 # APN::Feedback accepts the same optional :environment
 # and :cert_path / :full_cert_path options as APN::Sender
 feedback =

 tokens = feedback.tokens # Array of device tokens
 tokens.each do |token|
   # ... custom logic here to stop you app from
   # sending further notifications to this token

If you're interested in knowing exactly when Apple determined each token was expired (which can be useful in determining if the application re-registered with your service since it first appeared in the expired queue):

 items = # Array of APN::FeedbackItem elements
 items.each do |item|
   # ... custom logic here

The Feedback Service works as a big queue. When you connect it pops off all its data and sends it over the wire at once, which means connecting a second time will return an empty array, so for ease of use a call to either +tokens+ or +data+ will connect once and cache the data. If you call either one again it'll continue to use its cached version (rather than connecting to Apple a second time to retrieve an empty array, which is probably not what you want).

Forcing a reconnect is as easy as calling either method with the single parameter +true+, but be sure you've already used the existing data because you'll never get it back.

Warning: No really, check Apple's Feedback Service occasionally

If you're sending notifications, you should definitely call one of the receive methods periodically, as Apple's policies require it and they apparently monitor providers for compliance. I'd definitely recommend throwing together a quick rake task to take care of this for you (the whenever library provides a nice wrapper around scheduling tasks to run at certain times (for systems with cron enabled)).

Just for the record, this is essentially what you want to have whenever run periodically for you:

def self.clear_uninstalled_applications
  feedback_data = #> :production).data

  feedback_data.each do |item|
    user = User.find_by_iphone_token( item.token )

    if user.iphone_token_updated_at && user.iphone_token_updated_at > item.timestamp
      return true # App has been reregistered since Apple determined it'd been uninstalled
      user.update_attributes(iphone_token: nil, iphone_token_updated_at:

Keeping Your Workers Working

There's also an included sample apn_sender.monitrc file in the contrib/ folder to help monit handle server restarts and unexpected disasters.


Add this line to your application's Gemfile:

gem 'apn_sender', require: 'apn'

And then execute:

$ bundle

Or install it yourself as:

$ gem install apn_sender

To add a few useful rake tasks for running workers, add the following line to your Rakefile:

  require 'apn/tasks'


APN Sender is released under the MIT License.