{"id":13836826,"url":"https://github.com/inaka/apns4erl","last_synced_at":"2025-05-15T11:08:50.740Z","repository":{"id":1151595,"uuid":"1035547","full_name":"inaka/apns4erl","owner":"inaka","description":"Apple Push Notification Server for Erlang","archived":false,"fork":false,"pushed_at":"2024-07-31T13:56:53.000Z","size":483,"stargazers_count":368,"open_issues_count":25,"forks_count":215,"subscribers_count":78,"default_branch":"master","last_synced_at":"2025-04-11T20:00:01.555Z","etag":null,"topics":["apns","apple","erlang","hacktoberfest","notifications"],"latest_commit_sha":null,"homepage":"http://inaka.github.com/apns4erl","language":"Erlang","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/inaka.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2010-10-29T17:48:38.000Z","updated_at":"2024-11-04T23:52:35.000Z","dependencies_parsed_at":"2024-11-20T06:15:14.677Z","dependency_job_id":null,"html_url":"https://github.com/inaka/apns4erl","commit_stats":{"total_commits":92,"total_committers":18,"mean_commits":5.111111111111111,"dds":0.5760869565217391,"last_synced_commit":"18c68aadc1d921ddf47e5a663895b8b204fce437"},"previous_names":[],"tags_count":18,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/inaka%2Fapns4erl","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/inaka%2Fapns4erl/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/inaka%2Fapns4erl/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/inaka%2Fapns4erl/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/inaka","download_url":"https://codeload.github.com/inaka/apns4erl/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254328385,"owners_count":22052632,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["apns","apple","erlang","hacktoberfest","notifications"],"created_at":"2024-08-04T15:00:55.218Z","updated_at":"2025-05-15T11:08:50.720Z","avatar_url":"https://github.com/inaka.png","language":"Erlang","funding_links":[],"categories":["Erlang"],"sub_categories":[],"readme":"\nApns4erl v2 [![Build Status](https://github.com/inaka/apns4erl/workflows/build/badge.svg)](https://github.com/inaka/apns4erl)[![codecov](https://codecov.io/gh/inaka/apns4erl/branch/master/graph/badge.svg)](https://codecov.io/gh/inaka/apns4erl)\n========\n\n\u003cimg src=\"https://media.giphy.com/media/uZQP0PR0BmkGA/giphy.gif\" align=\"right\" style=\"float:right\" height=\"400\" /\u003e\n\nThis lib is intended to allow you to write an APNs provider for [Apple Push Notificaion services (APNs)](https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/APNSOverview.html) over HTTP2 in Erlang.\n\nCopyright (c) 2017 Erlang Solutions Ltd. \u003csupport@inaka.net\u003e, released under the Apache 2 license\n\nYou can find the v1 [here](https://github.com/inaka/apns4erl/releases/tag/1.0.6-final)?\n\n__Note:__ `Apns4erl v2` is still under development. Currently it supports push notifications with certificate and authentication token.\n\nContact Us\n==========\nIf you find any **bugs** or have a **problem** while using Apns4erl, please [open an issue](https://github.com/inaka/apns4erl/issues/new) in this repo (or a pull request :)).\n\n## Requirements\n- You must have installed an updated Openssl version or, at least, be sure it supports TLS 1.2+. New APNs server only supports connections over TLS 1.2+.\n- Erlang R19+\n\n## Important Links\n\n- [Pool of connections Example](examples/apns_pool/README.md)\n\n## How to use it?\n\nFirst we have to fill our `config` data. There are two ways for do this, one is filling a `config` file. This is an example you can find at `test/test.config`:\n\n```erlang\n{\n  apns,\n  [ {apple_host,       \"api.development.push.apple.com\"}\n  , {apple_port,       443}\n  , {certfile,         \"priv/apns-dev-cert.pem\"}\n  , {keyfile,          \"priv/apns-dev-key-noenc.pem\"}\n  , {token_keyfile,    \"priv/APNsAuthKey_KEYID12345.p8\"}\n  , {timeout,          10000}\n\n  %% APNs Headers\n\n  , {apns_id,          undefined}\n  , {apns_expiration,  0}\n  , {apns_priority,    10}\n  , {apns_topic,       \"com.example.myapp\"}\n  , {apns_collapse_id, undefined}\n  , {apns_push_type,   \"alert\"}\n\n  %% Feedback\n  , {feedback_host,    \"feedback.push.apple.com\"}\n  , {feedback_port,    2195}\n  ]\n  ]\n}\n```\n\nThe other way is send all that info as a parameter to `apns:connect/1` function encapsulated in a `apns_connection:connection()` structure:\n\n```erlang\n#{ name       := name()\n , apple_host := host()\n , apple_port := inet:port_number()\n , certfile   =\u003e path()\n , keyfile    =\u003e path()\n , timeout    =\u003e integer()\n , type       := type()\n }.\n```\n\nAPNs allows two connection types, one is using `Provider Certificates`. The first certificate option is to supply cert paths in `certfile` and `keyfile`. Alternatively, you can supply a cert binary in `certdata` and a `keydata()`-type tuple (see: https://github.com/inaka/apns4erl/blob/master/src/apns_connection.erl#L64) in `keydata`. Certs are the `Provider Certificates` and the keys are the `Private Key` both provided by Apple. We need them in `.pem` format, here is an example of how to convert them, check the [certificates](https://blog.serverdensity.com/how-to-build-an-apple-push-notification-provider-server-tutorial/) section.\n\nThe other way to connect against APNs is using `Provider Authentication Tokens`, for this choice you must fill the field `token_keyfile`. This is a path to the Authentication Key provided by Apple. This is in `.p8` format and it doesn't need conversion.\n\nThis `key` will be needed in order to generate a token which will be used every time we try to push a notification. In connection's time it is not needed.\n\n## Run\n\n`apns4erl` can be included as a dependency and started from `yourapp.app.src`. You also can run it on the shell for testing.\n\n```\n\u003e rebar3 compile\n\u003e erl -pa _build/default/lib/*/ebin -config test/test.config\n```\nDon't forget your config file if you want to use `apns:connect/2`.\n```erlang\n1\u003e apns:start().\nok\n```\n\n## Create connections\n\nAfter running `apns4erl` app we can start creating connections. As we mentioned before there are two types of connections. Both are created using the functions `apns:connect/1` and `apns:connect/2`.\n\n- `apns:connect/1`: This function accepts as a parameter an `apns_connection:connection()` structure.\n  ```erlang\n  #{ name       := name()\n   , apple_host := host()\n   , apple_port := inet:port_number()\n   , certdata   =\u003e binary()\n   , certfile   =\u003e path()\n   , keydata    =\u003e keydata()\n   , keyfile    =\u003e path()\n   , timeout    =\u003e integer()\n   , type       := type()\n   }.\n  ```\n  where the `type` field indicates if is `certdata`, `cert`, or `token`.\n\n- `apns:connect/2`: The first argument is the type and the second one is the connection's name. In order to use it successfully we have to fill the `config` file before, as explained in `how to use it?` section.\n\nExample:\n\n```erlang\n1\u003e apns:connect(cert, my_first_connection).\n{ok,\u003c0.87.0\u003e}\n2\u003e apns:connect(#{name =\u003e another_cert, apple_host =\u003e \"api.push.apple.com\", apple_port =\u003e 443,\ncertfile =\u003e \"priv/cert.pem\", keyfile =\u003e \"priv/key.pem\", type =\u003e cert}).\n3\u003e apns:connect(token, my_second_connection).\n{ok,\u003c0.95.0\u003e}\n```\nNote `cert` and `token` define the type we want.\n\n`apns:connect/2` returns the connection `pid`.\n\n## Create Connections without name\n\nIn some scenarios we don't want to assign names to the connections instead we want working just with the `pid` (working with a pool of connections for example). If that is the case we use the same `apns:connect/1` and `apns:connect/2` functions but instead of a connection name we put `undefined`:\n\n```erlang\n1\u003e apns:connect(cert, undefined).\n{ok,\u003c0.127.0\u003e}\n2\u003e apns:connect(#{name =\u003e undefined, apple_host =\u003e \"api.push.apple.com\", apple_port =\u003e 443,\ncertfile =\u003e \"priv/cert2.pem\", keyfile =\u003e \"priv/key2-noenc.pem\", type =\u003e cert}).\n{ok,\u003c0.130.0\u003e}\n3\u003e apns:connect(token, my_second_connection).\n{ok,\u003c0.132.0\u003e}\n```\n\n## Push Notifications over `Provider Certificate` connections\n\nIn order to send Notifications over `Provider Certificate` connection we will use `apns:push_notification/3,4`.\n\nWe will need the connection, a notification, the device ID and some http2 headers. The connection is the `atom` we used when we executed `apns:connect/2` for setting a name or its `pid`, the device ID is provided by Apple, the notification is a `map` with the data we want to send, that map will be encoded to json later and the http2 headers can be explicitly sent as a parameter using `apns:push_notification/4` or can be defined at the `config` file, in that case we would use `apns:push_notification/3`.\n\nThis is the `headers` format:\n\n```erlang\n-type headers()   :: #{ apns_id          =\u003e binary()\n                      , apns_expiration  =\u003e binary()\n                      , apns_priority    =\u003e binary()\n                      , apns_topic       =\u003e binary()\n                      , apns_collapse_id =\u003e binary()\n                      , apns_push_type   =\u003e binary()\n                      , apns_auth_token  =\u003e binary()\n                      }.\n```\n\nAll of them are defined by Apple  [here](https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CommunicatingwithAPNs.html)\n\nLets send a Notification.\n\n```erlang\n1\u003e {ok, Pid} = apns:connect(cert, my_first_connection).\n{ok,\u003c0.85.0\u003e}\n2\u003e DeviceId = \u003c\u003c\"a0dc63fb059cb9c13b03e5c974af3dd33d67fed4147da8c5ada0626439e18935\"\u003e\u003e.\n\u003c\u003c\"a0dc63fb059cb9c13b03e5c974af3dd33d67fed4147da8c5ada0626439e18935\"\u003e\u003e\n3\u003e Notification = #{aps =\u003e #{alert =\u003e \u003c\u003c\"you have a message\"\u003e\u003e}}.\n#{aps =\u003e #{alert =\u003e \u003c\u003c\"you have a message\"\u003e\u003e}}\n4\u003e apns:push_notification(my_first_connection, DeviceId, Notification).\n{200,\n [{\u003c\u003c\"apns-id\"\u003e\u003e,\u003c\u003c\"EFDE0D9D-F60C-30F4-3FF1-86F3B90BE434\"\u003e\u003e}],\n no_body}\n5\u003e apns:push_notification(Pid, DeviceId, Notification).\n{200,\n [{\u003c\u003c\"apns-id\"\u003e\u003e,\u003c\u003c\"EFDE0D9D-F60C-30F4-3FF1-86F3B90BE654\"\u003e\u003e}],\n no_body}\n```\n\nThe result is the response itself, its format is:\n\n```erlang\n-type response()  :: { integer()          % HTTP2 Code\n                     , [term()]           % Response Headers\n                     , [term()] | no_body % Response Body\n                     } | timeout.\n```\n\nAnd that's all.\n\n## Push Notifications over `Provider Authentication Tokens` connections\n\nThis is the other way APNs allows us to send notifications. In this case we don't need a certificate but we will need a `p8` file with the private key we will use to sign the token. Lets assume we've got the file  `APNsAuthKey_KEYID12345.p8` from Apple. We then have to fill the `config` file key `token_keyfile` with the path to that file.\n\nWe will need a `kid` value, this is the key identifier. In our case is the last 10 chars of the file name (`KEYID123456`). We will need also the `iss` value, this is the Team Id, that can be checked on your Apple's Developer account, in our case it will be `THEATEAM`. And that's it.\n\nYou can find more info [here](https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CommunicatingwithAPNs.html)\n\nIn order to push a notification we will use `apns:push_notification_token/4,5`. We will need the same attributes we used sending a notification over `Provider Certificate` connections plus a signed `token`. This token has a 1 hour life, so that means we can generate one token and use it many times till it expires. Lets try.\n\nCreate the token:\n\n```erlang\n6\u003e TeamId = \u003c\u003c\"THEATEAM\"\u003e\u003e.\n\u003c\u003c\"THEATEAM\"\u003e\u003e\n7\u003e KeyID = \u003c\u003c\"KEYID123456\"\u003e\u003e.\n\u003c\u003c\"KEYID123456\"\u003e\u003e\n8\u003e Token = apns:generate_token(TeamId, KeyID).\n\u003c\u003c\"eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IktFWUlEMTIzNDU2In0.eyJpc3MiOiJUSEVBVEVBTSIsImlhdCI6MTQ4NjE0OTMzNH0.MEQC\"...\u003e\u003e\n```\n\nNow push the notification:\n\n```erlang\n12\u003e DeviceId = \u003c\u003c\"a0dc63fb059cb9c13b03e5c974af3dd33d67fed4147da8c5ada0626439e18935\"\u003e\u003e.\n\u003c\u003c\"a0dc63fb059cb9c13b03e5c974af3dd33d67fed4147da8c5ada0626439e18935\"\u003e\u003e\n13\u003e Notification = #{aps =\u003e #{alert =\u003e \u003c\u003c\"you have a message\"\u003e\u003e}}.\n#{aps =\u003e #{alert =\u003e \u003c\u003c\"you have a message\"\u003e\u003e}}\n14\u003e apns:push_notification_token(my_second_connection, Token, DeviceId, Notification).\n{200,\n [{\u003c\u003c\"apns-id\"\u003e\u003e,\u003c\u003c\"EBC03BF9-A784-FDED-34F7-5A8D859DA977\"\u003e\u003e}],\n no_body}\n```\n\nWe can use this token for an entire hour, after that we will receive something like this:\n\n```erlang\n16\u003e apns:push_notification_token(my_second_connection, Token, DeviceId, Notification).\n{403,\n [{\u003c\u003c\"apns-id\"\u003e\u003e,\u003c\u003c\"03FF9497-8A6B-FFD6-B32B-160ACEDE35F0\"\u003e\u003e}],\n [{\u003c\u003c\"reason\"\u003e\u003e,\u003c\u003c\"ExpiredProviderToken\"\u003e\u003e}]}\n```\n\n## Pushing notifications\n\n*NOTE* in order to push notifications, in both ways, we _must_ call `apns:push_notification/3,4` and `apns:push_notification_token/4,5` from the same\nprocess which created the connection. If we try to do it from a different one we will get an error `{error, not_connection_owner}`.\n\n## Reconnection\n\nIf network goes down or something unexpected happens the `gun` connection with APNs will go down. In that case `apns4erl` will send a message `{reconnecting, ServerPid}` to the client process, that means `apns4erl` lost the connection and it is trying to reconnect. Once the connection has been recover a `{connection_up, ServerPid}` message will be send.\n\n\nWe implemented an *Exponential Backoff* strategy. We can set the *ceiling* time adding the `backoff_ceiling` variable on the `config` file. By default it is set to 10 (seconds).\n\n## Close connections\n\nApple recommends us to keep our connections open and avoid opening and closing very often. You can check the [Best Practices for Managing Connections](https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CommunicatingwithAPNs.html) section.\n\nBut when closing a connection makes sense `apns4erl` gives us the function `apns:close_connection/1` where the parameter is the connection's name or the connection's `pid`. After using it the name will be available for new connections again (if it was different than `undefined`).\n\n## Feedback\n\n`apns4erl` also allows us to get feedback from APNs service. It does it thru the [binary API](https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/BinaryProviderAPI.html).\n\nIn order to get feedback we would need a `Provider Certificate`. `apns4erl` provides us two functions, `apns:get_feedback/0` and `apns:get_feedback/1` which require some Feedback's information like url, port, timeout...  We can set that info in our `config` file and use `apns:get_feedback/0`. We can also send all that configuration as a parameter to `apns:get_feedback/1` where the config structure must looks like this:\n```erlang\n#{ host     := string()\n , port     := pos_integer()\n , certfile := string()\n , keyfile  =\u003e string()\n , timeout  := pos_integer()\n }.\n```\nThe response for both functions will be a list of `feedback()`\n\n```erlang\n-type feedback() :: {calendar:datetime(), string()}.\n```\nWhere the first element in the tuple is the date when the device uninstalled the app and the second element is the Device Id.\n\n### Changelog Generation\nIf you want to generate a new release of this project, you'll need to update the [CHANGELOG.md](CHANGELOG.md) file. We generally do it using `github_changelog_generator`. This project needs a special option passed to it, tho: `--exclude-tags-regex '1*'`. Otherwise, it will fail since version 2 releases started from a clean HEAD and therefore have nothing in common with the ones for version 1.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Finaka%2Fapns4erl","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Finaka%2Fapns4erl","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Finaka%2Fapns4erl/lists"}