fix: use KQL $search with ConsistencyLevel:eventual for mailbox search (Graph $filter unsupported for nested from/emailAddress/address)

This commit is contained in:
lorentz 2026-04-01 10:14:16 -04:00
parent bc3904de4e
commit 04a720f0d0

View file

@ -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 ?? '',