feat(engagement): replace Graph email counts with real-time mimecast data

- Broaden mimecast retention from 30 days to 18 months rolling
- Re-enable mimecast-sync schedule (was disabled since March 17)
- Full sync triggered: 35,559 messages loaded for last 30 days
- Users list API: LATERAL join on mimecast_messages for emails_sent/received
- User detail API: add emails{d7,d30,d90} field from mimecast
- Engagement page: prefer mimecast email counts in detail panel sub-label

Graph API has 48-72hr reporting lag; mimecast is same-day
This commit is contained in:
lorentz 2026-06-02 20:25:14 -04:00
parent 078e087d5f
commit 758b7e7f15
4 changed files with 38 additions and 7 deletions

View file

@ -207,6 +207,22 @@ export async function GET(
[user.email, periodDays]
).catch(() => null);
// Mimecast real-time email counts
const mimecastEmailsResult = await postgresClient.query(
`SELECT
COUNT(*) FILTER (WHERE LOWER(sender_address) = LOWER($1) AND direction IN ('outbound','internal') AND sent_datetime >= NOW() - INTERVAL '7 days') AS sent_d7,
COUNT(*) FILTER (WHERE LOWER(sender_address) = LOWER($1) AND direction IN ('outbound','internal') AND sent_datetime >= NOW() - INTERVAL '30 days') AS sent_d30,
COUNT(*) FILTER (WHERE LOWER(sender_address) = LOWER($1) AND direction IN ('outbound','internal') AND sent_datetime >= NOW() - INTERVAL '90 days') AS sent_d90,
COUNT(*) FILTER (WHERE LOWER(recipient_address) = LOWER($1) AND direction IN ('inbound','internal') AND status IN ('archived','accepted') AND sent_datetime >= NOW() - INTERVAL '7 days') AS received_d7,
COUNT(*) FILTER (WHERE LOWER(recipient_address) = LOWER($1) AND direction IN ('inbound','internal') AND status IN ('archived','accepted') AND sent_datetime >= NOW() - INTERVAL '30 days') AS received_d30,
COUNT(*) FILTER (WHERE LOWER(recipient_address) = LOWER($1) AND direction IN ('inbound','internal') AND status IN ('archived','accepted') AND sent_datetime >= NOW() - INTERVAL '90 days') AS received_d90
FROM mimecast_messages
WHERE LOWER(sender_address) = LOWER($1)
OR (LOWER(recipient_address) = LOWER($1) AND direction IN ('inbound','internal') AND status IN ('archived','accepted'))`,
[user.email]
).catch(() => null);
const me = mimecastEmailsResult?.rows[0];
// After-hours meetings (5:30 PM 7:00 AM America/New_York)
const afterHoursMeetingsResult = await postgresClient.query(
`SELECT COUNT(*) as count
@ -496,6 +512,11 @@ export async function GET(
messagesPct: totalMessages > 0 ? Math.round((afterHoursMessages / totalMessages) * 100) : 0,
meetingsPct: totalMeetings > 0 ? Math.round((afterHoursMeetings / totalMeetings) * 100) : 0,
},
emails: me ? {
d7: { sent: parseInt(me.sent_d7 ?? 0), received: parseInt(me.received_d7 ?? 0) },
d30: { sent: parseInt(me.sent_d30 ?? 0), received: parseInt(me.received_d30 ?? 0) },
d90: { sent: parseInt(me.sent_d90 ?? 0), received: parseInt(me.received_d90 ?? 0) },
} : null,
snapshots: snapshotsResult.rows,
hours: hours
? {

View file

@ -82,8 +82,8 @@ export async function GET(request: NextRequest) {
COALESCE(es.teams_calls, 0) as teams_calls,
COALESCE(es.teams_meetings_attended, 0) as teams_meetings_attended,
COALESCE(es.teams_meetings_organized, 0) as teams_meetings_organized,
COALESCE(es.emails_sent, 0) as emails_sent,
COALESCE(es.emails_received, 0) as emails_received,
COALESCE(mc.emails_sent, 0) as emails_sent,
COALESCE(mc.emails_received, 0) as emails_received,
COALESCE(es.emails_read, 0) as emails_read,
COALESCE(es.audio_duration_seconds, 0) as audio_duration_seconds,
COALESCE(es.meeting_duration_seconds, 0) as meeting_duration_seconds,
@ -139,6 +139,14 @@ export async function GET(request: NextRequest) {
WHERE start_time >= NOW() - INTERVAL '${interval}'
GROUP BY host_email
) zm ON LOWER(r.email) = LOWER(zm.host_email)
LEFT JOIN LATERAL (
SELECT
COUNT(*) FILTER (WHERE LOWER(mm.sender_address) = LOWER(gu.email) AND mm.direction IN ('outbound', 'internal')) AS emails_sent,
COUNT(*) FILTER (WHERE LOWER(mm.recipient_address) = LOWER(gu.email) AND mm.direction IN ('inbound', 'internal') AND mm.status IN ('archived', 'accepted')) AS emails_received
FROM mimecast_messages mm
WHERE (LOWER(mm.sender_address) = LOWER(gu.email) OR LOWER(mm.recipient_address) = LOWER(gu.email))
AND mm.sent_datetime >= NOW() - INTERVAL '${interval}'
) mc ON true
WHERE gu.account_enabled = true
AND LOWER(gu.email) LIKE '%@wulfconsulting.%'
AND LOWER(gu.email) NOT LIKE '%#ext#%'