Delivers one notification to one external channel (Telegram, …), off the hot
path. Enqueued transactionally from the notification creation choke point —
one job per {source, channel} — so a slow bot API call never blocks the
business action that logged the activity.
Job args
%{"channel" => "telegram",
"recipient_uuid" => "<uuid>",
"type_key" => "posts.likes",
# exactly ONE source:
"activity_uuid" => "<uuid>" # activity-driven (renders from the Entry)
# or
"notification_uuid" => "<uuid>"} # standalone (renders from the row)Retry / failure policy
The channel returns a permanent/transient verdict:
:ok— done.{:error, {:transient, _}}/{:error, {:transient, _}, retry_ms}— Oban retries (with a snooze onretry_ms), up tomax_attempts.{:error, {:permanent, reason}}— soft-disable the channel in the user's config (enabled: false,status,disabled_reason) and stop. A bot the user blocked shouldn't retry-storm.
A channel/user/source that's gone by run time is discarded (:ok), not
retried. The channel resolves its own credentials owner-scoped to the
recipient, so this worker never handles a token.
Summary
Functions
Builds the Oban job changeset for a delivery, stamping the source_uuid used
by the unique key (whichever of activity/notification uuid is present). String
keys throughout (JSONB args). Callers pass this to Oban.insert/*.
Functions
@spec build(map()) :: Ecto.Changeset.t()
Builds the Oban job changeset for a delivery, stamping the source_uuid used
by the unique key (whichever of activity/notification uuid is present). String
keys throughout (JSONB args). Callers pass this to Oban.insert/*.