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.
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
@type interruption_level() :: :passive | :active | :time_sensitive | :critical
APNS interruption level (iOS 15+): how the notification may interrupt the user.
@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
@spec badge(t(), non_neg_integer()) :: t()
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.
@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).
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.
@spec new() :: t()
Creates a new empty message.
Examples
iex> PushX.Message.new()
%PushX.Message{title: nil, body: nil, data: %{}, priority: nil}
Creates a new message with title and body.
Examples
iex> PushX.Message.new("Hello", "World")
%PushX.Message{title: "Hello", body: "World", data: %{}, priority: nil}
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.
Returns a keyword list suitable for merging into the opts of
PushX.APNS.send/3 — explicit call-site options take precedence.
priority: :high→priority: 10,priority: :normal→priority: 5ttl(seconds from now) →expiration(absolute Unix timestamp;ttl: 0maps toexpiration: 0, APNS's "attempt once, don't store")collapse_key→collapse_id
Examples
iex> PushX.Message.new("Hi", "There") |> PushX.Message.priority(:normal) |> PushX.Message.to_apns_options()
[priority: 5]
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.
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
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
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.
ttl(seconds) →ttl:(theTTLheader)priority: :high→urgency: :high,priority: :normal→urgency: :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]
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"}}
@spec ttl(t(), non_neg_integer()) :: t()
Sets the TTL (time to live) in seconds.