Skip to main content

Communication Service — User Stories

Service: communication-service Story prefix: COMMS-US Last updated: 2026-04-17

Stories

COMMS-US-001 — Create a care-team thread

FieldValue
Issue typeStory
SummaryCreate a provider-to-provider thread with participants
Epic linkCOMMS-EPIC-01
StatusTo Do
PriorityMust
Story points3
Labelsservice:communication, type:backend, slice:S1
Componentsmessaging
FR referencesFR-COMMS-MSG-001
Legacy FR refsFR-MSG-001, FR-DCOM-MSG-001
DependenciesIDENT-US-001

User story: As a physician, when I need to coordinate on a patient, I want to open a secure thread with selected colleagues so that we can discuss without insecure channels.

Acceptance criteria (Gherkin):

  • Given I have the DOCTOR role, when I POST /v1/communication/threads with valid participantIds in my tenant, then the server returns 201 and emits communication.thread.created.
  • Given a participantId is in a different tenant, when I POST, then the server returns 403 FORBIDDEN.
  • Given I pass a patientId I cannot read (no chart permission), when I POST, then the server returns 403 FORBIDDEN.

Technical notes: Controller ThreadsController; use case CreateThreadUseCase; RLS on message_threads.

Definition of Done: Unit + integration tests; OpenAPI updated; event schema registered; trace + metric added; docs updated.


COMMS-US-002 — Send message in a thread

FieldValue
Issue typeStory
SummarySend a message with urgency into an existing thread
Epic linkCOMMS-EPIC-01
StatusTo Do
PriorityMust
Story points3
Labelsservice:communication, type:backend, slice:S1
Componentsmessaging, interop
FR referencesFR-COMMS-MSG-003
Legacy FR refsFR-MSG-001, FR-DCOM-MSG-003
DependenciesCOMMS-US-001, INTEROP-US-005

User story: As a thread participant, when I compose a message, I want it persisted and projected to FHIR Communication so downstream systems and chart timelines see it.

Acceptance criteria:

  • Given I am a participant, when I POST a non-empty body with urgency, then the server returns 201 with messageId and emits communication.message.sent.
  • Given the thread is archived, when I POST, then the server returns 409 INVALID_STATE_TRANSITION.
  • Given I repeat with the same Idempotency-Key, when I POST, then the server returns the prior response without creating a duplicate.

Technical notes: Outbox-published event; FHIR write via interop-service in same saga; Idempotency-Key honored.


COMMS-US-003 — Mark messages read (LWW across devices)

FieldValue
Issue typeStory
SummaryIdempotent read receipts with LWW across devices
Epic linkCOMMS-EPIC-01
StatusTo Do
PriorityMust
Story points2
Labelsservice:communication, type:backend, slice:S1
Componentsmessaging
FR referencesFR-COMMS-MSG-004
Legacy FR refsFR-DCOM-MSG-004
DependenciesCOMMS-US-002

User story: As a user reading messages on multiple devices, when I scroll through a thread, I want the latest read time preserved so unread counts are consistent.

Acceptance criteria:

  • Given I POST /read with messageIds, when the request succeeds, then message_read_receipts upserts with read_at.
  • Given another device POSTs older read_at, when the second request arrives, then the newer read_at is retained (LWW).

COMMS-US-004 — Escalate thread

FieldValue
Issue typeStory
SummaryEscalate a thread to on-call routing
Epic linkCOMMS-EPIC-01
StatusTo Do
PriorityShould
Story points3
Labelsservice:communication, type:backend, slice:S1
Componentsmessaging, notifications
FR referencesFR-COMMS-MSG-006
Legacy FR refsFR-MSG-005, FR-DCOM-MSG-006
DependenciesCOMMS-US-007

User story: As a clinician, when a conversation becomes urgent, I want to escalate it so on-call staff get paged immediately.

Acceptance criteria:

  • Given I escalate a thread, when I POST /escalate, then the server emits communication.thread.escalated and queues a high-priority notification intent to the on-call role.
  • Given a role cannot escalate per policy, when they POST, then 403.

COMMS-US-005 — Archive thread

FieldValue
Issue typeStory
SummaryArchive a thread
Epic linkCOMMS-EPIC-01
StatusTo Do
PriorityCould
Story points1
Labelsservice:communication, type:backend
FR referencesFR-COMMS-MSG-005
Legacy FR refsFR-DCOM-MSG-005

User story: As a thread owner, when a topic resolves, I want to archive the thread so it no longer appears in my inbox.

Acceptance criteria:

  • Given an active thread, when I PATCH /archive, then status = archived and new messages rejected.

COMMS-US-006 — Attachment upload with AV scan

FieldValue
Issue typeStory
SummaryUpload attachment with malware scan before attach
Epic linkCOMMS-EPIC-01
StatusTo Do
PriorityShould
Story points5
Labelsservice:communication, type:backend, slice:S2
Componentsmessaging, av-scan
FR referencesFR-COMMS-MSG-008
Legacy FR refsFR-MSG-004, FR-DCOM-ENH-003

User story: As a clinician, when I attach a file, I want it scanned before peers can see it so malware is blocked.

Acceptance criteria:

  • Given a file uploaded, when scan = clean, then attachment becomes attachable to messages.
  • Given scan = infected, when scan completes, then quarantine event emitted and attachment unusable.

COMMS-US-007 — Submit notification intent (internal API)

FieldValue
Issue typeStory
SummaryInternal API for services to request notifications
Epic linkCOMMS-EPIC-02
StatusTo Do
PriorityMust
Story points3
Labelsservice:communication, type:api, slice:S1
FR referencesFR-COMMS-NOTIF-001
Legacy FR refsFR-MSG-012, FR-DCOM-NOTIF-001

User story: As a domain service, when I need to notify a user, I want to post a single intent with category + recipient + template so I don't encode PHI or channel choice.

Acceptance criteria:

  • Given a valid intent with opaque variables, when POST to /notifications/intents, then 202 returned with intentId and server emits .queued.
  • Given duplicate (tenantId, correlationId, channel, recipientRef), when POST repeats, then idempotent — prior intent returned.

COMMS-US-008 — Send SMS via Ghasi-SMS-Gateway

FieldValue
Issue typeStory
SummarySMS dispatch through Ghasi-SMS-Gateway adapter with DLR
Epic linkCOMMS-EPIC-02
StatusTo Do
PriorityMust
Story points5
Labelsservice:communication, type:backend, slice:S1
Componentsadapters
FR referencesFR-COMMS-NOTIF-002, FR-COMMS-NOTIF-004
Legacy FR refsFR-MSG-010

User story: As a platform operator, when intents target Afghan tenants, I want SMS routed through Ghasi-SMS-Gateway with DLR callbacks so delivery is visible.

Acceptance criteria:

  • Given a queued SMS intent for Afghan tenant, when the worker dispatches, then adapter calls Ghasi-SMS-Gateway and records providerMessageId.
  • Given a DLR callback arrives with valid HMAC, when received, then DispatchRecord.outcome upserts to delivered/failed.
  • Given Ghasi-SMS-Gateway is unhealthy, when configured secondary exists, then failover to secondary provider.

COMMS-US-009 — Push notifications (FCM / APNs / WebPush)

FieldValue
Issue typeStory
SummaryPush dispatch without PHI in payload
Epic linkCOMMS-EPIC-02
StatusTo Do
PriorityMust
Story points5
Labelsservice:communication, type:backend, slice:S2
FR referencesFR-COMMS-NOTIF-002
Legacy FR refsFR-MSG-009

User story: As a mobile user, when I receive a notification, I want it to be useful but free of PHI so a glance doesn't expose clinical data.

Acceptance criteria:

  • Given a push intent with opaque variables, when dispatched, then payload contains only category, templateKey, deepLink; zero PHI.
  • Given a device token is invalid, when FCM returns feedback, then token marked inactive and DispatchRecord.outcome=undeliverable.

COMMS-US-010 — Email dispatch with bounce handling

FieldValue
Issue typeStory
SummaryEmail via SES/SendGrid with bounce feedback
Epic linkCOMMS-EPIC-02
StatusTo Do
PriorityMust
Story points3
Labelsservice:communication, type:backend, slice:S1
FR referencesFR-COMMS-NOTIF-002
Legacy FR refsFR-MSG-009

User story: As an operator, when bounce rates spike, I want the system to auto-throttle the affected category so reputation is protected.

Acceptance criteria:

  • Given bounce ratio > 5% over 15 min, when threshold breached, then alert fires and category paused for tenant.

COMMS-US-011 — Category channel policy configuration

FieldValue
Issue typeStory
SummaryTenant-configurable per-category channel allowlist
Epic linkCOMMS-EPIC-02
StatusTo Do
PriorityShould
Story points3
Labelsservice:communication, type:api, slice:S2
FR referencesFR-COMMS-NOTIF-005

User story: As a tenant admin, when I want to disable SMS for marketing, I want to configure allowed channels per category without code changes.

Acceptance criteria:

  • Given a PUT to /config/notifications/channels with valid policy, when accepted, then policy persists and takes effect on next dispatch.

COMMS-US-012 — Create virtual session from scheduling event

FieldValue
Issue typeStory
SummaryConsume SCHEDULING appointment.created (class=VR) to create session idempotently
Epic linkCOMMS-EPIC-03
StatusTo Do
PriorityMust
Story points5
Labelsservice:communication, type:backend, slice:S2
FR referencesFR-COMMS-VC-016
Legacy FR refsFR-DCOM-VC-016

User story: As a scheduler, when I create a virtual visit appointment, I want the session auto-created so the patient and provider can join when the time comes.

Acceptance criteria:

  • Given SCHEDULING.appointment.created with class=VR, when consumed, then at most one VirtualSession exists for that appointmentId in the tenant.
  • Given the same event redelivered, when consumed, then no duplicate session created.

COMMS-US-013 — Issue short-lived join token

FieldValue
Issue typeStory
SummaryOne-time KMS-signed join token with ≤ 5 min TTL
Epic linkCOMMS-EPIC-03
StatusTo Do
PriorityMust
Story points3
Labelsservice:communication, type:backend, slice:S2
FR referencesFR-COMMS-VC-002
Legacy FR refsFR-DCOM-VC-002

User story: As an enrolled participant, when I tap the join link, I want a short-lived token issued so unauthorized parties cannot join.

Acceptance criteria:

  • Given I have a valid JWT and am a session participant, when GET /join-token, then I receive a token with ≤ 5 min TTL.
  • Given the token has already been used, when validated, then the server rejects.

COMMS-US-014 — Waiting-room admission by provider

FieldValue
Issue typeStory
SummaryProvider admits participants from waiting room
Epic linkCOMMS-EPIC-03
StatusTo Do
PriorityMust
Story points2
Labelsservice:communication, type:backend, slice:S2
FR referencesFR-COMMS-VC-003
Legacy FR refsFR-DCOM-VC-003

User story: As a provider, when patients queue in waiting room, I want to admit them deliberately so I control the visit start.

Acceptance criteria:

  • Given a participant is in WAITING state, when provider POSTs /admit/{participantId}, then state transitions to ACTIVE and event emitted.

COMMS-US-015 — End virtual session & emit billing signal

FieldValue
Issue typeStory
SummarySession end emits duration + billing chargeable event
Epic linkCOMMS-EPIC-03
StatusTo Do
PriorityShould
Story points3
Labelsservice:communication, type:backend, slice:S2
FR referencesFR-COMMS-VC-004
Legacy FR refsFR-DCOM-VC-004

User story: As a billing service, when a VC session ends, I want a chargeable event so billing can post charges automatically.

Acceptance criteria:

  • Given session ended with duration > threshold, when ended, then virtual_session.billing.chargeable emitted.

COMMS-US-016 — Fallback-to-messaging on VC failure

FieldValue
Issue typeStory
SummaryAuto-spawn secure thread when virtual session fails
Epic linkCOMMS-EPIC-04
StatusTo Do
PriorityShould
Story points5
Labelsservice:communication, type:backend, slice:S3
FR referencesFR-COMMS-VC-023
Legacy FR refsFR-DCOM-VC-023

User story: As a patient whose virtual session drops, I want the system to spin up a secure message thread with my provider so we don't lose continuity.

Acceptance criteria:

  • Given session enters FAILED state, when failure detected, then new thread created linking all participants and session id.
  • Given participants share no thread yet, when fallback spawns, then they are all added and a seed message prompts continuation.

COMMS-US-017 — Critical-result fan-out

FieldValue
Issue typeStory
SummaryCritical-result-flagged events dispatch push + SMS + in-app
Epic linkCOMMS-EPIC-04
StatusTo Do
PriorityMust
Story points3
Labelsservice:communication, slice:S3
FR referencesFR-COMMS-NOTIF-003
Legacy FR refsFR-MSG-011

User story: As an on-call physician, when a critical result is released, I want immediate push + SMS + in-app so I don't miss it.

Acceptance criteria:

  • Given laboratory.result.critical.flagged, when consumed, then three intents dispatch in parallel on distinct channels.

COMMS-US-018 — Identity-bound PATIENT access

FieldValue
Issue typeStory
SummaryPATIENT role queries only their own threads/sessions
Epic linkCOMMS-EPIC-05
StatusTo Do
PriorityMust
Story points2
Labelsservice:communication, type:api, slice:S1
FR referencesFR-COMMS-018
Legacy FR refsFR-DCOM-018

User story: As a patient, when I query my communications, I want only my data so nobody else is exposed.

Acceptance criteria:

  • Given PATIENT role JWT, when GET /threads or /virtual-sessions, then results are scoped to the caller's patientId only.

COMMS-US-019 — Module entitlement enforcement

FieldValue
Issue typeStory
Summary403 MODULE_NOT_LICENSED on unlicensed tenants
Epic linkCOMMS-EPIC-05
StatusTo Do
PriorityMust
Story points2
Labelsservice:communication, slice:S1
FR referencesFR-COMMS-LIC-001
Legacy FR refsFR-MSG-008, FR-DCOM-LIC-001

User story: As a tenant without the messaging license, when my users try to call the API, I want a clear 403 so I know to upgrade.

Acceptance criteria:

  • Given a tenant without engage.messaging entitlement, when any /v1/communication/threads* called, then 403 MODULE_NOT_LICENSED.

COMMS-US-020 — GDPR erasure participation

FieldValue
Issue typeStory
SummaryAnonymize messages and drop dispatch PII on erasure
Epic linkCOMMS-EPIC-05
StatusTo Do
PriorityMust
Story points5
Labelsservice:communication, slice:S3
FR referencesFR-COMMS-019
Legacy FR refsFR-DCOM-019

User story: As a data subject, when I request erasure, I want my content removed while legal audit preserved.

Acceptance criteria:

  • Given identity.user.gdpr_erasure_requested, when consumed, then message bodies redacted and dispatch_records recipient_ref removed within SLA; legal-hold overrides.

COMMS-US-021 — Per-channel dispatch status query

FieldValue
Issue typeStory
SummaryQuery outcome per channel by correlation id
Epic linkCOMMS-EPIC-06
StatusTo Do
PriorityShould
Story points3
Labelsservice:communication, type:api, slice:S4
FR referencesFR-COMMS-ENH-001
Legacy FR refsFR-DCOM-ENH-001

User story: As support, when a user says "I never got the SMS", I want to look up the correlation id and see which channel succeeded.

Acceptance criteria:

  • Given a correlation id, when GET /dispatch-status, then per-channel outcomes are returned with timestamps.

COMMS-US-022 — Notification p95 latency SLO dashboards

FieldValue
Issue typeStory
SummaryDashboards + alerts for dispatch p95 by channel
Epic linkCOMMS-EPIC-06
StatusTo Do
PriorityShould
Story points3
Labelsservice:communication, type:observability, slice:S4
FR referencesNFR-COMMS-ENH-001
Legacy FR refsNFR-DCOM-ENH-001

User story: As SRE, when p95 drifts past SLO, I want an alert so I act before customers escalate.

Acceptance criteria:

  • Given p95 dispatch latency > SLO for 10 min, when the condition holds, then PagerDuty fires with runbook link.