'use client'; import { useState, useEffect } from 'react'; import Link from 'next/link'; import { Button } from '@/components/ui/button'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'; import { ArrowLeft, Activity, History, Calendar, Mail, RefreshCw, Loader2, CheckCircle2, XCircle, AlertTriangle, Shield, Inbox, Send, Clock, ChevronDown, ChevronRight, Users, Search, LockKeyhole, UnlockKeyhole, PauseCircle, Building2, Check, Info, TrendingUp, ExternalLink, Eye, Trash2, } from 'lucide-react'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table'; import { StatusBadge } from '@/components/ui/status-badge'; import { Checkbox } from '@/components/ui/checkbox'; import SyncScheduler from '@/components/admin/SyncScheduler'; function fmtDate(d: string | null | undefined) { if (!d) return 'Never'; return new Date(d).toLocaleString(undefined, { month: 'short', day: 'numeric', year: 'numeric', hour: '2-digit', minute: '2-digit', }); } function fmtNum(n: number | null | undefined) { if (n == null) return '—'; return n.toLocaleString(); } function StatCard({ label, value, sub, icon: Icon, cls }: { label: string; value: string | number; sub?: string; icon?: React.ElementType; cls?: string; }) { return (
{Icon && }{label}
{value}
{sub &&
{sub}
}
); } function MessageStatusBadge({ status }: { status: string }) { const tone = status === 'delivered' ? 'ok' : status === 'rejected' ? 'error' : status === 'held' ? 'warn' : status === 'bounced' ? 'warn' : status === 'spam' ? 'accent' : 'inactive'; return {status || '—'}; } function ThreatLevelBadge({ level }: { level: string }) { const tone = level === 'high' ? 'error' : level === 'medium' ? 'warn' : level === 'low' ? 'pending' : 'inactive'; return {level || 'info'}; } // ── Status Tab ──────────────────────────────────────────────────────────────── function StatusTab({ data, onSync, syncing }: { data: any; onSync: (t: string) => void; syncing: boolean }) { if (!data) return (
); const stats = data.stats ?? {}; return (
{/* Connection banner */}
{data.connected ? : }

{data.connected ? `Connected — ${data.accountName ?? 'Mimecast'}` : 'Not connected'}

{data.packageName && ( ({data.packageName}) )}

Last sync: {fmtDate(stats.lastSync)} · Oldest message: {fmtDate(stats.oldestMessage)}

{data.error && (
{data.error}
)} {/* Stats grid */}
0 ? 'border-red-500/20 bg-red-500/5' : ''} />

Data Retention

120-day rolling window. Messages older than 120 days are automatically purged on each sync. Message bodies are fetched for delivered inbound messages (up to 500 per sync run).

); } // ── Messages Tab ────────────────────────────────────────────────────────────── function MessagesTab() { const [rows, setRows] = useState([]); const [loading, setLoading] = useState(true); const [search, setSearch] = useState(''); const [direction, setDirection] = useState(''); const [status, setStatus] = useState(''); const [days, setDays] = useState('7'); const load = () => { setLoading(true); const params = new URLSearchParams({ days, limit: '200' }); if (search) params.set('search', search); if (direction) params.set('direction', direction); if (status) params.set('status', status); fetch(`/api/mimecast/messages?${params}`) .then(r => r.json()) .then(d => setRows(d.messages ?? [])) .catch(() => setRows([])) .finally(() => setLoading(false)); }; useEffect(() => { load(); }, [days, direction, status]); return (
{/* Filters */}
setSearch(e.target.value)} onKeyDown={e => e.key === 'Enter' && load()} className="flex-1 min-w-48 border rounded-md px-3 py-1.5 text-sm bg-background" />
{loading ? (
) : !rows.length ? (
No messages found — run a sync first or adjust filters
) : (
From To Subject Direction Status Sent {rows.map((r: any) => ( {r.sender_address ?? '—'} {r.recipient_address ?? '—'} {r.subject ?? '—'} {r.direction ?? '—'} {fmtDate(r.sent_datetime)} ))}
)}
); } // ── Threats Tab ─────────────────────────────────────────────────────────────── function ThreatsTab() { const [rows, setRows] = useState([]); const [loading, setLoading] = useState(true); useEffect(() => { setLoading(true); fetch('/api/mimecast/threats?limit=200') .then(r => r.json()) .then(d => setRows(d.threats ?? [])) .catch(() => setRows([])) .finally(() => setLoading(false)); }, []); if (loading) return
; if (!rows.length) return
No threat events — run a sync first
; return (
Type Level Actor Verdict URL / File When {rows.map((r: any) => ( {r.event_type ?? '—'} {r.actor_email ?? '—'} {r.verdict ?? '—'} {r.url ?? r.file_name ?? '—'} {fmtDate(r.event_datetime)} ))}
); } // ── Cloud Users Tab ─────────────────────────────────────────────────────────── function CloudUserTab() { const [email, setEmail] = useState(''); const [domain, setDomain] = useState(''); const [loading, setLoading] = useState(false); const [result, setResult] = useState(null); const [showRaw, setShowRaw] = useState(false); const handleEmailChange = (v: string) => { setEmail(v); const atIdx = v.indexOf('@'); if (atIdx >= 0) setDomain(v.slice(atIdx + 1)); }; const lookup = async () => { if (!email || !domain) return; setLoading(true); setResult(null); setShowRaw(false); try { const params = new URLSearchParams({ emailAddress: email, domain }); const res = await fetch(`/api/mimecast/cloud-user?${params}`); setResult(await res.json()); } catch (err: any) { setResult({ error: err.message }); } finally { setLoading(false); } }; const user = result?.user; const lockedOut = user?.lockedOut ?? false; return (
handleEmailChange(e.target.value)} onKeyDown={e => e.key === 'Enter' && lookup()} className="border rounded-md px-3 py-1.5 text-sm bg-background w-72" />
setDomain(e.target.value)} onKeyDown={e => e.key === 'Enter' && lookup()} className="border rounded-md px-3 py-1.5 text-sm bg-background w-48" />
{result?.error && (
{result.error}
)} {result && !result.error && !result.found && (
User not found in Mimecast Cloud Gateway.
)} {user && (
{lockedOut ? : }

{lockedOut ? 'Account Locked Out' : 'Account Active'}

{user.name && {user.name} · } {user.emailAddress} {user.status && · Status: {user.status}}

{showRaw && (
              {JSON.stringify(user._raw ?? user, null, 2)}
            
)}
)}
); } // ── History Tab ─────────────────────────────────────────────────────────────── function HistoryTab() { const [rows, setRows] = useState([]); const [loading, setLoading] = useState(true); useEffect(() => { setLoading(true); fetch('/api/sync/history?entityType=mimecast&limit=30') .then(r => r.json()) .then(d => setRows(d.history ?? [])) .catch(() => setRows([])) .finally(() => setLoading(false)); }, []); if (loading) return
; if (!rows.length) return
No sync history yet
; return (
Type Status Messages Threats Started Duration {rows.map((r: any, i: number) => { const dur = r.completed_at && r.started_at ? new Date(r.completed_at).getTime() - new Date(r.started_at).getTime() : null; const durStr = dur == null ? '—' : dur < 60000 ? `${Math.round(dur / 1000)}s` : `${Math.floor(dur / 60000)}m ${Math.round((dur % 60000) / 1000)}s`; const statusTone = r.status === 'completed' ? 'ok' : r.status === 'failed' ? 'error' : 'inactive'; const meta = typeof r.metadata === 'string' ? JSON.parse(r.metadata || '{}') : (r.metadata ?? {}); return ( {r.sync_type ?? '—'} {r.status} {fmtNum(meta.messagesUpserted ?? r.records_added)} {fmtNum(meta.threatsUpserted)} {fmtDate(r.started_at)} {durStr} ); })}
); } // ── Message Analysis ───────────────────────────────────────────────────────── type AnalysisAction = { label: string; description: string; type: 'release' | 'info'; warning?: boolean }; type Analysis = { headline: string; explanation: string; severity: 'high' | 'medium' | 'low'; priorSteps: string[]; actions: AnalysisAction[]; }; function analyzeMessage(m: any): Analysis { const code: string = (m.reasonCode ?? '').toLowerCase(); const policy: string = (m.policyInfo ?? '').toLowerCase(); const reason: string = (m.reason ?? '').toLowerCase(); const from: string = m.from ?? ''; const fromDisplay: string = m.fromDisplay ?? ''; const fromDomain = from.includes('@') ? from.split('@')[1] : from; const subject: string = (m.subject ?? '').toLowerCase(); // What Mimecast has already evaluated (always true — it went through the full pipeline) const priorSteps = [ 'Passed through Mimecast inbound gateway', 'Evaluated against permitted sender policies — no matching bypass found', 'Evaluated against recipient-based allow rules — no match', ]; // DMARC if (code.includes('dmarc') || policy.includes('dmarc') || reason.includes('dmarc')) { return { headline: 'DMARC Authentication Failure', severity: 'high', explanation: `The sending domain failed DMARC validation. The "From" address (${fromDisplay || from}) does not align with the domain that actually sent the message (SPF/DKIM mismatch). This can indicate spoofing — but also fires for legitimate senders using shared email infrastructure (e.g. Mailchimp, Zendesk, HubSpot) who haven't set up DKIM alignment.`, priorSteps: [...priorSteps, 'SPF and DKIM alignment checks failed'], actions: [ { label: 'Release this message', description: 'Deliver it now if you recognise the sender. The recipient will receive it normally.', type: 'release' }, { label: 'Add a permitted sender policy', description: `In Mimecast: Administration > Gateway > Policies > Permitted Senders. Add sender domain "${fromDomain}" to bypass DMARC holds for this domain going forward.`, type: 'info' }, { label: 'Ask the sender to fix their authentication', description: 'The sender should configure DKIM signing on their email platform and ensure the d= domain in DKIM matches their From domain.', type: 'info' }, ], }; } // Impersonation if (code.includes('impersonation') || policy.includes('impersonation') || reason.includes('impersonation')) { return { headline: 'Impersonation Protection Hold', severity: 'high', explanation: `Mimecast's impersonation protection flagged "${fromDisplay || from}" as potentially impersonating an internal user or trusted contact. The display name may match an executive or employee while the sending address is external. This is the primary vector for BEC (Business Email Compromise) fraud.`, priorSteps: [...priorSteps, 'Display name matched internal user list — external sender flagged'], actions: [ { label: 'Release this message', description: 'Only release after verifying identity through another channel (phone/Teams). Do not confirm via reply to the held email.', type: 'release', warning: true }, { label: 'Add to permitted senders', description: `If this is a legitimate contact, add "${from}" as a permitted sender in Mimecast to bypass impersonation checks for this specific address.`, type: 'info' }, ], }; } // Spam if (code.includes('spam') || policy.includes('spam') || reason.includes('spam')) { const isAuthCode = subject.includes('authentication code') || subject.includes('verification code') || subject.includes('your code') || subject.includes('otp') || subject.includes('one-time') || subject.includes('access code') || subject.includes('login code'); const isMarketing = subject.includes('unsubscribe') || subject.includes('offer') || subject.includes('deal') || subject.includes('sale') || subject.includes('newsletter') || (m.hasAttachments === false && m.size > 30000); if (isAuthCode) { return { headline: 'Authentication Code — Held by Spam Filter', severity: 'low', explanation: `This is almost certainly a legitimate authentication or verification code email from "${fromDisplay || from}". It was caught by spam detection due to the sending infrastructure's reputation score — not because the content is malicious. The recipient is likely waiting for this code.`, priorSteps: [...priorSteps, `Spam score exceeded threshold for policy "${m.policyInfo}"`, 'No permitted sender rule found for this address'], actions: [ { label: 'Release this message', description: 'Deliver it now. The verification code is time-sensitive.', type: 'release' }, { label: 'Add permitted sender rule', description: `Add "${from}" to Mimecast > Administration > Gateway > Policies > Permitted Senders so future codes from this address are delivered without holds.`, type: 'info' }, ], }; } if (isMarketing) { return { headline: 'Marketing / Promotional Email', severity: 'low', explanation: `This appears to be a marketing or promotional email from "${fromDisplay || from}" that triggered the spam policy "${m.policyInfo}". These are frequently held when sent from bulk mail platforms (Mailchimp, Constant Contact, etc.) with mixed sender reputation.`, priorSteps: [...priorSteps, `Spam score exceeded threshold for policy "${m.policyInfo}"`], actions: [ { label: 'Release this message', description: 'Deliver if the recipient has opted in or is expecting communications from this sender.', type: 'release' }, { label: 'Add permitted sender rule', description: `Add sender domain "${fromDomain}" to permitted senders if this is a trusted marketing partner.`, type: 'info' }, { label: 'Block this sender', description: `Add "${fromDomain}" to blocked senders in Mimecast > Gateway > Policies > Blocked Senders if this is unwanted mail.`, type: 'info' }, ], }; } return { headline: 'Spam Signature Match', severity: 'medium', explanation: `The email from "${fromDisplay || from}" matched a spam signature under policy "${m.policyInfo}". This can be a false positive for legitimate transactional or notification emails sent through shared infrastructure with a low sender reputation.`, priorSteps: [...priorSteps, `Spam score exceeded threshold for policy "${m.policyInfo}"`], actions: [ { label: 'Release this message', description: 'Deliver if you recognise the sender and the recipient is expecting this email.', type: 'release' }, { label: 'Add permitted sender rule', description: `Add "${from}" to Mimecast > Administration > Gateway > Policies > Permitted Senders to prevent future holds.`, type: 'info' }, { label: 'Block this sender', description: `Add "${fromDomain}" to blocked senders if this is definitively spam.`, type: 'info' }, ], }; } // Malware / threat if (code.includes('malware') || code.includes('virus') || code.includes('threat') || policy.includes('malware') || reason.includes('malware')) { return { headline: 'Malware or Threat Detected', severity: 'high', explanation: `Mimecast detected a potentially malicious attachment or URL in this email. Do not release without thorough review by a security administrator.`, priorSteps: [...priorSteps, 'Attachment/URL scanned — threat signature matched'], actions: [ { label: 'Do not release without security review', description: 'Contact the sender through a separate channel to verify this email is legitimate before considering release.', type: 'info', warning: true }, { label: 'View full threat details', description: 'Open Mimecast Administration Console > Gateway > Held Queue to view the full attachment analysis and URL scan results.', type: 'info' }, ], }; } return { headline: 'Message Hold Applied', severity: 'medium', explanation: `This email was held under policy "${m.policyInfo || m.reason}". Review the sender and content before releasing.`, priorSteps, actions: [ { label: 'Release this message', description: 'Deliver it to the recipient if you determine it is safe.', type: 'release' }, { label: 'Add permitted sender rule', description: `Add "${from}" to Mimecast > Administration > Gateway > Policies > Permitted Senders to bypass this hold for future messages.`, type: 'info' }, ], }; } function MessageAnalysisDialog({ message, onClose, onRelease, releasing }: { message: any; onClose: () => void; onRelease: (m: any) => void; releasing: boolean; }) { if (!message) return null; const analysis = analyzeMessage(message); const severityBar = { high: 'border-l-red-500', medium: 'border-l-amber-500', low: 'border-l-blue-500', }; const severityBadge = { high: 'bg-red-100 text-red-700 dark:bg-red-950/40 dark:text-red-400', medium: 'bg-amber-100 text-amber-700 dark:bg-amber-950/40 dark:text-amber-400', low: 'bg-blue-100 text-blue-700 dark:bg-blue-950/40 dark:text-blue-400', }; const severityLabel = { high: 'High risk', medium: 'Review needed', low: 'Likely safe' }; const severityIcon = { high: , medium: , low: , }; return ( !open && onClose()}>
{analysis.headline} {severityIcon[analysis.severity]} {severityLabel[analysis.severity]}
{/* Message details */}
Subject {message.subject || '(no subject)'}
From {message.fromDisplay ? `${message.fromDisplay} ` : ''}{message.fromDisplay ? `<${message.from}>` : message.from}
To {message.toDisplay || message.to}
Received {new Date(message.dateReceived).toLocaleString()}
Policy {message.policyInfo || '—'}
{message.reason && (
Reason {message.reason}
)}
Size {message.size ? `${(message.size / 1024).toFixed(1)} KB` : '—'} {message.hasAttachments ? has attachments : ''}
{/* Body not available note */}
Message body is not accessible via the Mimecast held mail API. To preview the full content, open the Mimecast Administration Console. Open Mimecast Console →
{/* Explanation */}
{analysis.explanation}
{/* What Mimecast already checked */}

What was already evaluated

{analysis.priorSteps.map((step, i) => (
{step}
))}
{/* Resolution options */}

Resolution options

{analysis.actions.map((action, i) => (

{action.label}

{action.description}

{action.type === 'release' && ( )}
))}
); } // ── Held Mail Tab ───────────────────────────────────────────────────────────── const TENANT_OPTIONS = [ { id: '1', name: 'Wulf Consulting' }, { id: '2', name: 'Seubert & Associates' }, ]; function HeldMailTab() { const [data, setData] = useState(null); const [messages, setMessages] = useState([]); const [loading, setLoading] = useState(false); const [loadError, setLoadError] = useState(null); const [recipient, setRecipient] = useState(''); const [tenantId, setTenantId] = useState('1'); const [policyFilter, setPolicyFilter] = useState(''); const [loaded, setLoaded] = useState(false); const [releasing, setReleasing] = useState>({}); const [releaseErrors, setReleaseErrors] = useState>({}); const [analysisMessage, setAnalysisMessage] = useState(null); const load = async (recipientVal?: string) => { setLoading(true); setLoadError(null); const params = new URLSearchParams(); const r = recipientVal ?? recipient; if (r) params.set('recipient', r); if (tenantId) params.set('tenantId', tenantId); try { const res = await fetch(`/api/mimecast/held?${params}`); if (!res.ok) { const text = await res.text(); throw new Error(`HTTP ${res.status}: ${text.slice(0, 200)}`); } const d = await res.json(); setData(d); setMessages(d.messages ?? []); setLoaded(true); } catch (e: any) { setLoadError(e.message ?? 'Unknown error'); } finally { setLoading(false); } }; const release = async (m: any) => { setReleasing(r => ({ ...r, [m.id]: true })); setReleaseErrors(e => { const n = { ...e }; delete n[m.id]; return n; }); try { const res = await fetch('/api/mimecast/held/release', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: m.id, tenantId: m.tenantId }), }); const d = await res.json(); if (!res.ok || !d.released) { throw new Error(d.error ?? 'Release failed'); } // Optimistically remove from list setMessages(prev => prev.filter(x => x.id !== m.id)); } catch (e: any) { setReleaseErrors(prev => ({ ...prev, [m.id]: e.message })); } finally { setReleasing(r => { const n = { ...r }; delete n[m.id]; return n; }); } }; const recipientLower = recipient.trim().toLowerCase(); const filtered = messages.filter(m => { if (recipientLower && !m.to?.toLowerCase().includes(recipientLower) && !m.toDisplay?.toLowerCase().includes(recipientLower)) return false; if (policyFilter && !m.policyInfo?.toLowerCase().includes(policyFilter.toLowerCase())) return false; return true; }); const policies = [...new Set(messages.map((m: any) => m.policyInfo).filter(Boolean))].sort(); const tenantInfo: any[] = data?.tenants ?? []; const currentTenant = tenantInfo[0]; return (
{/* Controls */}
setRecipient(e.target.value)} className="w-full border rounded-md px-3 py-1.5 text-sm bg-background" />
{loaded && policies.length > 0 && (
)}
{/* Tenant summary badge */} {loaded && currentTenant && (
0 ? 'border-yellow-400/40 bg-yellow-500/5 text-yellow-700' : 'border-border bg-muted/30 text-muted-foreground' }`}> {currentTenant.accountName} {currentTenant.error ? — permission denied : — showing {messages.length.toLocaleString()}{currentTenant.totalCount > messages.length ? ` of ${currentTenant.totalCount.toLocaleString()}` : ''} held }
)} {loadError && (
{loadError}
)} {!loaded && !loading && !loadError && (
Select a tenant and click “Load Held Mail”
)} {loading && (
Fetching held messages…
)} {loaded && !loading && filtered.length === 0 && (
No held messages found.
)} {loaded && !loading && filtered.length > 0 && (
{filtered.length.toLocaleString()} held message{filtered.length !== 1 ? 's' : ''} {filtered.length < messages.length ? ` — filtered from ${messages.length.toLocaleString()}${currentTenant?.totalCount > messages.length ? ` of ${currentTenant.totalCount.toLocaleString()} total` : ''}` : currentTenant?.totalCount > messages.length ? ` (showing ${messages.length.toLocaleString()} of ${currentTenant.totalCount.toLocaleString()} total)` : ''}
Date To From Subject Policy {filtered.map((m: any) => ( {new Date(m.dateReceived).toLocaleString(undefined, { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' })}
{m.to}
{m.fromDisplay || m.from}
{m.fromDisplay &&
{m.from}
}
{m.subject || '(no subject)'}
{m.policyInfo || m.reason || '—'}
{releaseErrors[m.id] && (
{releaseErrors[m.id]}
)}
))}
)} setAnalysisMessage(null)} onRelease={release} releasing={analysisMessage ? !!releasing[analysisMessage.id] : false} />
); } // ── Delivered Mail Tab ──────────────────────────────────────────────────────── // Known phishing/scam subject patterns const SEXTORTION_PATTERNS = [ /you pervert/i, /i recorded you/i, /i have your password/i, /i hacked your/i, /your device was hacked/i, /your camera was/i, /rat (software|trojan)/i, /pay .{0,20}bitcoin/i, /send .{0,20}btc/i, /your (intimate|private|sexual) (video|footage|content)/i, /i have access to your/i, /you visited (adult|porn|xxx)/i, ]; const PHISHING_PATTERNS = [ /verify your account/i, /your account (has been|will be) (suspended|terminated|closed|locked)/i, /click here to (verify|confirm|restore|unlock|reactivate)/i, /unusual (sign|login|activity) (in|on|detected)/i, /update your (billing|payment|credit card) (info|information|details)/i, /you have (won|been selected|been chosen)/i, /claim your (prize|reward|gift card)/i, /wire transfer/i, /urgent (action|response) (required|needed)/i, /your (order|package|parcel|shipment) (is|has been) (held|delayed|pending)/i, ]; function detectSubjectThreat(subject: string): 'sextortion' | 'phishing' | null { const s = subject ?? ''; if (SEXTORTION_PATTERNS.some(p => p.test(s))) return 'sextortion'; if (PHISHING_PATTERNS.some(p => p.test(s))) return 'phishing'; return null; } function analyzeDelivered(m: any): { headline: string; explanation: string; severity: 'high' | 'medium' | 'low'; actions: AnalysisAction[] } { const score: number = m.spamScore ?? 0; const level: string = (m.detectionLevel ?? '').toLowerCase(); const status: string = (m.status ?? '').toLowerCase(); const from: string = m.from ?? ''; const subject: string = m.subject ?? ''; const fromDomain = from.includes('@') ? from.split('@')[1] : from; const fromEnvDomain = m.fromEnv?.includes('@') ? m.fromEnv.split('@')[1] : ''; const envelopeMismatch = fromEnvDomain && fromDomain && fromEnvDomain !== fromDomain; // Subject-based threat detection — catches zero-score phishing/sextortion const subjectThreat = detectSubjectThreat(subject); if (subjectThreat === 'sextortion') { return { headline: 'Sextortion Scam — Bypassed Spam Filter', severity: 'high', explanation: `This is a known sextortion scam pattern. Despite a spam score of ${score}, these emails evade spam filters because they use plain text (no links or attachments), send from free email providers like Gmail with good sender reputation (${fromDomain}), and send individually rather than in bulk — all of which make them invisible to volume-based spam detection. The sender has no actual recordings or access; this is a social engineering attempt to extort payment, typically in cryptocurrency.`, actions: [ { label: 'Block this sender immediately', description: `Add "${from}" to Mimecast > Administration > Gateway > Policies > Blocked Senders. Also add the domain "${fromDomain}" if it is not a legitimate provider.`, type: 'info', warning: true }, { label: 'Enable Content Examination policy', description: 'In Mimecast: Administration > Gateway > Policies > Content Examination. Create a rule to hold/reject messages containing keywords like "bitcoin", "I recorded you", "I hacked". This catches sextortion that spam scores miss.', type: 'info' }, { label: 'Report to Mimecast threat intel', description: 'Forward the raw email as an attachment to abuse@mimecast.com to improve detection for all customers.', type: 'info' }, { label: 'Advise the recipient', description: 'Let the recipient know this is a scam. They should not respond, not pay, and delete the email. No credentials were actually compromised.', type: 'info' }, ], }; } if (subjectThreat === 'phishing') { return { headline: 'Suspected Phishing — Bypassed Spam Filter', severity: 'high', explanation: `The subject line matches known phishing patterns. Despite a spam score of ${score}, phishing emails frequently score 0 because they use legitimate sending infrastructure, contain no bulk-send signatures, and rely on social engineering rather than technical spam traits. The sender (${from}) should be verified before any action is taken on this email.`, actions: [ { label: 'Block this sender', description: `Add "${from}" to Mimecast > Administration > Gateway > Policies > Blocked Senders.`, type: 'info', warning: true }, { label: 'Enable Impersonation Protection', description: 'In Mimecast: Administration > Gateway > Policies > Impersonation Protection. Enable checks for display name spoofing and lookalike domains.', type: 'info' }, { label: 'Enable Content Examination', description: 'Create a Mimecast Content Examination policy to hold messages matching phishing keyword patterns.', type: 'info' }, { label: 'Report to Mimecast', description: 'Forward the raw email as an attachment to abuse@mimecast.com.', type: 'info' }, ], }; } if (score >= 10 || level === 'high') { return { headline: 'High Spam Score — Delivered', severity: 'high', explanation: `This message scored ${score} on Mimecast's spam engine and was still delivered. A score of 10+ typically indicates bulk spam infrastructure or known spam signatures. The ${envelopeMismatch ? `envelope sender (${m.fromEnv}) differs from the header From (${from}), which is a common indicator of spoofing or mailing list abuse. ` : ''}message passed through without being held, likely because no policy threshold was set at this score level.`, actions: [ { label: 'Review spam policy thresholds', description: 'In Mimecast: Administration > Gateway > Policies > Spam Scanning. Consider lowering the "hold" threshold to catch messages with scores ≥10.', type: 'info' }, { label: 'Block this sender domain', description: `Add "${fromDomain}" to Administration > Gateway > Policies > Blocked Senders to prevent future delivery from this domain.`, type: 'info' }, { label: 'Report as spam', description: 'Forward the email as an attachment to abuse@mimecast.com to improve future detection.', type: 'info' }, ], }; } if (score >= 5 || level === 'moderate') { return { headline: 'Moderate Spam Score — Delivered', severity: 'medium', explanation: `This message scored ${score} on spam detection (detection level: ${m.detectionLevel || 'moderate'}) but was delivered because it fell below the hold threshold. ${envelopeMismatch ? `The envelope sender (${m.fromEnv}) differs from the header From (${from}), suggesting use of a third-party sending platform. ` : ''}This may be legitimate marketing mail or a marginal false negative.`, actions: [ { label: 'Add to blocked senders', description: `If this is unwanted, add "${fromDomain}" to Mimecast > Administration > Gateway > Policies > Blocked Senders.`, type: 'info' }, { label: 'Adjust spam hold threshold', description: 'Lower the spam hold threshold in Mimecast Spam Scanning policy to hold messages with scores ≥5 for admin review.', type: 'info' }, ], }; } if (envelopeMismatch) { return { headline: 'Envelope / Header Mismatch', severity: 'medium', explanation: `The email's envelope sender (${m.fromEnv}) doesn't match the From header (${from}). This is common with third-party sending platforms (Mailchimp, SendGrid, HubSpot) but can also indicate spoofing. Spam score was ${score}. The message was delivered.`, actions: [ { label: 'Verify the sender', description: 'Check whether the sending platform is authorised to send on behalf of this domain (SPF/DKIM). Contact the sender via another channel if unsure.', type: 'info' }, { label: 'Add DMARC bypass if legitimate', description: `If this is a known sender using a third-party platform, add "${fromDomain}" to a Mimecast permitted sender policy.`, type: 'info' }, ], }; } if (status === 'rejected' || status === 'bounced') { return { headline: `Message ${status === 'rejected' ? 'Rejected' : 'Bounced'}`, severity: 'low', explanation: `This message was ${status} — it was not delivered to the recipient. ${status === 'rejected' ? 'Mimecast or the destination server rejected it during the SMTP session.' : 'It was accepted but subsequently bounced by the destination mailbox.'}`, actions: [ { label: 'Check recipient mailbox', description: 'Verify the recipient address is valid and the mailbox is not full or disabled.', type: 'info' }, ], }; } return { headline: 'Delivered — Clean', severity: 'low', explanation: `This message was delivered with a spam score of ${score} and no threat flags. Status: ${m.status}. No action is required.`, actions: [ { label: 'No action needed', description: 'This message appears clean. If you believe it is malicious, report it via the Mimecast console.', type: 'info' }, ], }; } function DeliveredAnalysisDialog({ message, onClose, onFindSimilar, allMessages }: { message: any; onClose: () => void; onFindSimilar?: (type: 'sender' | 'ip' | 'subject', value: string) => void; allMessages?: any[]; }) { const [remedStep, setRemedStep] = useState<'idle' | 'searching' | 'confirm' | 'removing' | 'done'>('idle'); const [remedMatches, setRemedMatches] = useState([]); const [remedSelected, setRemedSelected] = useState>(new Set()); const [remedResults, setRemedResults] = useState<{ succeeded: number; failed: number } | null>(null); const [remedError, setRemedError] = useState(null); const [permError, setPermError] = useState(null); // Reset remediation state when message changes const prevMessageId = message?.id; useEffect(() => { setRemedStep('idle'); setRemedMatches([]); setRemedSelected(new Set()); setRemedResults(null); setRemedError(null); setPermError(null); }, [prevMessageId]); if (!message) return null; const analysis = analyzeDelivered(message); const searchMailbox = async () => { setRemedStep('searching'); setRemedError(null); setPermError(null); try { const res = await fetch('/api/mimecast/mailbox-remediate', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ action: 'search', userEmail: message.to, fromAddress: message.from }), }); if (!res.ok) { const isJson = res.headers.get('content-type')?.includes('application/json'); const d = isJson ? await res.json() : null; if (d?.permissionRequired) { setPermError(d.detail); setRemedStep('idle'); return; } throw new Error(d?.error ?? `HTTP ${res.status}`); } const d = await res.json(); const matches = d.messages ?? []; setRemedMatches(matches); setRemedSelected(new Set(matches.map((m: any) => m.id))); setRemedStep('confirm'); } catch (e: any) { setRemedError(e.message); setRemedStep('idle'); } }; const removeSelected = async () => { setRemedStep('removing'); setRemedError(null); try { const res = await fetch('/api/mimecast/mailbox-remediate', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ action: 'move', userEmail: message.to, messageIds: [...remedSelected] }), }); if (!res.ok) { const isJson = res.headers.get('content-type')?.includes('application/json'); const d = isJson ? await res.json() : null; throw new Error(d?.error ?? `HTTP ${res.status}`); } const d = await res.json(); setRemedResults({ succeeded: d.succeeded, failed: d.failed }); setRemedStep('done'); } catch (e: any) { setRemedError(e.message); setRemedStep('confirm'); } }; const severityBar = { high: 'border-l-red-500', medium: 'border-l-amber-500', low: 'border-l-blue-500' }; const severityBadge = { high: 'bg-red-100 text-red-700 dark:bg-red-950/40 dark:text-red-400', medium: 'bg-amber-100 text-amber-700 dark:bg-amber-950/40 dark:text-amber-400', low: 'bg-blue-100 text-blue-700 dark:bg-blue-950/40 dark:text-blue-400', }; const severityLabel = { high: 'High risk', medium: 'Review needed', low: 'Clean' }; const severityIcon = { high: , medium: , low: , }; return ( !open && onClose()}>
{analysis.headline} {severityIcon[analysis.severity]} {severityLabel[analysis.severity]}
{/* Message details */}
Subject {message.subject || '(no subject)'}
From
{message.from}
{message.fromEnv && message.fromEnv !== message.from && (
Envelope: {message.fromEnv}
)}
To {message.toDisplay ? `${message.toDisplay} <${message.to}>` : message.to}
Received {new Date(message.received).toLocaleString()}
Status {message.status}
Spam score
= 10 ? 'text-red-600' : message.spamScore >= 5 ? 'text-amber-600' : 'text-green-700'}`}> {message.spamScore} {message.detectionLevel && ( ({message.detectionLevel}) )}
= 10 ? 'bg-red-500' : message.spamScore >= 5 ? 'bg-amber-500' : 'bg-green-500'}`} style={{ width: `${Math.min(100, (message.spamScore / 20) * 100)}%` }} />
{message.senderIP && (
Sender IP {message.senderIP}
)}
Attachments {message.attachments ? 'Yes' : 'No'}
{/* Explanation */}
{analysis.explanation}
{/* Actions */}

Recommended actions

{analysis.actions.map((action, i) => (

{action.label}

{action.description}

))}
{/* Cluster context — show how many other messages match sender/IP in current results */} {onFindSimilar && allMessages && allMessages.length > 1 && (() => { const sameFrom = allMessages.filter(x => x.from === message.from && x.id !== message.id); const sameIP = message.senderIP ? allMessages.filter(x => x.senderIP === message.senderIP && x.id !== message.id) : []; const subjectWords = (message.subject ?? '').split(/\s+/).slice(0, 5).join(' '); const sameSubject = subjectWords.length > 8 ? allMessages.filter(x => x.id !== message.id && (x.subject ?? '').startsWith(subjectWords)) : []; const hasClusters = sameFrom.length > 0 || sameIP.length > 0 || sameSubject.length > 0; if (!hasClusters) return null; return (

Pattern matches in current results

{sameFrom.length > 0 && (
{sameFrom.length + 1} messages from {message.from} {' '}→ {[...new Set([message.to, ...sameFrom.map((x: any) => x.to)])].join(', ')}
)} {sameIP.length > 0 && sameIP.length !== sameFrom.length && (
{sameIP.length + 1} messages from IP {message.senderIP} {' '}(multiple senders)
)} {sameSubject.length > 0 && (
{sameSubject.length + 1} messages with similar subject
)}
); })()} {/* Mailbox Remediation */}
Remove from mailbox — search {message.to}'s mailbox and delete
{remedStep === 'idle' && ( )}
{permError && (

Permission required

{permError}

Mail.ReadWrite (Application)

)} {remedError && (
{remedError}
)} {remedStep === 'searching' && (
Searching {message.to}'s mailbox…
)} {remedStep === 'confirm' && (
{remedMatches.length === 0 ? (
No matching messages found in mailbox.
) : ( <>
Found {remedMatches.length} message{remedMatches.length !== 1 ? 's' : ''} in mailbox — select to move to Deleted Items:
{remedMatches.map(m => ( ))}
{remedSelected.size} selected — will move to Deleted Items (recoverable)
)}
)} {remedStep === 'removing' && (
Removing {remedSelected.size} message{remedSelected.size !== 1 ? 's' : ''}…
)} {remedStep === 'done' && remedResults && (
{remedResults.failed === 0 ? : } {remedResults.succeeded} message{remedResults.succeeded !== 1 ? 's' : ''} moved to Deleted Items {remedResults.failed > 0 && `, ${remedResults.failed} failed`}
)}
View full message tracking in the Mimecast Administration Console under Gateway > Message Center > Message Finder. Open Console →
); } function DeliveredMailTab() { const [tenantId, setTenantId] = useState('1'); const [to, setTo] = useState(''); const [from, setFrom] = useState(''); const [subject, setSubject] = useState(''); const [startHours, setStartHours] = useState(24); const [messages, setMessages] = useState([]); const [loading, setLoading] = useState(false); const [loadError, setLoadError] = useState(null); const [loaded, setLoaded] = useState(false); const [statusFilter, setStatusFilter] = useState(''); const [sortBy, setSortBy] = useState<'received' | 'spamScore'>('received'); const [analysisMessage, setAnalysisMessage] = useState(null); const [clusterExpanded, setClusterExpanded] = useState(true); const handleFindSimilar = (type: 'sender' | 'ip' | 'subject', value: string) => { if (type === 'sender') { const domain = value.includes('@') ? value.split('@')[1] : value; const isFreemail = ['gmail.com','yahoo.com','hotmail.com','outlook.com','live.com','aol.com','icloud.com'].includes(domain); if (isFreemail) { setFrom(value); } else { setFrom('@' + domain); } } else if (type === 'ip') { setFrom(''); setSubject(''); } else if (type === 'subject') { setSubject(value); setFrom(''); } setTimeout(() => searchWithOverrides(type, value), 50); }; const searchWithOverrides = async (type: 'sender' | 'ip' | 'subject', value: string) => { setLoading(true); setLoadError(null); setLoaded(false); try { let body: any = { tenantId, startHours }; if (type === 'sender') { const domain = value.includes('@') ? value.split('@')[1] : value; const isFreemail = ['gmail.com','yahoo.com','hotmail.com','outlook.com','live.com','aol.com','icloud.com'].includes(domain); body.from = isFreemail ? value : '@' + domain; } else if (type === 'subject') { body.subject = value; } if (!body.from && !body.subject && !to) body.to = to || undefined; if (!body.from && !body.to && !body.subject) { setLoading(false); return; } const res = await fetch('/api/mimecast/delivered', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), }); if (!res.ok) { const t = await res.text(); throw new Error(`HTTP ${res.status}: ${t.slice(0, 200)}`); } const d = await res.json(); setMessages(d.messages ?? []); setLoaded(true); } catch (e: any) { setLoadError(e.message ?? 'Unknown error'); } finally { setLoading(false); } }; const search = async () => { if (!to && !from && !subject) return; setLoading(true); setLoadError(null); try { const res = await fetch('/api/mimecast/delivered', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ tenantId, to: to || undefined, from: from || undefined, subject: subject || undefined, startHours }), }); if (!res.ok) { const t = await res.text(); throw new Error(`HTTP ${res.status}: ${t.slice(0, 200)}`); } const d = await res.json(); setMessages(d.messages ?? []); setLoaded(true); } catch (e: any) { setLoadError(e.message ?? 'Unknown error'); } finally { setLoading(false); } }; const filtered = messages .filter(m => !statusFilter || m.status === statusFilter) .sort((a, b) => sortBy === 'spamScore' ? (b.spamScore ?? 0) - (a.spamScore ?? 0) : new Date(b.received).getTime() - new Date(a.received).getTime() ); const statuses = [...new Set(messages.map(m => m.status).filter(Boolean))].sort(); const highRisk = messages.filter(m => (m.spamScore ?? 0) >= 10 || detectSubjectThreat(m.subject) !== null).length; const medRisk = messages.filter(m => (m.spamScore ?? 0) >= 5 && (m.spamScore ?? 0) < 10 && detectSubjectThreat(m.subject) === null).length; return (
{/* Search controls */}
Search Delivered Mail — find messages that passed through Mimecast
setTo(e.target.value)} onKeyDown={e => e.key === 'Enter' && search()} className="w-full border rounded-md px-3 py-1.5 text-sm bg-background" />
setFrom(e.target.value)} onKeyDown={e => e.key === 'Enter' && search()} className="w-full border rounded-md px-3 py-1.5 text-sm bg-background" />
setSubject(e.target.value)} onKeyDown={e => e.key === 'Enter' && search()} className="w-full border rounded-md px-3 py-1.5 text-sm bg-background" />
{!to && !from && !subject && (

Enter recipient, sender, or subject to search

)}
{loadError && (
{loadError}
)} {loading && (
Searching message logs…
)} {/* Summary stats */} {loaded && !loading && messages.length > 0 && (
Total {messages.length}
{highRisk > 0 && (
{highRisk} high spam score (≥10)
)} {medRisk > 0 && (
{medRisk} moderate spam score (5–9)
)}
{messages.length - highRisk - medRisk} clean
)} {/* Cluster / Pattern Analysis */} {loaded && !loading && messages.length > 1 && (() => { // Group by sender email const bySender: Record = {}; for (const m of messages) { if (m.from) { (bySender[m.from] ??= []).push(m); } } const topSenders = Object.entries(bySender).filter(([, v]) => v.length > 1) .sort((a, b) => b[1].length - a[1].length).slice(0, 5); // Group by sender IP const byIP: Record = {}; for (const m of messages) { if (m.senderIP) { (byIP[m.senderIP] ??= []).push(m); } } const topIPs = Object.entries(byIP).filter(([, v]) => v.length > 1) .sort((a, b) => b[1].length - a[1].length).slice(0, 5); // Group by subject prefix (first 5 words) const bySubject: Record = {}; for (const m of messages) { const prefix = (m.subject ?? '').split(/\s+/).slice(0, 5).join(' ').toLowerCase(); if (prefix.length > 5) { (bySubject[prefix] ??= []).push(m); } } const topSubjects = Object.entries(bySubject).filter(([, v]) => v.length > 1) .sort((a, b) => b[1].length - a[1].length).slice(0, 3); if (!topSenders.length && !topIPs.length && !topSubjects.length) return null; return (
{clusterExpanded && (
{topSenders.length > 0 && (

Repeated senders

{topSenders.map(([sender, msgs]) => { const recipients = [...new Set(msgs.map((m: any) => m.to))]; const isThreat = msgs.some((m: any) => detectSubjectThreat(m.subject) !== null || (m.spamScore ?? 0) >= 5); return (
{isThreat && }
{sender}
{msgs.length} messages → {recipients.length} recipient{recipients.length !== 1 ? 's' : ''}: {recipients.slice(0, 3).join(', ')}{recipients.length > 3 ? ` +${recipients.length - 3} more` : ''}
); })}
)} {topIPs.length > 0 && (

Repeated sending IPs

{topIPs.map(([ip, msgs]) => { const senders = [...new Set(msgs.map((m: any) => m.from))]; const recipients = [...new Set(msgs.map((m: any) => m.to))]; const isThreat = msgs.some((m: any) => detectSubjectThreat(m.subject) !== null || (m.spamScore ?? 0) >= 5); return (
{isThreat && }
{ip}
{msgs.length} messages · {senders.length} sender{senders.length !== 1 ? 's' : ''} · {recipients.length} recipient{recipients.length !== 1 ? 's' : ''} {senders.length <= 2 ? `: ${senders.join(', ')}` : ''}
IP pivot N/A
); })}
)} {topSubjects.length > 0 && (

Repeated subject patterns

{topSubjects.map(([prefix, msgs]) => { const senders = [...new Set(msgs.map((m: any) => m.from))]; const recipients = [...new Set(msgs.map((m: any) => m.to))]; const isThreat = msgs.some((m: any) => detectSubjectThreat(m.subject) !== null); return (
{isThreat && }
"{msgs[0].subject}"
{msgs.length} messages · {senders.length} sender{senders.length !== 1 ? 's' : ''} · {recipients.length} recipient{recipients.length !== 1 ? 's' : ''}
); })}
)}
)}
); })()} {/* Filters + sort */} {loaded && !loading && messages.length > 0 && (
{statuses.length > 1 && ( )} {filtered.length !== messages.length && ( {filtered.length} of {messages.length} shown )}
)} {loaded && !loading && messages.length === 0 && (
No messages found for this search. Try a broader time range or different search terms.
)} {loaded && !loading && filtered.length > 0 && (
Date To From Subject Status Spam {filtered.map((m: any) => ( = 10 || detectSubjectThreat(m.subject) !== null ? 'bg-red-500/5' : m.spamScore >= 5 ? 'bg-amber-500/5' : '' }> {new Date(m.received).toLocaleString(undefined, { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' })}
{m.to}
{m.from}
{m.fromEnv && m.fromEnv !== m.from && (
{m.fromEnv}
)}
{m.subject || '(no subject)'}
{m.status} = 10 ? 'text-red-600' : m.spamScore >= 5 ? 'text-amber-600' : 'text-muted-foreground'}`}> {m.spamScore}
))}
)} setAnalysisMessage(null)} onFindSimilar={handleFindSimilar} allMessages={messages} />
); } // ── Page ────────────────────────────────────────────────────────────────────── export default function MimecastSyncPage() { const [statusData, setStatusData] = useState(null); const [syncing, setSyncing] = useState(false); const [lastResult, setLastResult] = useState(null); const fetchStatus = () => { fetch('/api/mimecast/status') .then(r => r.json()) .then(d => setStatusData(d)) .catch(() => setStatusData({ configured: false, connected: false })); }; useEffect(() => { fetchStatus(); }, []); const handleSync = async (syncType: string) => { setSyncing(true); setLastResult(null); try { const res = await fetch('/api/sync/mimecast', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ syncType }), }); const data = await res.json(); setLastResult(data); fetchStatus(); } catch (err: any) { setLastResult({ error: err.message }); } finally { setSyncing(false); } }; return (
{/* Header */}

Email Security — Mimecast

Message logs, threat events, 120-day retention

{/* Last sync result banner */} {lastResult && (
{lastResult.error ? `Sync failed: ${lastResult.error}` : `Sync complete — ${fmtNum(lastResult.messagesUpserted)} messages, ${fmtNum(lastResult.threatsUpserted)} threats, ${fmtNum(lastResult.bodiesFetched)} bodies fetched in ${Math.round((lastResult.durationMs ?? 0) / 1000)}s` } {lastResult.errors?.length > 0 && (
{lastResult.errors.slice(0, 3).join(' · ')}
)}
)} Status Held Mail Delivered Messages Threats Cloud Users History Schedules
); }