# Activity and mailbox delivery

Fetch relationship events for display or delivery and acknowledge each event
explicitly after your application reaches its chosen durable delivery point.
The receipt means your application reported delivery; it does not independently
prove a human read the message.

## Routes and authority

All routes use POST under `https://api.relationship.service.m7.org/v1/` and
require the [Bearer and workspace contract](https://m7.org/docs/api/api.relationship.service.m7.org/authorization.md).

| Route | Required fields | Optional fields | Capability and result inside `data` |
| --- | --- | --- | --- |
| `/projects/activity/list` | `project_id` | `subject_type`, `space_id`, `subspace_id`, `count`, `offset` | `relationship.list`; `project_id`, `items`, `next_offset`. |
| `/mailbox/list` | `project_id` | `principal`, `subject_type`, `space_id`, `subspace_id`, `search`, `state`, `count`, `offset` | `mailbox.notifications`; mailbox page. |
| `/mailbox/view` | `project_id`, `principal` | Same list filters except `principal` is required | `mailbox.notifications`; mailbox page. |
| `/mailbox/ack` | `project_id`, `principal`, `entry_id` | — | `mailbox.notifications`; `project_id`, `entry_id`, `principal`, `acknowledged_at`. |

All IDs are non-nil UUID strings. `principal` uses the exact
[namespace and principal_id object](https://m7.org/docs/api/api.relationship.service.m7.org/scopes.md#principal-reference). Your
application must authenticate its user and select the correct principal.
Knowing a recipient ID does not establish the user's authority.

## Filters and page results

| Field | Contract |
| --- | --- |
| `subject_type` | `space`, `subspace`, `relationship`, `invitation`, `application` or `connection`; omit for all types. |
| `space_id`, `subspace_id` | Optional exact ID filters; omission leaves that dimension unfiltered. |
| `search` | Mailbox-only UTF-8 string, at most 255 bytes; case-insensitive literal substring, trimmed before use. Empty means no search. |
| `state` | Mailbox-only `all`, `unacknowledged` or `acknowledged`. `/list` defaults to `all`; `/view` defaults to `unacknowledged`. Without a principal only `all` is valid. |
| `count`, `offset` | Integer count 1–200 and nonnegative offset at most `PHP_INT_MAX - 201`. Mailbox defaults: 50/0; activity defaults: 100/0. |

Mailbox pages return `project`, `project_id`, nullable `principal`, `items`,
integer `total_count`, boolean `has_more` and nullable `next_offset`. Filters
apply before pagination and counting. Activity pages omit total count.

An event includes `id`, `project_id`, nullable scope IDs, `subject_type`,
`subject_id`, `action`, nullable actual `actor`, `occurred_at`, `created` and
`snapshot`. The snapshot holds `before`, `after`, `space` and `subspace`; removed
relationships also have `removal` attribution and reason. Principal-filtered
mailbox items add nullable `acknowledged_at`.

Principal filtering matches the exact namespace and UUID in source/target
references from before/after snapshots. It selects relationship and enrollment
events involving that principal. An actor field alone does not address an
event to that actor. Project-wide lists also include scope-management events.

```bash
curl --fail-with-body --silent --show-error \
  --request POST 'https://api.relationship.service.m7.org/v1/mailbox/view' \
  --header 'Authorization: Bearer ACCESS_TOKEN' \
  --header 'Content-Type: application/json' \
  --data '{"project_id":"PROJECT_UUID","principal":{"namespace":"example.app","principal_id":"PERSON_UUID"},"state":"unacknowledged","count":50,"offset":0}'
```

## Acknowledge after delivery

```bash
curl --fail-with-body --silent --show-error \
  --request POST 'https://api.relationship.service.m7.org/v1/mailbox/ack' \
  --header 'Authorization: Bearer ACCESS_TOKEN' \
  --header 'Content-Type: application/json' \
  --data '{"project_id":"PROJECT_UUID","principal":{"namespace":"example.app","principal_id":"PERSON_UUID"},"entry_id":"ENTRY_UUID"}'
```

Ack handles one entry per call and records the first acknowledgement time.
Repeating it keeps the original `acknowledged_at`. Pairwise participants have
independent receipts. A missing, foreign or unrelated recipient entry returns
body code 404; current caller admission and project authority are rechecked
during the mutation.

Mailbox fetches and previews do not acknowledge or expire requests. Acknowledge
only when the consumer's chosen delivery step is complete. Activity reads can
settle expired enrollment requests and append their outcome events.

## Offset delivery loops

Acknowledging a page removes its entries from an `unacknowledged` filtered
set. Do not then follow that page's old `next_offset`, which can skip messages.
Drain from offset zero after each acknowledged page, or finish a fetch pass
before acknowledging it. Deduplicate by event ID because new events can shift
offsets. Delivery is not a cursor or exactly-once protocol.

Use current [relationship records](https://m7.org/docs/api/api.relationship.service.m7.org/relationships.md) for current access
decisions. An event is a historical fact, including after removal or scope
deletion. Your application curates its presentation and any email, webhook or
permission effects.
