docs: document get-hold-message-list endpoint and its held-queue filtering gotcha

This commit is contained in:
lorentz 2026-07-18 06:10:59 -04:00
parent 5b09dbff96
commit b638189cb0

View file

@ -209,6 +209,46 @@ POST /api/audit/get-audit-events
--- ---
### E. Held Message Queue (Blast Radius / Phishing Triage)
Used by Pulse's phishing-triage Blast Radius panel (`lib/services/mimecast-blast-radius.ts`) to check what's sitting in a recipient's spam/policy hold queue.
```http
POST /api/gateway/get-hold-message-list
{
"data": [{
"admin": true,
"start": "2026-07-16T18:10:55+0000",
"end": "2026-07-18T09:38:00+0000",
"searchBy": {
"fieldName": "recipient",
"value": "user@example.com"
}
}]
}
```
- `admin`: `true` returns held messages for all recipients (the client falls back to omitting it on a 403 for tenants lacking that permission); `false`/omitted returns only the authenticated user's own held mail.
- `start`/`end`: date range, same `yyyy-MM-dd'T'HH:mm:ssZ` format as every other endpoint in this guide — confirmed live in 2026-07 against [Mimecast's official docs](https://developer.services.mimecast.com/docs/cloudgateway/1/routes/api/gateway/get-hold-message-list/post) and a real tenant; easy to miss since without it you query a recipient's ENTIRE hold queue with no date bound.
- `searchBy.fieldName`: exactly one of `all`, `subject`, `sender`, `recipient`, `reasonCode`, `senderIP` per call — `searchBy` is a single object, not an array, so `sender` + `recipient` cannot be combined in one request.
- **Required permission**: `Account | Dashboard | Read`
- **Pagination**: `pageSize` in the request is ignored — always returns 10 rows; paginate via `meta.pagination.next`.
Key response fields:
| Field | Description |
|-------|-------------|
| `from.emailAddress` / `fromHeader.emailAddress` | Envelope vs. header sender — use `fromHeader` when present |
| `to.emailAddress` | Recipient |
| `subject` | Message subject |
| `reason` / `reasonCode` | Why it's held (e.g. "Message Hold Applied - Spam Signature policy", "... - DMARC Quarantine") |
| `dateReceived` | ISO 8601 timestamp |
| `route` | `INBOUND` \| `OUTBOUND` \| `INTERNAL` \| `EXTERNAL` |
Official docs: https://developer.services.mimecast.com/docs/cloudgateway/1/routes/api/gateway/get-hold-message-list/post
---
## 4. Pagination ## 4. Pagination
All paginated endpoints use cursor tokens: All paginated endpoints use cursor tokens:
@ -339,3 +379,4 @@ Sync strategy: poll SIEM logs hourly using stored `mc-siem-token` per account. S
- **POST for reads** — don't expect REST conventions; almost everything is POST. - **POST for reads** — don't expect REST conventions; almost everything is POST.
- **Token expiry** — implement proactive refresh (check `expires_in`, refresh at 80% of TTL). - **Token expiry** — implement proactive refresh (check `expires_in`, refresh at 80% of TTL).
- **Region routing** — if a client's tenant is in EU/AU, the base URL differs; use the discovery endpoint per account. - **Region routing** — if a client's tenant is in EU/AU, the base URL differs; use the discovery endpoint per account.
- **Held-message queue has no combined filter**`get-hold-message-list`'s `searchBy` only accepts one field at a time (`recipient`, `sender`, `subject`, `reasonCode`, `senderIP`, or `all`). Querying by recipient alone returns their entire hold queue for any sender/reason unless you also pass `start`/`end` — and even then, an unrelated held message can coincidentally fall in the same window. Always cross-check the held row's sender against the message you actually care about before treating it as related (this exact false positive surfaced on a real ticket and is now guarded in `lib/services/mimecast-blast-radius.ts`'s `domainsMatch()` check).