Table of Contents
14 — Coach–family messaging
Technical reference. For task-based help, see the corresponding user guide.
Documentation status: complete for the current implementation
Reviewed: 2026-07-21
Rollout status: implemented but explicitly gated
Primary users: a linked family and its genuine assigned coach; owner moderation is operational
Surfaces: mobile web app, coach admin portal and platform API
Purpose
Coach–family messaging provides one private conversation for each coach assignment. It supports inbox previews, unread state, paginated history, idempotent sending, participant pause/push preferences, reporting, owner moderation, relationship-aware history and automatic retention.
The feature is implemented in all three application layers, but repository code alone does not prove that it is enabled in a deployed environment. Server capability, organization canary, beta UI and client-build gates must all agree before a person sees or uses chat.
User outcomes
| User | Outcome |
|---|---|
| Family member with current support | Opt into the conversation, exchange messages with the assigned coach, manage push, mark messages read and report a concern. |
| Genuine assigned coach | Search/open family conversations in mobile or the coach portal, send and retry safely, manage personal settings and see unread totals. |
| Family after support ends | Read retained history until its displayed expiry, but not send new messages. |
| Former coach | Lose participant access immediately when the relationship ends. |
| Super-admin owner | Review reported conversations, moderate chat and operate retention through owner-only tooling. |
Entry points
| Surface | Entry | Function |
|---|---|---|
| Family mobile | Family chat inbox/thread | List coach conversations and open history. |
| Coach mobile | Coach dashboard chat inbox | List family conversations and unread totals. |
| Coach portal | Messages | Search/paginate conversations and open a family thread. |
| Family/caseload context | Message action | Open the single conversation associated with that assignment. |
| Owner portal | Chat moderation | Filter conversations/reports, inspect a thread and disable or reopen it. |
Rollout and capability gates
Chat is available only when every applicable layer permits it:
| Gate | Current contract |
|---|---|
| Server core | COACH_FAMILY_CHAT_ENABLED must be explicitly 1 or true; the default is off. |
| Organization canary | COACH_FAMILY_CHAT_CANARY_ORGANIZATION_IDS can restrict enabled chat to named organizations. An empty set means all eligible organizations after core enablement. |
| Server beta UI | A beta restriction is on by default and limits coach UI capability to a configured pilot email allowlist. It can be explicitly disabled or replaced through environment configuration. |
| Client UI | VITE_COACH_FAMILY_CHAT_ENABLED must be explicitly enabled in both mobile and coach-portal builds. |
| Push server | COACH_FAMILY_CHAT_PUSH_ENABLED must be explicitly enabled in addition to core chat. |
| Push client | Mobile also requires VITE_COACH_FAMILY_CHAT_PUSH_ENABLED. |
| Native compatibility | Chat push requires native wrapper version 4.7.0 or newer. |
| Retention worker | COACH_FAMILY_CHAT_RETENTION_JOB_ENABLED explicitly enables scheduled former-conversation purge processing. |
GET /auth/me supplies role-specific family, coach and owner capability values. A disabled role includes feature_off, not_in_canary or role_unavailable. Push capability separately reports whether the server enables it and the minimum wrapper version.
The application must treat server capability as authoritative. A visible client control does not bypass a disabled server capability, relationship state or moderation state.
Conversation and message model
- There is one conversation per family assignment, not one global thread per pair of users.
- Conversation lifecycle is
active,formerorpurged. - Moderation is
openordisabled. - The participant-facing effective state is
active,participant_disabled,relationship_unavailable,owner_disabledorformer. - Each participant owns
chatEnabled,pushEnabledand a last-read sequence independently. - Messages receive a monotonically increasing sequence in the conversation.
- A message body is trimmed, non-empty and limited to 4,000 Unicode code points.
clientMessageIdis a UUID idempotency key. Retrying the same send returns the existing message rather than creating a duplicate.
Functional flow
1. Inbox and unread state
- The inbox returns the other participant's display name, last-message preview/time, unread count, lifecycle/effective state, personal settings and any history-expiry date.
- Conversation pages default to 30 and permit up to 100 records with an opaque cursor.
- The coach portal supports local search over its loaded conversation presentation and paginated retrieval.
- A separate unread endpoint allows the navigation badge to update without loading every thread.
- Conversation resource changes trigger an opaque realtime invalidation and refetch. Mobile also uses a periodic safety refresh while appropriate.
2. Open and read history
- A thread requests the newest 50 messages by default and can request up to 100.
- Older pages use
beforeSequence, avoiding timestamp ambiguity. - Read state advances through an explicit
throughMessageId. It does not infer that every delivered push was read. - Current participants see the composer only when
effectiveStateisactive. - Former/read-only states retain clear copy explaining why sending is unavailable and, for the family, when history expires.
3. Send, optimistic state and retry
- The client creates a UUID, adds a pending message and sends the trimmed body.
- A successful first creation returns HTTP 201. An idempotent replay returns HTTP 200 with the same durable message.
- A failed optimistic message remains identifiable and can be retried using the same UUID.
- The platform serializes conversation sequence allocation and rechecks access inside the write path.
- Server limits are 20 messages per sender per minute and 300 messages per conversation per hour.
4. Participant settings
- Either participant can disable their own chat participation without deleting the conversation.
- New messages require a current eligible relationship, open owner moderation and both participants enabled.
- Personal push can be changed independently, but delivery also requires effective active chat and server/client/native push eligibility.
- Disabling chat alone does not convert the relationship to
formerand does not start the six-month former-history clock.
5. Reports and moderation
- Either participant can report the conversation or a specific message.
- Categories are
harassment,privacyandother; optional detail is limited to 1,000 Unicode code points. clientReportIdmakes retries idempotent. A reporter is limited to 10 reports per day.- Reports move through
open,reviewingandresolvedin owner tooling. - An owner can disable a conversation with a moderation reason or reopen it.
owner_disabledprevents participant sends while preserving the evidence/history needed for review.
6. Generic push
- Push content is deliberately non-sensitive: New message and Open Littles Baby Tracker to read it.
- The payload routes back to chat but does not contain sender name or message body.
- Delivery is rechecked at dispatch for current access, personal chat/push settings, moderation and a compatible registered device.
- Offline or failed push does not change the durable message or unread state; opening the app reloads from the API.
Current, former and purged lifecycle
| State | Family access | Coach access | Sending | Retention |
|---|---|---|---|---|
| Current and effective active | Read | Read | Both can send | No former deadline |
| Current but participant/owner disabled | Read | Read | Disabled | Conversation retained |
| Relationship unavailable | Depends on trusted relationship resolution | Denied where record access is unavailable | Disabled | Lifecycle transition is reconciled server-side |
| Former | Family can read retained history | Former coach is denied immediately | Disabled | historyExpiresAt / purgeAfter is six calendar months after formerAt |
| Purged | No message content | No message content | Disabled | Conversation content has been removed globally |
A relationship becomes former when the assignment is revoked/transferred or support actually ends. Pausing personal chat is not the same event. If the same assignment becomes eligible again before purge, reconciliation may make it available again; after purge, deleted message content is not recoverable. Account deletion can require earlier purge.
The deadline uses a database interval of six calendar months in UTC, not a fixed 180-day duration. The former coach does not retain participant-history access during that window; family and authorized owner workflows are the intended retained readers.
Platform API contract
Participant routes exist under the actor-specific prefix: /families/me/chat for family and /coach/chat for coach.
| Method and relative path | Role |
|---|---|
GET /conversations | Cursor-paginated inbox summaries. |
GET /unread | Total unread count for navigation badges. |
GET /conversations/:conversationId/messages | Paginated message history using beforeSequence. |
POST /conversations/:conversationId/messages | Idempotently send { clientMessageId, body }. |
POST /conversations/:conversationId/read | Advance read cursor through throughMessageId. |
PATCH /conversations/:conversationId/settings | Change personal chat and/or push enablement. |
POST /conversations/:conversationId/reports | Idempotently report a conversation or message. |
GET /coach/families/:familyId/chat/conversation | Resolve the assignment's conversation from a family context. |
Owner-only routes add conversation/report filtering, detail inspection, moderation and retention operations. They are not available through coach or family sessions.
Authorization and privacy rules
- Family and coach routes derive the participant from the trusted session and verify membership in the conversation.
- Coach access requires a genuine coach session. Synthetic owner view-as and mobile coach view-as contexts do not become chat participants and cannot send.
- Active sending requires assignment, organization, membership, billing and support eligibility in addition to both participant settings and moderation.
- A former coach loses access immediately even though family history remains temporarily available.
- Inbox preview and generic push are intentionally bounded; push never exposes message text or family/coach identity on the lock screen.
- Reports and owner inspection are privileged operational data and are auditable.
- Purge removes conversation content rather than merely hiding it from participant clients.
Acceptance checklist
- Chat remains absent/denied unless server capability and the applicable client gate are enabled.
- One assignment resolves to one conversation and participant access cannot cross assignments.
- Inbox unread totals, paginated history and explicit read cursors remain consistent after realtime refetch.
- Retrying a send with the same
clientMessageIdnever creates a duplicate. - Sending is permitted only in
activeeffective state and rate limits are enforced server-side. - Participants can pause themselves and manage push without deleting history.
- Report creation is idempotent, category/detail validated and owner moderation can disable sending.
- Former family history displays its deadline; the former coach is denied and no participant can send.
- The retention worker purges expired former conversations, and push payloads remain generic.
Source evidence
Mobile web
mobile-web/src/features/coach-family-chat/mobile-web/src/App.tsxmobile-web/src/feature-flags.ts
Coach portal
admin-web/src/features/coach-family-chat/admin-web/src/feature-flags.ts
Platform and contracts
platform/apps/api/src/routes/coach-family-chat-routes.tsplatform/apps/api/src/routes/family-chat-routes.tsplatform/apps/api/src/routes/coach-family-chat-route-helpers.tsplatform/apps/api/src/services/coach-family-chat-service.tsplatform/apps/api/src/services/coach-family-chat-retention-service.tsplatform/apps/api/src/services/coach-family-chat-push-service.tsplatform/apps/api/src/feature-flags.tsplatform/packages/shared/src/coach-family-chat.tsplatform/docs/multi-tenancy/coach-family-chat/
