From b638189cb09a461bc702052dc8b018f744478608 Mon Sep 17 00:00:00 2001 From: lorentz Date: Sat, 18 Jul 2026 06:10:59 -0400 Subject: [PATCH] docs: document get-hold-message-list endpoint and its held-queue filtering gotcha --- docs/mimecast-api-guide.md | 41 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/docs/mimecast-api-guide.md b/docs/mimecast-api-guide.md index b961a82..3b8f031 100644 --- a/docs/mimecast-api-guide.md +++ b/docs/mimecast-api-guide.md @@ -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 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. - **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. +- **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).