Appearance
Admin Communication Hub
The platform-admin mission-control surface for the whole communication channel: accounts, templates, policy, campaigns, delivery, cost, alerts — in one place, instead of Meta configuration scattered across features. This page is the hub's module contract: what each module controls, what Meta's APIs can actually feed it, and what phase it belongs to.
THREE THINGS DO NOT EXIST YET, AND THE HUB SITS BEHIND ALL OF THEM
- The admin panel surface is not started —
apps/web's admin tier has zero code, and ADR-0011/ADR-0028 place it in Stack 1 behind/admin(a trust boundary that must never share a route group with/org). is_admin()does not exist (QRS-803) — the platform-admin identity is an open ADR-0006 decision, and every read this hub makes is gated on it.- No Communication Hub screen exists in the design project. The admin-panel section (
prototype/admin-panel/, desktop only, 12 live sections) was enumerated on 2026-08-21 and holds no communications screen — so this is a design request, not a design pull, and it goes through the Claude Design process first. The spec/prompt is Communication Hub Spec.
Where it sits in the admin panel
The admin design's own conventions (from the design registry, round 30+):
- A new sidebar section (
Communications), one page sharing the admin shell — the same "one surface per job" rule that gave Subscriptions and Affiliates their own sections. It is not a tab of Ad Manager:Campaigns.dc.htmlin the admin panel is ad campaigns (a redirect to AdManager), and message campaigns are a different job with different policy constraints. - The channel kill-switch registers in the platform control registry (
platform/controls.js, the 28-control MissionControl registry), so pausing all WhatsApp sends is findable from Mission Control like every other platform-wide switch — the left nav never grows an item per switch. - High-risk actions confirm with old value, new value and consequence (the MissionControl pattern): kill-switch, category disable, template delete, campaign cancel.
Modules
Phases: P1 ships with the transactional money-path messages · P2 with template/campaign operations · P3 with merchant/enterprise exposure. "Meta feed" says where the data really comes from, so no module is designed around an API that does not exist.
| # | Module | Controls | Meta feed | Phase |
|---|---|---|---|---|
| 1 | Channel health | WABA + phone-number registry, quality rating, messaging-limit tier, webhook health (event lag, last event at), throughput | mirrored from phone_number_quality_update / account_update / business_capability_update webhooks + reconciling reads | P1 |
| 2 | Templates | full lifecycle: author → submit to Meta → review status → paused/disabled alerts; usage/dependency view (which platform flows send which template); category + recategorization tracking; library-template shortcuts | Business Management API CRUD + message_template_status_update / message_template_quality_update / template_category_update webhooks | P2 (a read-only status list is P1) |
| 3 | Sending policy & consent | transactional vs marketing enablement per category, quiet hours, the consent ledger (opt-in/opt-out, source, history), suppression browsing, the kill-switch | ours — Meta only enforces (per-user marketing caps, error 131049); consent is QRSETU's ledger | P1 (policy) / P2 (ledger UI) |
| 4 | Message campaigns | create → audience (consented contacts × filters) → schedule → execute → per-campaign delivered/read/failed folds | send API + status webhooks; per-template template_analytics for clicks | P3 |
| 5 | Message log & delivery | the communication_messages ledger explorer: per-message event timeline, failed queue with retry state, suppressed reasons, dead-lettered (uncorrelated) events | messages status webhooks, recorded verbatim | P1 |
| 6 | Usage & cost | daily volume + cost by category/number/workspace, our-count-vs-Meta-count reconciliation, per-merchant usage reporting | pricing_analytics (COST + VOLUME — the one real cost endpoint) + our ledger folds | P2 |
| 7 | Billing runbook | payment method, invoices, GST, INR migration — surfaced as documented ops tasks with deep links into Meta Billing Hub, not as UI, because no API exists for any of them (verified — see the assessment) | none (UI-only at Meta) | P2 |
| 8 | Merchant & enterprise config | per-workspace communication settings and per-plan usage limits — configuration against feature_grants.limit_value + on_exceed, which already exists; entitlement-gated merchant campaigns; and (much later) tenant-owned WABAs via Embedded Signup. ⚠ No credit-balance or top-up control until Model A collection exists (ADR-0030) | ours for limits · Tech Provider / Embedded Signup for tenant senders | limits P2 · campaigns P3 · tenant senders P3+ |
| 9 | Alerts | quality drop, template paused, webhook silence, failure-rate spike, 131049 spike, messaging-limit approach | derived from 1/5/6 — plus an out-of-band watchdog workflow, the payments-watchdog.yml pattern applied to the channel that is otherwise its own only witness | P2 |
The three honest answers this hub's spec must carry
"Meta billing/usage visibility where APIs support it" — usage and computed cost: yes, via pricing_analytics. Invoices, payment methods, top-up/recharge: no API exists; module 7 is a runbook by design, and drawing a recharge screen would be designing against a capability Meta does not offer (prepaid balances are a BSP-product concept, not a Meta Cloud API one).
"Customer-specific branding" — in R1 every message is sent by QRSETU's own number and display name. Per-merchant sender branding means per-tenant WABAs/numbers (Tech Provider + Embedded Signup + per-number display-name review), which is real but is a P3+ programme, not a hub setting. What IS available per-merchant from day one: template variables carrying the merchant's name, and per-workspace attribution of every message in the ledger. Full assessment, including why a customer's name may not simply be set as the display name on one of our own numbers, and why own-branding and QRSETU-sold credits are mutually exclusive without Solution Partner status: the tenant model · ADR-0030.
"Retry/error handling" — retry is the dispatcher's job (outbox next_attempt_at, capped backoff); the hub shows it and offers a manual re-drive for terminal failures. A paused template or a 131049 marketing cap is not retried on a timer — it is an alert, because resending into a quality problem deepens it.
What stays out of this hub, deliberately
- Org-admin communication policy (an enterprise customer configuring their messaging) — that is the
/orgportal's job (ADR-0024 oversight model, ADR-0028 trust boundaries). The platform hub administers the platform; conflating the two admin surfaces is the privilege-escalation class CLAUDE.md names. - Merchant self-serve campaign authoring — merchant console scope, entitlement-gated (ADR-0021
entitlementaxis: visible-locked when unentitled), consuming the same backend. - In-app notification preferences — the existing notifications feature owns the in-app half; this hub governs the WhatsApp channel.