{"id":19725737,"url":"https://github.com/esl/sparrow","last_synced_at":"2026-03-09T22:31:59.855Z","repository":{"id":38862993,"uuid":"151107094","full_name":"esl/sparrow","owner":"esl","description":"iOS and Android push notifications for Elixir.","archived":false,"fork":false,"pushed_at":"2025-05-22T12:59:32.000Z","size":350,"stargazers_count":33,"open_issues_count":0,"forks_count":10,"subscribers_count":7,"default_branch":"master","last_synced_at":"2025-10-05T12:35:38.456Z","etag":null,"topics":["apns","elixir","fcm","push-notifications"],"latest_commit_sha":null,"homepage":"","language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/esl.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","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,"zenodo":null}},"created_at":"2018-10-01T14:58:30.000Z","updated_at":"2025-08-08T08:19:47.000Z","dependencies_parsed_at":"2024-02-12T16:53:56.751Z","dependency_job_id":"9f8634e2-fdc9-4f21-b1f2-45ba2ab6f11c","html_url":"https://github.com/esl/sparrow","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/esl/sparrow","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/esl%2Fsparrow","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/esl%2Fsparrow/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/esl%2Fsparrow/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/esl%2Fsparrow/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/esl","download_url":"https://codeload.github.com/esl/sparrow/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/esl%2Fsparrow/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30314628,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-09T20:05:46.299Z","status":"ssl_error","status_checked_at":"2026-03-09T19:57:04.425Z","response_time":61,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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","elixir","fcm","push-notifications"],"created_at":"2024-11-11T23:32:59.114Z","updated_at":"2026-03-09T22:31:59.822Z","avatar_url":"https://github.com/esl.png","language":"Elixir","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Sparrow\n## Summary\nSparrow is an Elixir library for [push notifications](https://en.wikipedia.org/wiki/Push_technology#Push_notification).\n[![Build Status](https://travis-ci.org/esl/sparrow.svg?branch=master)](https://travis-ci.org/esl/sparrow)\n[![Coverage Status](https://coveralls.io/repos/github/esl/sparrow/badge.svg)](https://coveralls.io/github/esl/sparrow)\n\nCurrently it provides support for the following APIs:\n* [FCM v1](https://firebase.google.com/docs/cloud-messaging/)\n* [APNS](https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/APNSOverview.html)\n\n## Requirements\n* Elixir 1.13 or higher\n* Erlang OTP 24 or higher\n\n# Build sparrow config\nThis section describes how to write a config file for Sparrow.\nIf you wish to use just one of the following services, do not include the other in the config.\n\n```elixir\nconfig :sparrow, \n    fcm: [...],\n    apns: [...]\n```\n\n## Config examples\n\nFCM only:\n\n```elixir\nconfig :sparrow, fcm: [\n        [\n            path_to_json: \"path/to/google-services.json\"\n        ]\n    ]\n```\n\nAPNS only:\n\n```elixir\nconfig :sparrow, \n    apns: [\n        dev:\n        [\n            [\n                auth_type: :certificate_based,\n                cert: \"path/to/apns/cert.pem\",\n                key: \"path/to/apns/key.pem\"\n            ]\n        ],\n        prod:\n        [\n            [ \n                auth_type: :token_based,\n                token_id: :some_atom_id,\n                tags: [:production, :token_based_auth]\n            ],\n            [ \n                auth_type: :token_based,\n                token_id: :other_atom_id,\n                tags: [:other_production_pool, :token_based_auth]\n            ]\n        ],\n        tokens:\n        [\n            [\n                token_id: :some_atom_id,\n                key_id: \"FAKE_KEY_ID\",\n                team_id: \"FAKE_TEAM_ID\",\n                p8_file_path: \"path/to/file/token.p8\"\n            ],\n             [\n                token_id: :other_atom_id,\n                key_id: \"OTHER_FAKE_KEY_ID\",\n                team_id: \"OTHER_FAKE_TEAM_ID\",\n                p8_file_path: \"path/to/file/other/token.p8\"\n            ]\n        ]\n    ]\n```\n\nBoth FCM  and APNS:\n\n```elixir\nconfig :sparrow,\n    fcm: [\n        [\n            path_to_json: \"path/to/fcm_token.json\"\n        ]\n    ],\n    apns: [\n        dev:\n        [\n            [\n                auth_type: :certificate_based,\n                cert: \"path/to/apns/cert.pem\",\n                key: \"path/to/apns/key.pem\"\n            ]\n        ]\n    ]\n```\n\n## Config options description\n\n### FCM specific\n- `:path_to_json` - is a path to a json file provided by FCM\n\n### APNS specific\n- `:auth_type` - defines the authentication type, the allowed values are: `:token_based`, `:certificate_based`\n\n    - `:token_based` requires setting:\n        - `:token_id` - is a unique atom referring to a token with the same `:token_id` in config\n    \n    - `:certificate_based` requires setting:\n    \n        - `:cert` - path to the certificate file provided by APNS\n        - `:key` - path to the key file provided by APNS\n\n\n### Connection config\n- `:endpoint` - service uri\n- `:port` - service port\n- `:tls_opts` - passed to erlang [ssl](http://erlang.org/doc/man/ssl.html) module (see DATA TYPES -\u003e ssl_option())\n- `:ping_interval` - number of miliseconds between each [ping](https://http2.github.io/http2-spec/#PING), to switch ping off set `:ping_interval` to `nil`\n- `:reconnect_attempts` - number of attempts to reconnect before failing the connection\n\n### Connection pool config\n- `:pool_name` - defines pool name, not recommended - please use [tags](#tags) instead \n- `:tags` - see the [tags](#tags) section\n- `:worker_num` - number of workers in a pool\n- `:raw_opts` - opts passed directly to [wpool](github.com/inaka/worker_pool)\n\n### FCM config example\n```elixir\nfcm_config = \n    [\n        [\n            # Authentication\n            path_to_json: \"path/to/fcm_token.json\", # mandatory, path to FCM authentication JSON file\n            # Connection config\n            endpoint: \"fcm.googleapis.com\", # optional\n            port: 443, # optional\n            tls_opts: [], # optional\n            ping_interval: 5000, # optional\n            reconnect_attempts: 3, # optional\n            # Pool config \n            tags: [], # optional\n            worker_num: 3, # optional\n            raw_opts: [] # optional, options passed directly to wpool\n        ]\n    ]\n```\n### APNS config example\n```elixir\napns_config = \n[\n    dev: [apns_pool_1, apns_pool_2 ], # list of apns_pool_configs by default set to APNS development endpoint, is a list of APNS pools\n    prod: [apns_pool_3 ],  # list of apns_pool_configs by default set to APNS production endpoint, is a list of APNS pools\n    tokens: [apns_token_1, apns_token_2 ] # optional, is a list of APNS tokens\n]\n```\n### APNS pool example\n\nToken based authentication example:\n```elixir\napns_pool = [\n    # Token based authentication\n    auth_type: :token_based, # mandatory, :token_based or :certificate_based\n    token_id: :some_atom_id, # mandatory, token with the same id must be in `tokens` in `apns_config`\n    # Connection config\n    endpoint: \"api.development.push.apple.com\", # optional\n    port: 443, # optional\n    tls_opts: [], # optional\n    ping_interval: 5000, # optional\n    reconnect_attempts: 3, # optional\n    # pool config\n    tags: [:first_batch_clients, :beta_users], # optional\n    worker_num: 3, # optional\n    raw_opts: [], # optional\n]\n```\n\nCertificate based authentication example:\n\n```elixir\napns_pool = [\n    # Certificate based authentication\n    auth_type: :certificate_based, # mandatory, :token_based or :certificate_based\n    cert: \"path/to/apns/cert.pem\", # mandatory, path to certificate file provided by APNS\n    key: \"path/to/apns/key.pem\", # mandatory, path to key file provided by APNS\n    # Connection config\n    endpoint: \"api.push.apple.com\", # optional\n    port: 443, # optional\n    tls_opts: [], # optional\n    ping_interval: 5000, # optional\n    reconnect_attempts: 3, # optional\n    # pool config\n    tags: [:another_batch_clients], # optional\n    worker_num: 3, # optional\n    raw_opts: [] # optional\n]\n```\n\n### APNS token example\n\n```elixir\napns_token = [\n          token_id: :some_atom_id, # mandatory, the same as in APNSPOOL\n          key_id: \"FAKE_KEY_ID\", # mandatory, data obtained form APNS account\n          team_id: \"FAKE_TEAM_ID\", # mandatory, data obtained form APNS account\n          p8_file_path: \"path/to/file/token.p8\" # mandatory, path to file storing APNS token\n        ]\n```\n\n## Include sparrow in your project\n\n```elixir\ndefp deps do\n    [\n      {:sparrow, github: \"esl/sparrow\", tag: \"cc80bbc\"},\n      ]\n  end\n```\n\n## Many pools\n\nSparrow suports many pools in a single `:sparrow` instance.\n[Tags](#tags) are used to choose a pool when sending a notification.\n\n\n## Tags\n\nTags is a mechanism allowing to choose a pool to send a notification from.\nEach pool has a defined list of tags (`[]` as default).\nThe algorithm has the following steps:\n1) Filter pooltype based on notification type (`:fcm` or `:{apns, :dev}` or `{:apns, :prod}`).\n2) Choose only pools that have all tags (from the function call) included in their tags (from pool configuration).\n3) Choose first of the filtered pools.\n\nExample:\n    Let's say you have the following pools:\n\n- `{:apns, :dev}`:\n    - *pool1*: `[:test_pool, :dev_pool, :homer]`,\n    - *pool2*: `[:test_pool, :dev_pool, :bart]`,\n    - *pool3*: `[:test_pool, :dev_pool, :ned, :bart]`\n- `{:apns, :prod}`:\n    - *pool1*: `[:prod_pool]`\n\n\nLets assume the notification type is `{:apns, :dev}`.\n\nIf you pass `[]`, *pool1* is chosen. \n\nIf you pass `[:homer]`, *pool1* is chosen. \n\nIf you pass `[:bart]`, *pool2* is chosen. \n\nIf you pass `[:ned]`, *pool3* is chosen. \n\nIf you pass `[:test_pool]`, *pool1* is chosen. \n\nIf you pass `[:test_pool, :homer]`, *pool1* is chosen. \n\nIf you pass `[:test_pool, :dev_pool, :homer]`, *pool1* is chosen. \n\nIf you pass `[:test_pool, :dev_pool, :ned]`, *pool3* is chosen. \n\nIf you pass `[:not_existing, :set_of_tags]`, `{:error, :configuration_error}` is returned. \n\n[](https://placehold.it/15/ff0000/ff0000?text=+) *`It is not recommended to choose a pool based on pools order!`*\n\n## Telemetry\nSparrow supports [telemetry](https://github.com/beam-telemetry/telemetry). Emitted events are defined with following tags:\n\n- `[:sparrow, :h2_worker, :init]`\n- `[:sparrow, :h2_worker, :terminate]`\n- `[:sparrow, :h2_worker, :conn_lost]`\n- `[:sparrow, :h2_worker, :request_error]`\n- `[:sparrow, :h2_worker, :request_success]`\n- `[:sparrow, :h2_worker, :conn_success]`\n- `[:sparrow, :h2_worker, :conn_fail]`\n- `[:sparrow, :pools_warden, :init]`\n- `[:sparrow, :pools_warden, :terminate]`\n- `[:sparrow, :pools_warden, :choose_pool]`\n- `[:sparrow, :pools_warden, :pool_down]`\n- `[:sparrow, :pools_warden, :add_pool]`\n\nThere are also events measuring the duration of a few chosen function calls:\n\n- `[:sparrow, :push, :api]`\n- `[:sparrow, :push, :apns]`\n- `[:sparrow, :push, :fcm]`\n- `[:sparrow, :h2_worker, :handle]`\n\n# Send your first push notification\n\n1) [Define your config](#build-sparrow-config)\n2) Start an application\n```elixir\nApplication.start(:sparrow)\n```\n3) Build and *Push* the notification\n    3.1 APNS\n    ```elixir\n        :ok =\n            \"my_device_token\"\n            |\u003e Sparrow.APNS.Notification.new(:dev)\n            |\u003e Sparrow.APNS.Notification.add_title(\"my first notification title\")\n            |\u003e Sparrow.APNS.Notification.add_body(\"my first notification body\")\n            # |\u003e Sparrow.APNS.Notification.add_...\n            |\u003e Sparrow.API.push()\n    ```\n    3.1 FCM\n    ```elixir\n    android =\n        Sparrow.FCM.V1.Android.new()\n        |\u003e Sparrow.FCM.V1.Android.add_title(\"my first notification title\")\n        |\u003e Sparrow.FCM.V1.Android.add_body(\"my first notification body\")\n    \n    webpush = \n        Sparrow.FCM.V1.Webpush.new(\"www.my.test.link.com\")\n        |\u003e Sparrow.FCM.V1.Webpush.add_title(\"my first notification title\")\n        |\u003e Sparrow.FCM.V1.Webpush.add_body(\"my first notification body\")\n      \n    notification =\n        Sparrow.FCM.V1.Notification.new(:topic, \"news\")\n        |\u003e Sparrow.FCM.V1.Notification.add_android(android)\n        |\u003e Sparrow.FCM.V1.Notification.add_webpush(webpush)\n        Sparrow.API.push()\n    ```\n    \n***\n\n## How to obtain and use APNS certificate for certificate based authorization?\n\nPre Requirements:\n* MacOS with Xcode installed\n* Apple mobile device\n* Tutorial (*) requirements\n\nFirst, follow [this](https://medium.com/flawless-app-stories/ios-remote-push-notifications-in-a-nutshell-d05f5ccac252) tutorial (*), specifically the \"Step 3, get APNS certificate\" section.\n\nWhen you reach point where you have `exampleName.cer` file, import it to Keychain Access:\nFile -\u003e Import Items... -\u003e Chose `exampleName.cer` \n\nNext, export the certificate you just imported as `exampleName.p12`.\nNote: you can just go with no password by pressing Enter. If you enter a password, remember it.\nI shall refer to this password as (pass1) later in tutorial.\n\nOpen terminal, go to your `exampleName.p12` file location \n```sh\n$ cd my/p12/file/location\n```\n\nNext convert `.p12` to `.pem`: \n```sh\n$ openssl pkcs12 -in `exampleName.p12` -out `exampleName.pem`\n```\n\nType (pass1), and then `exampleName.pem` file password (pass2), which cannot be empty.\n\nNext extract key:\n\n```sh\nopenssl rsa -in `exampleName.pem` -out `exampleKey.pem`\n```\n\n### How to obtain a device token?\n\nTry [this](https://developer.apple.com/documentation/usernotifications/registering_your_app_with_apns).\n\n### How to obtain an apns-topic?\n\nLast but not least, to get the 'apns-topic' header value, go to:\nXcode -\u003e open your swift app -\u003e General -\u003e Identity -\u003e Bundle Identifier\n\nGood luck :)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fesl%2Fsparrow","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fesl%2Fsparrow","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fesl%2Fsparrow/lists"}