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
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
Inbox
GET /v1/broadcasts/inboxreturns the caller's broadcasts, each withread_atandacknowledged_attimestamps and the sender's name and email. - 2
Mark read
POST /v1/broadcasts/{id}/readrecords that the recipient opened it. - 3
Acknowledge
POST /v1/broadcasts/{id}/acknowledgerecords an explicit acknowledgment — a stronger signal than a passive read. - 4
Notification chip
GET /v1/broadcasts/unread-countpowers the global unread badge.
Engagement metrics
Senders (only) can pull per-recipient engagement:
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.