---
module: profile.inbox
title: Inbox
tab: Claude
order: 40
audience: dev
---

Terse agent reference. Owner service: **`hiveops-messaging`** (port 8086, DB `hiveiq_messaging`). UI: `hiveops-profile/frontend/src/components/Profile/Inbox.svelte`.

## Ownership
- Message store/CRUD → `hiveops-messaging` (`MessageController` `@RequestMapping("/api/messaging/messages")`).
- Recipient picker → `hiveops-auth` `GET /auth/api/admin/users`.
- Institution dropdown → `hiveops-mgmt` `GET /api/v1/msp/institutions`.
- Do NOT add message tables/endpoints to profile, auth, or incident — messaging owns messages.

## Endpoints (backend paths; SPA hits `/messaging/...`, nginx prepends `/api`)
| METHOD | Path | Notes |
|---|---|---|
| GET | `/api/messaging/messages?page&size&sort` | paged inbox (Page<MessageResponse>) |
| GET | `/api/messaging/messages/sent` | sent list (sender_user_id = you) |
| GET | `/api/messaging/messages/unread` | unread list |
| GET | `/api/messaging/messages/unread/count` | `{unreadCount}`; cached per userId |
| POST | `/api/messaging/messages` | send; body `SendMessageRequest`; 201 |
| PATCH | `/api/messaging/messages/{id}/read` | mark one read; 204 |
| PATCH | `/api/messaging/messages/read-all` | mark all read; 204 |
| DELETE | `/api/messaging/messages/{id}` | per-user soft-delete; 204 |
| GET | `/auth/api/admin/users` | recipients; `authenticated()` (not admin-gated) |
| GET | `/api/v1/msp/institutions` | needs `MSP_ADMIN`/`BCOS_ADMIN` |

## Send payload (`SendMessageRequest`)
- `type`: `DIRECT` | `BROADCAST` (`ACTION` used internally / via Kafka, not user-composed).
- `recipientUserId` required unless BROADCAST.
- `institutionKey` optional (BROADCAST scope; blank/absent = global).
- `subject?`, `body` (required), `metadata?`, `recipientGroupId?` (fan-out).

## Tables (`hiveiq_messaging`)
- `messages` — `id,uuid,type,sender_user_id,sender_email,recipient_user_id,institution_key,subject,body,metadata(jsonb),created_at`.
- `message_reads` — unique `(message_id,user_id)`, `read_at`.
- `message_deletes` — PK `(message_id,user_id)`, `deleted_at`.

## Kafka topics
- `hiveops.messages` — messaging publishes after REST send AND consumes external events (group `hiveops-messaging`); self-events filtered by `sourceService`.
- `hiveops.reports.completed` — consumed → DIRECT messages to `recipientUserIds`.

## Roles
- BROADCAST requires `MessagingPrincipal.isAdmin()` = role ∈ {`ADMIN`,`MSP_ADMIN`,`BCOS_ADMIN`} (case-insensitive), enforced in controller (manual check, not `@PreAuthorize`).
- DIRECT/read/delete: any authenticated user with access (recipient, sender, or matching-institution broadcast) via `verifyMessageAccess`.
- Principal claims: `sub`→userId, `email`, `role`, `institutionKey`. Shared `JWT_SECRET` across auth/mgmt/messaging.

## Gotchas
- SPA base is `/messaging/messages`; real controller is `/api/messaging/messages` — nginx rewrites the `/api` segment. Don't assume the SPA path == controller path.
- Institution-scoped broadcasts appear ONLY when JWT `institutionKey` matches `messages.institution_key`; global broadcasts have `institution_key = NULL`.
- Delete = soft + per-user (writes `message_deletes`, also marks read); the `messages` row and other users are untouched.
- Inbox query hardcodes `ORDER BY created_at DESC` — the SPA's `sort` param is effectively redundant.
- unread count is `@Cacheable("unreadCount", key=userId)`, evicted on read/read-all/delete.
- `loadRecipients()` and `loadInstitutions()` fail silently in the SPA (non-fatal catch).

## Do NOT
- Do NOT create message endpoints/tables outside `hiveops-messaging`.
- Do NOT hard-delete from `messages` to "remove" a user's message — use per-user soft-delete.
- Do NOT publish to `hiveops.messages` from messaging's own REST path without `sourceService="hiveops-messaging"` (consumer would double-persist).
- Do NOT expect BROADCAST to carry `recipient_user_id` (always NULL).
- Do NOT gate the recipient picker as admin-only — `/auth/api/admin/users` GET is `authenticated()`.
- Do NOT call `/api/v1/msp/institutions` for non-admins (403).
