fix(260721-mmf): broaden Mimecast blast-radius fan-out to whole tenant
- searchDeliveredMessages now called with from+subject+start+end only (no `to`) so it returns every delivered/rejected message matching the campaign across all recipients, not just the reporter's mailbox - getHeldMessages now called with start+end only (no `recipient`) — domainsMatch() post-filter is the sole scoping mechanism for held rows - Updated inline comments to document the tenant-wide fan-out and the per-recipient merge behavior it now produces
This commit is contained in:
parent
f50b8a3a9b
commit
58202e0dee
1 changed files with 20 additions and 8 deletions
|
|
@ -139,15 +139,23 @@ export async function getBlastRadius(
|
|||
|
||||
// D-01 (corrected): the fan-out runs unconditionally — it is the only
|
||||
// source of the matched/delivered/held/rejected/clicked counts.
|
||||
//
|
||||
// Tenant-wide fan-out (T-17-01): deliberately NOT scoped by a single
|
||||
// recipient. searchDeliveredMessages is queried by sender+subject+
|
||||
// date-window only, so it returns every delivered/rejected message
|
||||
// matching this campaign across the whole tenant — the true blast
|
||||
// radius, not just the reporter's mailbox. getHeldMessages is queried
|
||||
// by date-window only (it has no server-side sender filter) and relies
|
||||
// entirely on the domainsMatch() post-filter below to stay scoped to
|
||||
// this campaign's sender.
|
||||
const [deliveredResult, heldResult, threatsResult] = await Promise.all([
|
||||
client.searchDeliveredMessages({
|
||||
to: input.recipient,
|
||||
from: input.sender,
|
||||
subject: input.subject,
|
||||
start: startStr,
|
||||
end: endStr,
|
||||
}),
|
||||
client.getHeldMessages({ recipient: input.recipient, start: startStr, end: endStr }),
|
||||
client.getHeldMessages({ start: startStr, end: endStr }),
|
||||
client.getThreatEvents(),
|
||||
]);
|
||||
|
||||
|
|
@ -192,12 +200,16 @@ export async function getBlastRadius(
|
|||
|
||||
const clicked = (threatsResult.items ?? []).filter((t) => isClickEvent(t.analysis)).length;
|
||||
|
||||
// Per-recipient merge: group by the single-string `to` field. Delivered
|
||||
// (non-rejected) rows → 'delivered', rejected rows → 'rejected', held
|
||||
// rows → 'held' (held overwrites a same-recipient delivered entry — a
|
||||
// held message for a recipient is the more actionable signal). Ensure
|
||||
// the originally-queried recipient is always represented, even if
|
||||
// neither result set returned a row for them ('unknown').
|
||||
// Per-recipient merge: group by the single-string `to` field. Since the
|
||||
// fan-out above is tenant-wide (not scoped to one recipient), this Map
|
||||
// naturally accumulates one entry per distinct `to` value seen across all
|
||||
// delivered/held/rejected rows — the true multi-recipient blast radius,
|
||||
// not just the reporter. Delivered (non-rejected) rows → 'delivered',
|
||||
// rejected rows → 'rejected', held rows → 'held' (held overwrites a
|
||||
// same-recipient delivered entry — a held message for a recipient is the
|
||||
// more actionable signal). Ensure the originally-reported recipient is
|
||||
// always represented, even if neither result set returned a row for them
|
||||
// ('unknown').
|
||||
const perRecipientMap = new Map<string, 'delivered' | 'held' | 'rejected' | 'unknown'>();
|
||||
for (const row of nonRejectedDeliveredRows) {
|
||||
if (row.to) perRecipientMap.set(row.to, 'delivered');
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue