From 04a720f0d0e0bcfb3bddb8eb85d28113544ec180 Mon Sep 17 00:00:00 2001 From: lorentz Date: Wed, 1 Apr 2026 10:14:16 -0400 Subject: [PATCH] fix: use KQL $search with ConsistencyLevel:eventual for mailbox search (Graph $filter unsupported for nested from/emailAddress/address) --- lib/services/msgraph-client.ts | 40 ++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/lib/services/msgraph-client.ts b/lib/services/msgraph-client.ts index 01cb2a0..fc962c1 100644 --- a/lib/services/msgraph-client.ts +++ b/lib/services/msgraph-client.ts @@ -338,31 +338,33 @@ export class MsGraphClient { userEmail: string; fromAddress?: string; subject?: string; - receivedAfter?: string; - receivedBefore?: string; maxResults?: number; }): Promise<{ id: string; subject: string; from: string; receivedDateTime: string; isRead: boolean }[]> { - const filters: string[] = []; - if (options.fromAddress) { - filters.push(`from/emailAddress/address eq '${options.fromAddress.replace(/'/g, "''")}'`); - } - if (options.subject) { - filters.push(`contains(subject,'${options.subject.replace(/'/g, "''")}') `); - } - if (options.receivedAfter) { - filters.push(`receivedDateTime ge ${options.receivedAfter}`); - } - if (options.receivedBefore) { - filters.push(`receivedDateTime le ${options.receivedBefore}`); - } - if (!filters.length) throw new Error('At least one search filter required'); + // Exchange Online requires KQL $search for nested properties like from/emailAddress/address + const kqlParts: string[] = []; + if (options.fromAddress) kqlParts.push(`from:${options.fromAddress}`); + if (options.subject) kqlParts.push(`subject:${options.subject}`); + if (!kqlParts.length) throw new Error('At least fromAddress or subject required'); const top = Math.min(options.maxResults ?? 50, 100); - const qs = `$filter=${encodeURIComponent(filters.join(' and '))}&$select=id,subject,from,receivedDateTime,isRead&$top=${top}&$orderby=receivedDateTime desc`; + const searchVal = `"${kqlParts.join(' ')}"`; + const qs = `$search=${encodeURIComponent(searchVal)}&$select=id,subject,from,receivedDateTime,isRead&$top=${top}`; const url = `/users/${encodeURIComponent(options.userEmail)}/messages?${qs}`; - const data = await this.fetchJson<{ value: any[] }>(url); - return (data.value ?? []).map(m => ({ + const token = await this.getToken(); + const res = await fetch(`https://graph.microsoft.com/v1.0${url}`, { + headers: { + Authorization: `Bearer ${token}`, + Accept: 'application/json', + ConsistencyLevel: 'eventual', + }, + }); + if (!res.ok) { + const text = await res.text(); + throw new Error(`Graph API error ${res.status}: ${text}`); + } + const data = await res.json(); + return (data.value ?? []).map((m: any) => ({ id: m.id, subject: m.subject ?? '', from: m.from?.emailAddress?.address ?? '',