feat: add Delivered Mail tab with message-finder search, spam scoring, analysis dialog

- New POST /api/mimecast/delivered route using message-finder/search API
- DeliveredMailTab: search by recipient, sender, subject, time range (6h–7d)
- Results table with status badge, spam score, row tinting for high/moderate risk
- Summary stats bar: total / high spam (≥10) / moderate (5-9) / clean counts
- Sort by date or spam score; filter by status (accepted/held/rejected/bounced)
- DeliveredAnalysisDialog: explains why high-score mail got through, envelope mismatch detection, actionable remediation steps (block domain, adjust policy threshold, report)
- MimecastDeliveredMessage interface + searchDeliveredMessages() method in client
This commit is contained in:
lorentz 2026-04-01 08:17:18 -04:00
parent 0df5c344b5
commit 8e28062d85
3 changed files with 552 additions and 2 deletions

View file

@ -89,6 +89,23 @@ export interface MimecastHeldMessage {
size: number;
}
export interface MimecastDeliveredMessage {
id: string;
status: string;
subject: string;
from: string;
fromEnv: string;
to: string;
toDisplay: string;
received: string;
senderIP: string;
spamScore: number;
detectionLevel: string;
attachments: boolean;
route: string;
info: string;
}
export interface MimecastCloudUser {
emailAddress: string;
domain: string;
@ -526,6 +543,65 @@ export class MimecastClient {
return { messages: all, totalCount };
}
/**
* POST /api/message-finder/search
* Search delivered/accepted inbound messages. Requires at least one of: to, from, subject, senderIP, url.
*/
async searchDeliveredMessages(options: {
to?: string;
from?: string;
subject?: string;
startHours?: number;
start?: string;
end?: string;
route?: string;
}): Promise<{ messages: MimecastDeliveredMessage[]; error?: string }> {
const now = new Date();
const startDate = options.start
? options.start
: new Date(now.getTime() - (options.startHours ?? 24) * 60 * 60 * 1000)
.toISOString()
.replace(/\.\d{3}Z$/, '+0000');
const endDate = options.end ?? now.toISOString().replace(/\.\d{3}Z$/, '+0000');
const opts: Record<string, any> = {};
if (options.to) opts.to = options.to;
if (options.from) opts.from = options.from;
if (options.subject) opts.subject = options.subject;
if (options.route) opts.route = options.route;
try {
const result = await this.request<any>('POST', '/api/message-finder/search', {
data: [{
start: startDate,
end: endDate,
advancedTrackAndTraceOptions: opts,
}],
});
const emails: any[] = result?.data?.[0]?.trackedEmails ?? [];
return {
messages: emails.map(e => ({
id: e.id,
status: e.status,
subject: e.subject ?? '',
from: e.fromHdr?.emailAddress ?? e.fromEnv?.emailAddress ?? '',
fromEnv: e.fromEnv?.emailAddress ?? '',
to: e.to?.[0]?.emailAddress ?? '',
toDisplay: e.to?.[0]?.displayableName ?? '',
received: e.received,
senderIP: e.senderIP ?? '',
spamScore: e.spamScore ?? 0,
detectionLevel: e.detectionLevel ?? '',
attachments: e.attachments ?? false,
route: e.route ?? '',
info: e.info ?? '',
})),
};
} catch (err: any) {
return { messages: [], error: err.message };
}
}
/**
* POST /api/gateway/hold-release
* Releases a held message by ID. Returns true if released successfully.