PPactDocs
Productivity

Broadcasts

Top-down internal announcements with pre-computed recipient sets, per-recipient read and acknowledgment tracking, and sender-side engagement metrics.

Broadcasts

Broadcasts are Pact's top-down internal comms: a leader posts an announcement, Pact fans it out to a recipient set at create time, and every recipient's read and acknowledgment state is tracked individually. It's the counterpart to Feedback, which flows bottom-up.

Live

Backed by api/routes/broadcasts.py and core.comms.broadcasts, with tables from alembic migration 0073_comms_broadcasts_feedback. Every route resolves the tenant via get_tenant_id and never accepts a tenant from the body.

Sending

code
POST /v1/broadcasts
{
  "subject": "Q3 kickoff",
  "body": "...",
  "scope_user_id": null,
  "attachments": [ { "filename": "deck.pdf", "url": "...", "mime": "application/pdf" } ]
}

The scope determines the recipient set:

  • scope_user_id: null — fan out tenant-wide. Restricted to admins and owners.
  • scope_user_id: <id> — fan out to the subordinates of that user only, so a manager can address just their reporting line.

Recipients are resolved and materialized into broadcast_recipients at create time, which makes every inbox read an O(index-lookup) rather than a chain walk.

Reading and acknowledging

  1. 1

    Inbox

    GET /v1/broadcasts/inbox returns the caller's broadcasts, each with read_at and acknowledged_at timestamps and the sender's name and email.
  2. 2

    Mark read

    POST /v1/broadcasts/{id}/read records that the recipient opened it.
  3. 3

    Acknowledge

    POST /v1/broadcasts/{id}/acknowledge records an explicit acknowledgment — a stronger signal than a passive read.
  4. 4

    Notification chip

    GET /v1/broadcasts/unread-count powers the global unread badge.

Engagement metrics

Senders (only) can pull per-recipient engagement:

code
GET /v1/broadcasts/{id}/engagement

Because read and acknowledgment state is stored per recipient row, the sender sees exactly who has read and who has acknowledged — not just an aggregate count. This route is sender-scoped, so a recipient cannot read another recipient's engagement.

Attribution

The caller's user id is resolved from the bearer token's SHA-256 hash against tenant_users.api_key_hash, and tenant-wide fan-out is gated on get_caller_role, so only admins and owners can address the whole tenant.