Test Plan: Communications & Broadcasts

♻️ Reconciled against current code — 2026-07-01. The biggest correction: "no test-send" and "no merge tags" are now FALSE — both shipped and are wired end-to-end (Send-Test #433, merge tags #252, both reconfirmed hardened in the Jun 25 release #449). The composer body is also no longer a raw <textarea> — it's a rich-text editor (#488, Jun 29 release) with an HTML-source toggle. See the green banner below for the full list. Everything else in the 2026-06-10/11/16 corrections (segment filters, scheduling, unsubscribe) still holds — see below.
🆕 New on stage — 2026-06-19 → 2026-07-01.
Earlier corrections (2026-06-10 → 2026-06-16, still accurate): the only segment filters are status / roles / membershipTypeId (no "unpaid invoices" filter) — but an invalid/unknown filter is now rejected with 400, not silently dropped (see the green banner above, #449). Scheduled sends are still non-functional (stored but never fired).

2026-06-11 update (#226 / #231 closed, live on stage): the member email-preferences screen is now reachable as the Notifications tab at /account/notifications (My Account → Notifications), and the backend 403 for members is fixed (user?.memberId). ⚠️ Note a remaining FE↔BE field-name mismatch: see §6.

2026-06-16 update: there is now a segment management UI at /admin/segments (#224 — supersedes the old "no segment-builder UI; pick from a dropdown only" note: you can now create/edit segments in the UI). The public unsubscribe flow is now functional (#257 — HMAC-token route registered before auth so the link resolves for logged-out recipients; pairs with the Jun 15 #254 tenant-param fix). See the Jun 16 release note.

1. Introduction

The Communications module is admin-to-member email broadcasts, addressed to a reusable segment of members, plus a public unsubscribe flow and a per-member email-preferences screen (the Notifications tab under My Account, reachable as of #226). There is no SMS and no campaign/drip automation. (A separate admin in-app bell/notification feature for sign-ups & event registrations shipped this week — #186/#217/#230/#233 — but that lives under Admin/Events, not this module.)

2. Where it lives (UI)

3. Endpoints (current code)

Mounted on the communicationsRouter at /api (auth-service/src/server.ts); handlers in auth-service/src/handlers/broadcastHandlers.ts & segmentHandlers.ts.

MethodEndpointPermissionNotes
GET/api/broadcastsCOMMUNICATIONS:VIEW:broadcastsQuery: status, limit (50), offset. Returns {items,total,limit,offset}.
POST/api/broadcastsCOMMUNICATIONS:CREATE:broadcastsBody {subject, body, plainText?, segmentId?, tags?, scheduledAt?}. Created as DRAFT.
GET/api/broadcasts/:idCOMMUNICATIONS:VIEW:broadcastsIncludes first 100 recipients.
PUT/api/broadcasts/:idCOMMUNICATIONS:EDIT:broadcastsEditable only while DRAFT or SCHEDULED.
DELETE/api/broadcasts/:idCOMMUNICATIONS:DELETE:broadcastsDeletes only while DRAFT.
GET/api/broadcasts/:id/previewCOMMUNICATIONS:VIEW:broadcastsReturns {html, plainText, subject} (unsubscribe link = #preview placeholder).
POST/api/broadcasts/:id/sendCOMMUNICATIONS:EDIT:broadcastsSends now (see §4). This is the only way to send — there is no scheduled trigger.
GET / POST/api/segmentsVIEW / CREATE:broadcastsGET auto-seeds 3 system segments on first call.
GET / PUT / DELETE/api/segments/:idVIEW / EDIT / DELETE:broadcastsSystem segments (isSystem:true) cannot be edited or deleted.
POST/api/segments/previewCOMMUNICATIONS:VIEW:broadcastsPreview member count for a filter set.
GET / PUT/api/email-preferencesrequireMemberId()Per-member toggles; UI = Notifications tab (#226). Backend persists paymentReminders, eventReminders, membershipUpdates, broadcasts, unsubscribedAll.
POST/api/unsubscribe, /api/resubscribepublicSigned-token based (#254/#257): body {token, category}memberId/tenantId are carried inside the HMAC token, no longer query/body params. Route registered before auth middleware so logged-out links resolve.

All four RBAC permissions use the resource broadcasts — even segment routes. Roles that hold them: ADMIN (COMMUNICATIONS:*:*), EVENT_MANAGER, COMMUNICATIONS_MANAGER.

4. Broadcast lifecycle & send mechanism

5. Segments (the real model)

A segment is a saved filter over the Member table. The API accepts exactly three filter fields (segmentHandlers.ts SegmentFilters):

FilterTypeBehaviour
statusstring[]Member.status IN [...]. Defaults to ["ACTIVE"] if omitted.
rolesstring[]Member.roles hasSome [...]. Optional.
membershipTypeIdstringExact match. Optional.

There is NO billing/payment filter. No "unpaid invoices", no dues state, no membershipExpiresAt, no last-login. Any TC asking to segment by invoice/payment status is testing a feature that doesn't exist.

3 auto-seeded system segments (created on first GET /api/segments, not by the DB seed): All Active Members (status:["ACTIVE"]), Administrators (status:["ACTIVE"], roles:["ADMIN"]), Pending Members (status:["PENDING_PAYMENT","PENDING_APPROVAL"]). These are read-only. The seed script creates no broadcasts and no segments.

6. Unsubscribe & preferences

7. Environment (staging)

8. Do NOT test (features that don't exist)

9. Test Deliverables

10. Risk Areas