Real-time updates for CRM interactions, backed by PhoenixKit.PubSub.Manager.
An interaction appears in the feed of every contact it involves — the subject
contact (interactions.contact_uuid) and any resolved party contacts — so
a change fans out to each of their per-contact topics. A contact's
Interactions tab subscribes to its own topic; any add/edit/delete to an
interaction touching that contact pushes a live refresh.
Messages are {:crm, event, payload} tuples where event is one of
:interaction_created | :interaction_updated | :interaction_deleted and
payload is %{interaction_uuid: uuid}.
Topics are global (no tenant partitioning) — but the per-contact topic is keyed
by uuid, so you can't subscribe without already knowing the contact, which
bounds the fan-out. (Mirrors phoenix_kit_projects — tenant scoping is a
framework-wide gap, not a CRM one.)
Summary
Functions
Fans a contact change out to the companies it is (or was) a member of, so each company's page can refresh its roster. Best-effort (rescued). Call it AFTER the DB commit.
Fans an interaction change out to every involved contact's feed topic.
Broadcasts a CRM list event on the global crm:lists topic — {:crm, event, payload}, same tuple shape as broadcast_interaction/2.
Broadcasts an interaction change to an EXPLICIT set of contact feed topics.
Subject contact + any resolved party contacts (deduped, nils dropped). Tolerates parties not being preloaded (treats them as none).
Subscribes the calling process to a topic.
Subscribes to a topic on the HOST app's PubSub — the server other
phoenix_kit modules broadcast on through PhoenixKit.PubSubHelper
(the catalogue's "phoenix_kit_catalogue" topic). CRM's own topics live
on core's internal server (subscribe/1); a cross-module subscription
made there never hears anything (review finding, 2026-08-24).
Topic for one company's live page: its member roster (a contact joining,
leaving, being trashed/restored/deleted, or renamed) — {:crm, event, %{contact_uuid: uuid}} with event one of :member_joined | :member_left | :member_changed.
Topic for the interaction feed of a single contact (as subject or party).
Topic for CRM contact-list live updates (membership changes, counters).
Unsubscribes the calling process from a topic.
Functions
Fans a contact change out to the companies it is (or was) a member of, so each company's page can refresh its roster. Best-effort (rescued). Call it AFTER the DB commit.
@spec broadcast_interaction(atom(), PhoenixKitCRM.Schemas.Interaction.t()) :: :ok
Fans an interaction change out to every involved contact's feed topic.
Best-effort: never raises out to the caller — a saved interaction must not be reported as failed just because the realtime broadcast hiccuped. Call it AFTER the DB commit.
Broadcasts a CRM list event on the global crm:lists topic — {:crm, event, payload}, same tuple shape as broadcast_interaction/2.
NET-NEW for CRM: PhoenixKitCRM.PartyRoles deliberately doesn't broadcast
(see its moduledoc), but list membership counters are shown live in the
admin UI, so PhoenixKitCRM.Lists broadcasts here on every mutation.
Best-effort: never raises out to the caller. Call it AFTER the DB commit.
Broadcasts an interaction change to an EXPLICIT set of contact feed topics.
Used by updates, which must reach the union of the old and new involved contacts so a contact dropped by an edit still gets a refresh to remove the entry. Best-effort (rescued).
@spec involved_contact_uuids(PhoenixKitCRM.Schemas.Interaction.t()) :: [binary()]
Subject contact + any resolved party contacts (deduped, nils dropped). Tolerates parties not being preloaded (treats them as none).
Subscribes the calling process to a topic.
Subscribes to a topic on the HOST app's PubSub — the server other
phoenix_kit modules broadcast on through PhoenixKit.PubSubHelper
(the catalogue's "phoenix_kit_catalogue" topic). CRM's own topics live
on core's internal server (subscribe/1); a cross-module subscription
made there never hears anything (review finding, 2026-08-24).
Topic for one company's live page: its member roster (a contact joining,
leaving, being trashed/restored/deleted, or renamed) — {:crm, event, %{contact_uuid: uuid}} with event one of :member_joined | :member_left | :member_changed.
Topic for the interaction feed of a single contact (as subject or party).
@spec topic_lists() :: String.t()
Topic for CRM contact-list live updates (membership changes, counters).
@spec unsubscribe(String.t()) :: :ok
Unsubscribes the calling process from a topic.
@spec unsubscribe_host(String.t()) :: :ok