---
module: profile.inbox
title: Inbox
tab: Architect
order: 30
audience: dev
---

How the Profile **Inbox** is built and how data flows. The feature spans three services: the SPA (`hiveops-profile`), the message store (`hiveops-messaging`), plus two read-only lookups (`hiveops-auth`, `hiveops-mgmt`). See [platform.service-ownership].

## Services involved
| Concern | Service | Where |
|---|---|---|
| UI (view/compose/tabs) | `hiveops-profile` | `frontend/src/components/Profile/Inbox.svelte` |
| Message CRUD + inbox/sent/read/delete | `hiveops-messaging` (8086, `hiveiq_messaging`) | `MessageController` → `MessageService` |
| Recipient picker (`GET /auth/api/admin/users`) | `hiveops-auth` | `UserAdminController` |
| Institution dropdown (`GET /api/v1/msp/institutions`) | `hiveops-mgmt` | `MspController` |

Every call carries the same JWT (Bearer). Messaging validates it with `hiveops-security-common`'s `JwtTokenValidator` and the shared `JWT_SECRET`, building a `MessagingPrincipal(userId, email, role, institutionKey)` from the `sub` / `email` / `role` / `institutionKey` claims (`JwtAuthenticationFilter`).

## Read path (inbox / sent)
1. SPA calls `GET /messaging/messages?page&size&sort=createdAt,desc` (nginx rewrites `/messaging/` → `/api/messaging/`).
2. `MessageService.getInbox(userId, institutionKey, pageable)` runs `MessageRepository.findInboxForUser`, which returns rows where:
   - `type <> BROADCAST AND recipient_user_id = userId` (DIRECT / ACTION addressed to you), **or**
   - `type = BROADCAST AND institution_key IS NULL` (global), **or**
   - `type = BROADCAST AND institution_key = :institutionKey` (scoped to your institution),
   - excluding any `message_deletes` rows for you, newest first.
3. Per-row read state is resolved by loading the user's `message_reads` ids once and setting `MessageResponse.read`.
4. **Sent** (`GET /messaging/messages/sent`) → `findSentByUser` where `sender_user_id = userId`, minus your soft-deletes; always flagged read.

## Write path (compose) — REST → DB → Kafka
`MessageService.sendMessage()` is the executor→store→publish pattern (see [platform.kafka]):
1. Validate (DIRECT requires `recipientUserId`; BROADCAST allowed only if `principal.isAdmin()`).
2. **Persist first** — save the `messages` row (scoped broadcasts store `institution_key`).
3. **Then publish** a `MessageEvent` to Kafka topic **`hiveops.messages`** with `sourceService = "hiveops-messaging"`.
4. Direct-to-many: the SPA fires one POST per selected recipient (`Promise.all`), so each becomes its own `messages` row.

## Inbound Kafka (external ACTION / system messages)
- `MessageConsumer` (`@KafkaListener` on `hiveops.messages`, group `hiveops-messaging`) consumes events. It **skips events where `sourceService = "hiveops-messaging"`** (already persisted via REST) to avoid double-writes, then calls `persistFromEvent()` to insert a `messages` row (typically `type=ACTION`). This is how other services (e.g. incident alerts) drop notifications into a user's inbox.
- `ReportCompletedConsumer` (`@KafkaListener` on `hiveops.reports.completed`) fans a completed report out to `recipientUserIds` as `DIRECT` messages — same store, surfaces in this same inbox.
- Group fan-out: if an event/request carries `recipientGroupId`, the service expands it to member user-ids (`ContactGroup` / `ContactGroupMember`) and writes one row per member.

## DB tables (`hiveiq_messaging`)
| Table | Role | Key columns |
|---|---|---|
| `messages` | canonical message | `id`, `uuid` (unique), `type` (DIRECT\|BROADCAST\|ACTION), `sender_user_id`, `sender_email`, `recipient_user_id` (NULL for BROADCAST), `institution_key` (broadcast scope), `subject`, `body`, `metadata` (jsonb), `created_at` |
| `message_reads` | per-user read state | unique `(message_id, user_id)`, `read_at` |
| `message_deletes` | per-user soft-delete | composite PK `(message_id, user_id)`, `deleted_at` |

Read and delete are modeled as **side tables**, never mutations of `messages` — the same broadcast row is independently read/deleted per user. See [platform.data-architecture].

## Kafka topics
| Topic | Direction | Consumer group | Purpose |
|---|---|---|---|
| `hiveops.messages` | publish + consume | `hiveops-messaging` | outbound after REST send; inbound external ACTION events (self-filtered by `sourceService`) |
| `hiveops.reports.completed` | consume | (report-completed listener) | report-run notifications → DIRECT inbox messages |

## Cross-links
[platform.service-ownership] · [platform.kafka] · [platform.data-architecture]
