# `PhoenixKitEcommerce.Notifications`
[🔗](https://github.com/BeamLabEU/phoenix_kit_ecommerce/blob/0.3.0/lib/phoenix_kit_ecommerce/notifications.ex#L1)

Shop notifications: who hears about an order or an import, and when.

Sends go through core's notification layer, so each recipient's own
per-type preferences and delivery channels (in-app inbox, email,
Telegram, digest cadence) apply. This module only decides the audience
and the copy.

## Recipients

`admin_recipients/0` unions three sources, because no single one is
complete:

  * holders of the relevant permission key (`users_with_permission/1`)
  * **Owner-role holders**, whose access is implicit and who therefore
    have no permission rows at all
  * holders of the `"*"` superadmin key, likewise absent from a
    key-specific query

A resolver that queried only the first would miss the primary operator
of a default install — the person most likely to want the notification.

## Failure is never fatal

Every send is wrapped: a notification is a message *about* a committed
fact, and must not be able to undo it. Order placement in particular
runs this after the conversion transaction commits.

# `admin_recipients`

Everyone who should hear about shop operations: holders of `key`,
unioned with Owner-role holders and `"*"` superadmins.

# `cart_item_added`

Storefront signal: an item landed in a cart.

Emits `shop.cart_first_item_added` once per cart (atomic claim) when that
toggle is on; otherwise emits `shop.cart_item_added` per add when the
every-add toggle is on. A first add never produces both.

# `checkout_started`

Storefront signal: a buyer reached the checkout page with a valid cart.
Fires once per cart (atomic claim), only when the toggle is on.

# `import_completed`

Notifies the admin who started an import that it finished.

Job-level only: one message per import carrying aggregate counts, never
one per row — a 10k-row import must not produce 10k notifications.

# `import_failed`

Notifies the initiator that an import failed.

Call ONLY on the terminal attempt: Oban retries a failed job, and
notifying on every attempt turns one transient failure into three
alerts followed by a success.

# `order_placed`

Notifies shop operators that an order was placed, and (separately) the
customer that theirs was confirmed.

Best-effort by construction — see the module doc.

# `shop_recipients`

Recipients for storefront signals: the configured recipient list
(`shop_notification_recipients`), or every shop admin when unset/empty.

Stored as `%{"uuids" => [...]}` rather than a bare list — core's
`value_json` column casts through an Ecto `:map` field, which rejects a
top-level list at the changeset.

The stored list is re-checked against `shop.manage_carts` holders on
every send, not only when the admin saved it: the setting is a snapshot
of who was an operator that day, and revoking someone's shop access has
to stop the cart-activity feed too — those messages carry what visitors
are shopping for and what their carts are worth. Fails closed, since
`admin_recipients/1` rescues to `[]`.

---

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