PushX.Message (PushX v0.15.0)

Copy Markdown View Source

A struct representing a push notification message.

Provides a builder API for constructing notifications with title, body, badge, sound, and custom data.

Examples

# Simple message
message = PushX.Message.new("Hello", "World")

# Builder pattern
message = PushX.Message.new()
  |> PushX.Message.title("Order Update")
  |> PushX.Message.body("Your order has been shipped!")
  |> PushX.Message.badge(1)
  |> PushX.Message.sound("default")
  |> PushX.Message.data(%{order_id: "12345"})

# iOS specifics and localization (also delivered to iOS via FCM)
message = PushX.Message.new("Order Update", "Shipped!")
  |> PushX.Message.subtitle("Order #12345")
  |> PushX.Message.interruption_level(:time_sensitive)
  |> PushX.Message.mutable_content()
  |> PushX.Message.localized_body("ORDER_SHIPPED_BODY", ["12345"])

Every field maps to both providers where the concept exists (see to_apns_payload/1, to_fcm_payload/1, to_fcm_android/1, to_fcm_apns/1); provider-only fields are silently ignored by the other.

Summary

Types

APNS interruption level (iOS 15+): how the notification may interrupt the user.

t()

Functions

Sets the badge count (iOS).

Sets the body of the message.

Sets the notification category (iOS).

Sets the collapse key for message deduplication.

Sets APNS content-available: 1: wake the app in the background to fetch data. Combine with PushX.push/4's push_type: "background" (and no title/body) for a silent push; with a visible alert it becomes an alert-plus-background-fetch notification.

Sets custom data payload.

Sets the image URL for rich notifications.

Sets the APNS interruption level (iOS 15+): :passive, :active (default on the device), :time_sensitive (breaks through Focus; needs the Time Sensitive entitlement) or :critical (needs Apple's critical-alerts entitlement).

Localizes the body (APNS loc-key/loc-args, FCM body_loc_key/ body_loc_args).

Localizes the subtitle (APNS subtitle-loc-key/subtitle-loc-args).

Localizes the title with a key from the app's Localizable.strings and optional format arguments (APNS title-loc-key/title-loc-args, FCM title_loc_key/title_loc_args). Any literal title set with title/2 is still sent as a fallback for clients without the key.

Marks the notification as modifiable by a Notification Service Extension (APNS mutable-content: 1) — required for rich media (image attachments, decrypting content on device). Passes through to iOS via FCM too.

Creates a new empty message.

Creates a new message with title and body.

Sets the priority (:high or :normal).

Adds a key-value pair to the data payload.

Sets the APNS relevance score (0.0..1.0) used to sort notifications in the iOS notification summary.

Sets the notification sound.

Sets the subtitle (APNS alert.subtitle; for FCM, delivered to iOS via the apns override).

Sets the thread ID for notification grouping (iOS).

Sets the title of the message.

Translates the message's delivery fields into APNS send options.

Converts the message to an APNS payload map.

Translates the message's delivery fields into an FCM android block: priority, ttl, collapse_key, and — under android.notification — the title/body localization keys.

Translates the iOS-specific fields into an FCM apns override (%{"payload" => %{"aps" => ...}}) so they reach iOS devices addressed through FCM: subtitle, mutable_content, content_available, interruption_level, relevance_score, and the localization keys.

Converts the message to an FCM payload map.

Translates the message's delivery fields into Web Push send options (RFC 8030 headers), for merging into the opts of PushX.WebPush.send/3 — explicit call-site options take precedence.

Converts the message to the Notification API shape a service worker shows via registration.showNotification(title, options): title, body, icon (from image/2), tag (from collapse_key/2) and data.

Sets the TTL (time to live) in seconds.

Types

interruption_level()

@type interruption_level() :: :passive | :active | :time_sensitive | :critical

APNS interruption level (iOS 15+): how the notification may interrupt the user.

t()

@type t() :: %PushX.Message{
  badge: non_neg_integer() | nil,
  body: String.t() | nil,
  body_loc_args: [String.t()] | nil,
  body_loc_key: String.t() | nil,
  category: String.t() | nil,
  collapse_key: String.t() | nil,
  content_available: boolean(),
  data: map(),
  image: String.t() | nil,
  interruption_level: interruption_level() | nil,
  mutable_content: boolean(),
  priority: :high | :normal | nil,
  relevance_score: float() | nil,
  sound: String.t() | nil,
  subtitle: String.t() | nil,
  subtitle_loc_args: [String.t()] | nil,
  subtitle_loc_key: String.t() | nil,
  thread_id: String.t() | nil,
  title: String.t() | nil,
  title_loc_args: [String.t()] | nil,
  title_loc_key: String.t() | nil,
  ttl: non_neg_integer() | nil
}

Functions

badge(message, badge)

@spec badge(t(), non_neg_integer()) :: t()

Sets the badge count (iOS).

body(message, body)

@spec body(t(), String.t()) :: t()

Sets the body of the message.

category(message, category)

@spec category(t(), String.t()) :: t()

Sets the notification category (iOS).

collapse_key(message, key)

@spec collapse_key(t(), String.t()) :: t()

Sets the collapse key for message deduplication.

content_available(message, flag \\ true)

@spec content_available(t(), boolean()) :: t()

Sets APNS content-available: 1: wake the app in the background to fetch data. Combine with PushX.push/4's push_type: "background" (and no title/body) for a silent push; with a visible alert it becomes an alert-plus-background-fetch notification.

data(message, data)

@spec data(t(), map()) :: t()

Sets custom data payload.

image(message, image_url)

@spec image(t(), String.t()) :: t()

Sets the image URL for rich notifications.

interruption_level(message, level)

@spec interruption_level(t(), interruption_level()) :: t()

Sets the APNS interruption level (iOS 15+): :passive, :active (default on the device), :time_sensitive (breaks through Focus; needs the Time Sensitive entitlement) or :critical (needs Apple's critical-alerts entitlement).

localized_body(message, key, args \\ [])

@spec localized_body(t(), String.t(), [String.t()]) :: t()

Localizes the body (APNS loc-key/loc-args, FCM body_loc_key/ body_loc_args).

localized_subtitle(message, key, args \\ [])

@spec localized_subtitle(t(), String.t(), [String.t()]) :: t()

Localizes the subtitle (APNS subtitle-loc-key/subtitle-loc-args).

localized_title(message, key, args \\ [])

@spec localized_title(t(), String.t(), [String.t()]) :: t()

Localizes the title with a key from the app's Localizable.strings and optional format arguments (APNS title-loc-key/title-loc-args, FCM title_loc_key/title_loc_args). Any literal title set with title/2 is still sent as a fallback for clients without the key.

mutable_content(message, flag \\ true)

@spec mutable_content(t(), boolean()) :: t()

Marks the notification as modifiable by a Notification Service Extension (APNS mutable-content: 1) — required for rich media (image attachments, decrypting content on device). Passes through to iOS via FCM too.

new()

@spec new() :: t()

Creates a new empty message.

Examples

iex> PushX.Message.new()
%PushX.Message{title: nil, body: nil, data: %{}, priority: nil}

new(title, body)

@spec new(String.t(), String.t()) :: t()

Creates a new message with title and body.

Examples

iex> PushX.Message.new("Hello", "World")
%PushX.Message{title: "Hello", body: "World", data: %{}, priority: nil}

priority(message, priority)

@spec priority(t(), :high | :normal) :: t()

Sets the priority (:high or :normal).

put_data(message, key, value)

@spec put_data(t(), atom() | String.t(), any()) :: t()

Adds a key-value pair to the data payload.

relevance_score(message, score)

@spec relevance_score(t(), float()) :: t()

Sets the APNS relevance score (0.0..1.0) used to sort notifications in the iOS notification summary.

sound(message, sound)

@spec sound(t(), String.t()) :: t()

Sets the notification sound.

subtitle(message, subtitle)

@spec subtitle(t(), String.t()) :: t()

Sets the subtitle (APNS alert.subtitle; for FCM, delivered to iOS via the apns override).

thread_id(message, thread_id)

@spec thread_id(t(), String.t()) :: t()

Sets the thread ID for notification grouping (iOS).

title(message, title)

@spec title(t(), String.t()) :: t()

Sets the title of the message.

to_apns_options(message)

@spec to_apns_options(t()) :: keyword()

Translates the message's delivery fields into APNS send options.

Returns a keyword list suitable for merging into the opts of PushX.APNS.send/3 — explicit call-site options take precedence.

  • priority: :highpriority: 10, priority: :normalpriority: 5
  • ttl (seconds from now) → expiration (absolute Unix timestamp; ttl: 0 maps to expiration: 0, APNS's "attempt once, don't store")
  • collapse_keycollapse_id

Examples

iex> PushX.Message.new("Hi", "There") |> PushX.Message.priority(:normal) |> PushX.Message.to_apns_options()
[priority: 5]

to_apns_payload(message)

@spec to_apns_payload(t()) :: map()

Converts the message to an APNS payload map.

Note: when the message has a title but no explicit sound, "default" is injected — a titled notification is assumed to be user-visible. To send a visible-but-silent notification, build the raw APNS payload map yourself (omit "sound") instead of using the Message builder.

to_fcm_android(message)

@spec to_fcm_android(t()) :: map() | nil

Translates the message's delivery fields into an FCM android block: priority, ttl, collapse_key, and — under android.notification — the title/body localization keys.

Returns nil when none of them are set.

Examples

iex> PushX.Message.new("Hi", "There") |> PushX.Message.ttl(3600) |> PushX.Message.to_fcm_android()
%{"ttl" => "3600s"}

iex> PushX.Message.new("Hi", "There") |> PushX.Message.to_fcm_android()
nil

to_fcm_apns(message)

@spec to_fcm_apns(t()) :: map() | nil

Translates the iOS-specific fields into an FCM apns override (%{"payload" => %{"aps" => ...}}) so they reach iOS devices addressed through FCM: subtitle, mutable_content, content_available, interruption_level, relevance_score, and the localization keys.

Returns nil when none are set. Title/body/image travel in FCM's own notification block (to_fcm_payload/1); badge and sound in android/apns are left to callers' explicit :apns/:android options.

Examples

iex> PushX.Message.new("Hi", "There") |> PushX.Message.mutable_content() |> PushX.Message.to_fcm_apns()
%{"payload" => %{"aps" => %{"mutable-content" => 1}}}

iex> PushX.Message.new("Hi", "There") |> PushX.Message.to_fcm_apns()
nil

to_fcm_payload(message)

@spec to_fcm_payload(t()) :: map()

Converts the message to an FCM payload map.

to_webpush_options(message)

@spec to_webpush_options(t()) :: keyword()

Translates the message's delivery fields into Web Push send options (RFC 8030 headers), for merging into the opts of PushX.WebPush.send/3 — explicit call-site options take precedence.

  • ttl (seconds) → ttl: (the TTL header)
  • priority: :highurgency: :high, priority: :normalurgency: :normal

collapse_key/2 is already the payload's tag, so it is not turned into the Topic header (whose 32-char base64url alphabet it may not fit).

Examples

iex> PushX.Message.new("Hi", "There") |> PushX.Message.ttl(3600) |> PushX.Message.priority(:high) |> PushX.Message.to_webpush_options()
[ttl: 3600, urgency: :high]

to_webpush_payload(message)

@spec to_webpush_payload(t()) :: map()

Converts the message to the Notification API shape a service worker shows via registration.showNotification(title, options): title, body, icon (from image/2), tag (from collapse_key/2) and data.

badge/2 (an iOS app-icon count) is not mapped — the Notification API's badge is an image URL; put one in data/2 if your service worker wants it. Delivery fields (ttl/2, priority/2) become headers, see to_webpush_options/1.

Examples

iex> PushX.Message.new("Hi", "There") |> PushX.Message.data(%{"url" => "/inbox"}) |> PushX.Message.to_webpush_payload()
%{"title" => "Hi", "body" => "There", "data" => %{"url" => "/inbox"}}

ttl(message, ttl)

@spec ttl(t(), non_neg_integer()) :: t()

Sets the TTL (time to live) in seconds.