# `PhoenixKit.Notifications.DeliveryWorker`
[🔗](https://github.com/BeamLabEU/phoenix_kit/blob/v2.13.11/lib/phoenix_kit/notifications/delivery_worker.ex#L1)

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 on `retry_ms`), up to `max_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.

# `build`

```elixir
@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/*`.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
