View Source APNS (Apple iOS)

Usage

  1. Set your environment variables. See below for setting up your certificate and key.

     config :pigeon, :apns,
       apns_default: %{
         cert: "cert.pem",
         key: "key_unencrypted.pem",
         mode: :dev
       }

    This config sets up a default socket connection to send to APNS servers. cert and key can be any of the following:

    • Static file path
    • Full-text string of the file contents (useful for environment variables)
    • {:my_app, "certs/cert.pem"} (indicates path relative to the priv folder of the given application)

    Alternatively, you can use token based authentication:

     config :pigeon, :apns,
       apns_default: %{
         key: "AuthKey.p8",
         key_identifier: "ABC1234567",
         team_id: "DEF8901234",
         mode: :dev
       }
    • :key - Created and downloaded via your developer account. Like :cert this can be a file path, file contents string or tuple
    • :key_identifier - The 10-character key identifier associated with :key, obtained from your developer account
    • :team_id - Your 10-character Team ID, obtained from your developer account
  2. Create a notification packet. Note: Your push topic is generally the app's bundle identifier.

     n = Pigeon.APNS.Notification.new("your message", "your device token", "your push topic (optional)")
  3. Send the packet. Pushes are synchronous and return the notification with an updated :response key.

     Pigeon.APNS.push(n)

Notification Struct

The contents of payload is what will be received on the iOS device. If updating this field directly, use strings for your keys. It is recommended to use the convenience functions defined in Notifications with Custom Data. expiration is a UNIX epoch date in seconds (UTC). Passing a value of 0 expires the notification immediately and Apple will not attempt to redeliver it.

  %Pigeon.APNS.Notification{
    collapse_id: String.t() | nil,
    device_token: String.t() | nil,
    expiration: non_neg_integer | nil,
    id: String.t() | nil,
    payload: %{String.t() => String.t()},
    response: atom,
    topic: String.t() | nil
  }

Generating Your Certificate and Key .pem

  1. In Keychain Access, right-click your push certificate and select "Export..."

  2. Export the certificate as cert.p12

  3. Click the dropdown arrow next to the certificate, right-click the private key and select "Export..."

  4. Export the private key as key.p12

  5. From a shell, convert the certificate.

      openssl pkcs12 -legacy -clcerts -nokeys -out cert.pem -in cert.p12
  6. Convert the key. Be sure to set a PEM pass phrase here. The pass phrase must be 4 or more characters in length or this will not work. You will need that pass phrase added here in order to remove it in the next step.

      openssl pkcs12 -legacy -nocerts -out key.pem -in key.p12
  7. Remove the PEM pass phrase from the key.

      openssl rsa -in key.pem -out key_unencrypted.pem
  8. cert.pem and key_unencrypted.pem can now be used as the cert and key in Pigeon.push, respectively. Set them in your config.exs

Notifications with Custom Data

Notifications can contain additional information in payload. (e.g. setting badge counters or defining custom sounds)

  import Pigeon.APNS.Notification
  n = Pigeon.APNS.Notification.new("message", "device token", "push topic")
  |> put_badge(5)
  |> put_sound("default")
  |> put_content_available
  |> put_mutable_content
  |> put_category("category")

Using a more complex alert dictionary?

  n
  |> put_alert(%{
    "title" => "alert title",
    "body" => "alert body"
  })

Define custom payload data like so:

  n
  |> put_custom(%{"your-custom-key" => %{
      "custom-value" => 500
    }})

Custom Worker Connections

Multiple APNS worker connections can be configured simultaneously. Useful for supporting multiple apps and/or certificates at once.

  config :pigeon, :apns,
    default: %{
      cert: "cert.pem",
      key: "key_unencrypted.pem",
      mode: :dev
    },
    custom_worker: %{
      cert: "another_cert.pem",
      key: "another_key_unencrypted.pem",
      mode: :prod
    }

Send pushes with a to option in your second parameter.

  n = Pigeon.APNS.Notification.new("message", "device token", "push topic")
  Pigeon.APNS.push(n, to: :custom_worker)

You can also start connections manually.

  iex> {:ok, pid} = Pigeon.APNS.start_connection(cert: "cert.pem",
  ...> key: "key.pem", mode: :dev)
  iex> Pigeon.APNS.push(notif, to: pid)

  iex> Pigeon.APNS.start_connection(cert: "cert.pem", key: "key.pem",
  ...> mode: :dev, name: :custom)
  iex> Pigeon.APNS.push(notif, to: :custom)

Asynchronous Pushing

  1. Pass an on_response option with an anonymous function in your second parameter.

     n = Pigeon.APNS.Notification.new("message", "device token", "push topic")
     Pigeon.APNS.push(n, on_response: fn(x) -> IO.inspect(x) end)
  2. Responses return a notification with an updated :response key. You could handle responses like so:

     handler = fn(%Pigeon.APNS.Notification{response: response}) ->
       case response do
         :success ->
           Logger.debug "Push successful!"
         :bad_device_token ->
           Logger.error "Bad device token!"
         _error ->
           Logger.error "Some other error happened."
       end
     end
    
     n = Pigeon.APNS.Notification.new("message", "device token", "push topic")
     Pigeon.APNS.push(n, on_response: handler)

Error Responses

Taken from APNS Provider API

ReasonDescription
:bad_collapse_idThe collapse identifier exceeds the maximum allowed size
:bad_device_tokenThe specified device token was bad. Verify that the request contains a valid token and that the token matches the environment.
:bad_expiration_dateThe apns-expiration value is bad.
:bad_message_idThe apns-id value is bad.
:bad_priorityThe apns-priority value is bad.
:bad_topicThe apns-topic was invalid.
:device_token_not_for_topicThe device token does not match the specified topic.
:duplicate_headersOne or more headers were repeated.
:idle_timeoutIdle time out.
:missing_device_tokenThe device token is not specified in the request :path. Verify that the :path header contains the device token.
:missing_topicThe apns-topic header of the request was not specified and was required. The apns-topic header is mandatory when the client is connected using a certificate that supports multiple topics.
:payload_emptyThe message payload was empty.
:topic_disallowedPushing to this topic is not allowed.
:bad_certificateThe certificate was bad.
:bad_certificate_environmentThe client certificate was for the wrong environment.
:expired_provider_tokenThe provider token is stale and a new token should be generated.
:forbiddenThe specified action is not allowed.
:invalid_provider_tokenThe provider token is not valid or the token signature could not be verified.
:invalid_push_typeThe apns-push-type value is invalid.
:missing_provider_tokenNo provider certificate was used to connect to APNs and Authorization header was missing or no provider token was specified.
:bad_pathThe request contained a bad :path value.
:method_not_allowedThe specified :method was not POST.
:unregisteredThe device token is inactive for the specified topic.
:payload_too_largeThe message payload was too large. The maximum payload size is 4096 bytes.
:too_many_provider_token_updatesThe provider token is being updated too often.
:too_many_requestsToo many requests were made consecutively to the same device token.
:internal_server_errorAn internal server error occurred.
:service_unavailableThe service is unavailable.
:shutdownThe server is shutting down.