PushX

CIHex.pmHex DocsLicense

Modern push notifications for Elixir. Supports Apple APNS and Google FCM with HTTP/2, JWT authentication, and a clean unified API.

Features

Installation

Add pushx to your dependencies in mix.exs:

def deps do
[
{:pushx, "~> 0.2.0"}
]
end

Getting Your Credentials

Apple APNS Setup

You'll need three things from Apple: Key ID, Team ID, and a Private Key (.p8 file).

Step 1: Get Your Team ID

  1. Go to Apple Developer Account
  2. Your Team ID is shown in the top-right corner (10 characters, e.g., TEAM123456)

Step 2: Create an APNS Key

  1. Go to Certificates, Identifiers & Profiles
  2. Click Keys+ (Create a new key)
  3. Enter a name (e.g., "PushX APNS Key")
  4. Check Apple Push Notifications service (APNs)
  5. Click ContinueRegister
  6. Download the .p8 file (you can only download it once!)
  7. Note the Key ID shown (10 characters, e.g., ABC123DEFG)

Step 3: Configure PushX

# config/config.exs or config/runtime.exs
config :pushx,
apns_key_id: "ABC123DEFG", # From step 2
apns_team_id: "TEAM123456", # From step 1
apns_private_key: {:file, "priv/keys/AuthKey_ABC123DEFG.p8"},
apns_mode: :prod # :prod or :sandbox

Or use environment variables:

config :pushx,
apns_key_id: System.get_env("APNS_KEY_ID"),
apns_team_id: System.get_env("APNS_TEAM_ID"),
apns_private_key: System.get_env("APNS_PRIVATE_KEY"), # PEM string directly
apns_mode: :prod

Note: The :topic option in push/4 should be your app's Bundle ID (e.g., com.yourcompany.app)


Google FCM Setup

You'll need a Project ID and a Service Account JSON file from Firebase.

Step 1: Create/Open Firebase Project

  1. Go to Firebase Console
  2. Create a new project or select an existing one
  3. Note your Project ID (shown in Project Settings, e.g., my-app-12345)

Step 2: Enable Cloud Messaging API

  1. Go to Google Cloud Console
  2. Select your Firebase project
  3. Go to APIs & ServicesLibrary
  4. Search for "Firebase Cloud Messaging API" and Enable it

Step 3: Create Service Account Key

  1. In Firebase Console, go to Project Settings (gear icon)
  2. Click Service accounts tab
  3. Click Generate new private key
  4. Save the JSON file securely (e.g., priv/keys/firebase-service-account.json)

Step 4: Configure PushX

# config/config.exs or config/runtime.exs
config :pushx,
fcm_project_id: "my-app-12345",
fcm_credentials: {:file, "priv/keys/firebase-service-account.json"}

Or inline the credentials:

config :pushx,
fcm_project_id: "my-app-12345",
fcm_credentials: %{
"type" => "service_account",
"project_id" => "my-app-12345",
"private_key_id" => "...",
"private_key" => "-----BEGIN PRIVATE KEY-----\n...",
"client_email" => "firebase-adminsdk-xxx@my-app-12345.iam.gserviceaccount.com",
# ... rest of service account JSON
}

Security: Never commit credentials to git! Use environment variables or secret management in production.


Configuration

APNS (Apple Push Notification Service)

config :pushx,
apns_key_id: "ABC123DEFG",
apns_team_id: "TEAM123456",
apns_private_key: {:file, "priv/keys/AuthKey.p8"},
apns_mode: :prod # or :sandbox

FCM (Firebase Cloud Messaging)

config :pushx,
fcm_project_id: "my-project-id",
fcm_credentials: {:file, "priv/keys/firebase-service-account.json"}

Finch Pool Configuration (Optional)

config :pushx,
finch_pool_size: 10, # connections per pool (default: 10)
finch_pool_count: 1 # number of pools (default: 1)

Usage

Unified API

# Send to iOS
PushX.push(:apns, device_token, "Hello World", topic: "com.example.app")
# Send to Android
PushX.push(:fcm, device_token, "Hello World")
# With title and body
PushX.push(:apns, token, %{title: "Alert", body: "Door unlocked"}, topic: "com.example.app")

Message Builder

message = PushX.message()
|> PushX.Message.title("Lock Alert")
|> PushX.Message.body("Front door unlocked")
|> PushX.Message.badge(1)
|> PushX.Message.sound("default")
|> PushX.Message.data(%{lock_id: "abc123"})
PushX.push(:apns, token, message, topic: "com.example.app")

Direct Provider Access

# APNS with all options
PushX.APNS.send(token, payload,
topic: "com.app.bundle",
mode: :sandbox,
push_type: "alert",
priority: 10
)
# FCM with data payload
PushX.FCM.send(token, notification, data: %{"key" => "value"})
# Silent/background notification
payload = PushX.APNS.silent_notification(%{action: "sync"})
PushX.APNS.send(token, payload, topic: "com.app", push_type: "background", priority: 5)

Response Handling

case PushX.push(:apns, token, message, topic: "com.app") do
{:ok, %PushX.Response{status: :sent, id: apns_id}} ->
Logger.info("Sent! ID: #{apns_id}")
{:error, %PushX.Response{status: :invalid_token}} ->
Tokens.delete(token) # Remove invalid token
{:error, %PushX.Response{status: status, reason: reason}} ->
Logger.warning("Failed: #{status} - #{reason}")
end
# Check if token should be removed
if PushX.Response.should_remove_token?(response) do
Tokens.delete(token)
end

Requirements

Tested on Elixir 1.18/1.19 with OTP 26, 27, and 28.

Roadmap

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add some amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

License

MIT License. See LICENSE for details.


Built with ❤️ by Cigno Systems AB