# Communication & Notifications — BAMS Legal

> Firm-wide announcements, internal messaging, matter discussions, the
> notification centre, and an SMS channel. Staff-only — the M11 portal has its
> own separate, much narrower request/notify surface.

---

## Table of Contents

1. [Announcement](#announcement)
2. [Message Thread](#message-thread)
3. [Notification](#notification)
4. [Daily Digest](#daily-digest)
5. [Notes & Guidelines](#notes--guidelines)

---

## Announcement

### Schema

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `id` | Integer | Auto | — | Primary key |
| `ulid` | ULID | Auto | — | Public identifier |
| `firm_id` | FK | Yes | — | |
| `title` | String | Yes | — | |
| `body` | Text | Yes | — | |
| `audience` | String | No | `all` | `all`, `role:{name}`, `office:{id}` |
| `requires_acknowledgement` | Boolean | No | `false` | |
| `pinned` | Boolean | No | `false` | |
| `publish_at` | Timestamp | No | `null` | |
| `expires_at` | Timestamp | No | `null` | |
| `status` | Enum | No | `draft` | `draft`, `published` |

### Schema (Read receipt)

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `announcement_id` | FK | Yes | — | |
| `user_id` | FK | Yes | — | |
| `read_at` | Timestamp | Yes | — | |
| `acknowledged_at` | Timestamp | No | `null` | |

### Admin Routes

**Base:** `/admin/announcements` · **Auth:** session, permission `announcements.manage`

| Method | Endpoint | Description |
|---|---|---|
| `GET` | `/admin/announcements` | List |
| `POST` | `/admin/announcements` | Create (as draft) |
| `PATCH` | `/admin/announcements/:id/publish` | Publish — resolves audience, fires notifications |
| `POST` | `/admin/announcements/:id/acknowledge` | Any authenticated user acknowledges |
| `GET` | `/admin/announcements/:id/readers` | Read/ack matrix (who has/hasn't) |

### Request & Response Examples

**Publish** — `PATCH /admin/announcements/:id/publish`

```json
{ "success": true, "data": { "notified_count": 24 }, "message": "Announcement published to 24 users." }
```

**Readers matrix** — `GET /admin/announcements/:id/readers`

```json
{
  "success": true,
  "data": {
    "total_audience": 24, "read": 18, "acknowledged": 15,
    "pending": [{ "user_id": 9, "name": "Femi Okonkwo" }]
  }
}
```

### Validation Rules

| Field | Rules |
|---|---|
| `title` / `body` | Required. |
| `audience` | Must resolve to at least one active user, or publish is rejected. |

### Notes & Guidelines

- Pinned, unexpired announcements show as a dismissible banner on the
  dashboard; `requires_acknowledgement = true` blocks via a modal until the
  user explicitly acknowledges — dismissing the banner alone does not count.

---

## Message Thread

### Schema (Thread)

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `id` | Integer | Auto | — | Primary key |
| `firm_id` | FK | Yes | — | |
| `subject` | String | No | `null` | |
| `created_by` | FK (User) | Yes | — | |

### Schema (Message)

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `id` | Integer | Auto | — | Primary key |
| `thread_id` | FK | Yes | — | |
| `user_id` | FK | Yes | — | |
| `body` | Text | Yes | — | |
| `attachment_path` | String | No | `null` | |

### Admin Routes

**Base:** `/admin/messages` · **Auth:** session, any authenticated staff user

| Method | Endpoint | Description |
|---|---|---|
| `GET` | `/admin/messages` | Inbox: threads the user participates in |
| `POST` | `/admin/messages` | Start a new thread |
| `GET` | `/admin/messages/:threadId` | Get thread + updates `last_read_at` |
| `POST` | `/admin/messages/:threadId/reply` | Reply |
| `GET` | `/admin/messages/unread-count` | JSON badge count (polled every 60s) |

### Request & Response Examples

**Start a thread** — `POST /admin/messages`

```json
{ "participant_ids": [12, 9], "subject": "Zenith matter — filing question", "body": "Can you confirm the filing fee for this suit?" }
```

```json
{ "success": true, "data": { "thread_id": 55 }, "message": "Message sent." }
```

### Validation Rules

| Field | Rules |
|---|---|
| `participant_ids` | Required, min 1, all must be active users in the same firm. |
| `body` | Required, min 1 character. |

### Notes & Guidelines

- No websockets in v1 — unread count is polled, not pushed. Simple and secure
  by default; revisit only if usage demands real-time delivery.

---

## Notification

Built on Laravel's native database + mail (+ optional SMS) notification
channels — one row per user per event, not a bespoke table design.

### Admin Routes

**Base:** `/admin/notifications` · **Auth:** session, own notifications only

| Method | Endpoint | Description |
|---|---|---|
| `GET` | `/admin/notifications` | List, grouped by type, unread filter |
| `POST` | `/admin/notifications/mark-all-read` | Mark all as read |

### Notes & Guidelines

- Grouped by type with icons: hearing, deadline, task, fee approval, message,
  announcement.
- The SMS channel is feature-flagged (`feature('sms')`) — used for hearing
  reminders (opt-in per user) and client-facing hearing-date notices (opt-in
  per client, sent via a separate mechanism, not this endpoint). Every SMS send
  is logged for cost tracking and audit.

---

## Daily Digest

Not an API resource — a scheduled job (`legal:daily-digest`, 07:00) that emails
each user a single consolidated summary of their own hearings today, tasks due
today, and pending approvals.

### Notes & Guidelines

- **Never sent empty** — a user with nothing pending gets no email.
- **Never cross-contaminated** — the digest for User A contains only User A's
  items, verified with an explicit test rather than assumed from the query shape.

---

## Notes & Guidelines (module-wide)

- Matter discussion comments (`matter_comments`, defined in M03) are notified
  through this module's pipeline — a new comment fires a notification to the
  matter's team excluding the commenter.
- Nothing in this module has a portal-facing route. Client-side requests and
  messages are handled entirely by M11's own `portal_requests` model, which is
  intentionally simpler (no threads, no announcements) and staff-actioned only.
