advanced_search
Filter and find emails by subject, sender, date, attachment, or read status. Use advanced criteria to locate specific messages in your Fastmail account.
Instructions
Advanced email search with multiple criteria
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| query | No | Text to search for in subject/body | |
| from | No | Filter by sender email | |
| to | No | Filter by recipient email | |
| subject | No | Filter by subject | |
| hasAttachment | No | Filter emails with attachments | |
| isUnread | No | Filter unread emails | |
| isPinned | No | Filter pinned emails | |
| mailboxId | No | Search within specific mailbox | |
| after | No | Emails after this date (ISO 8601) | |
| before | No | Emails before this date (ISO 8601) | |
| limit | No | Maximum results (default: 50) | |
| ascending | No | Sort oldest first instead of newest first (default: false) |
Implementation Reference
- src/index.ts:754-811 (registration)Tool registration in ListToolsRequestSchema: defines 'advanced_search' tool name, description, and input schema with all parameters (query, from, to, subject, hasAttachment, isUnread, isPinned, mailboxId, after, before, limit, ascending).
{ name: 'advanced_search', description: 'Advanced email search with multiple criteria', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Text to search for in subject/body', }, from: { type: 'string', description: 'Filter by sender email', }, to: { type: 'string', description: 'Filter by recipient email', }, subject: { type: 'string', description: 'Filter by subject', }, hasAttachment: { type: 'boolean', description: 'Filter emails with attachments', }, isUnread: { type: 'boolean', description: 'Filter unread emails', }, isPinned: { type: 'boolean', description: 'Filter pinned emails', }, mailboxId: { type: 'string', description: 'Search within specific mailbox', }, after: { type: 'string', description: 'Emails after this date (ISO 8601)', }, before: { type: 'string', description: 'Emails before this date (ISO 8601)', }, limit: { type: ['number', 'string'], description: 'Maximum results (default: 50)', default: 50, }, ascending: { type: 'boolean', description: 'Sort oldest first instead of newest first (default: false)', }, }, }, }, - src/index.ts:1573-1588 (handler)CallToolRequestSchema handler for 'advanced_search': extracts arguments, calls client.advancedSearch() with the filter object, and returns JSON results.
case 'advanced_search': { const { query, from, to, subject, hasAttachment, isUnread, isPinned, mailboxId, after, before, limit, ascending } = args as any; const client = initializeClient(); const validLimit = Math.min(Math.max(Number(limit) || 50, 1), 100); const emails = await client.advancedSearch({ query, from, to, subject, hasAttachment, isUnread, isPinned, mailboxId, after, before, limit: validLimit, ascending }); return { content: [ { type: 'text', text: JSON.stringify(emails, null, 2), }, ], }; } - src/jmap-client.ts:1181-1244 (handler)Core implementation of JmapClient.advancedSearch(): builds JMAP filter conditions (text, from, to, subject, hasAttachment, isUnread/$seen, isPinned/$flagged, inMailbox, after, before), handles hasKeyword/notKeyword conflicts with AND operator, executes Email/query + Email/get JMAP calls, and returns the results.
async advancedSearch(filters: { query?: string; from?: string; to?: string; subject?: string; hasAttachment?: boolean; isUnread?: boolean; isPinned?: boolean; mailboxId?: string; after?: string; before?: string; limit?: number; ascending?: boolean; }): Promise<any[]> { const session = await this.getSession(); // Build JMAP filter object const filter: any = {}; if (filters.query) filter.text = filters.query; if (filters.from) filter.from = filters.from; if (filters.to) filter.to = filters.to; if (filters.subject) filter.subject = filters.subject; if (filters.hasAttachment !== undefined) filter.hasAttachment = filters.hasAttachment; if (filters.isUnread === true) filter.notKeyword = '$seen'; else if (filters.isUnread === false) filter.hasKeyword = '$seen'; if (filters.isPinned === true) filter.hasKeyword = '$flagged'; if (filters.isPinned === false) filter.notKeyword = '$flagged'; if (filters.mailboxId) filter.inMailbox = filters.mailboxId; if (filters.after) filter.after = filters.after; if (filters.before) filter.before = filters.before; // When both isUnread and isPinned are set, hasKeyword/notKeyword may conflict. // JMAP FilterCondition only supports one hasKeyword, so wrap in an AND operator. let finalFilter: any = filter; if (filters.isUnread !== undefined && filters.isPinned !== undefined) { delete filter.hasKeyword; delete filter.notKeyword; const conditions: any[] = [filter]; conditions.push(filters.isUnread ? { notKeyword: '$seen' } : { hasKeyword: '$seen' }); conditions.push(filters.isPinned ? { hasKeyword: '$flagged' } : { notKeyword: '$flagged' }); finalFilter = { operator: 'AND', conditions }; } const request: JmapRequest = { using: ['urn:ietf:params:jmap:core', 'urn:ietf:params:jmap:mail'], methodCalls: [ ['Email/query', { accountId: session.accountId, filter: finalFilter, sort: [{ property: 'receivedAt', isAscending: filters.ascending ?? false }], limit: Math.min(filters.limit || 50, 100) }, 'query'], ['Email/get', { accountId: session.accountId, '#ids': { resultOf: 'query', name: 'Email/query', path: '/ids' }, properties: ['id', 'subject', 'from', 'to', 'cc', 'replyTo', 'receivedAt', 'preview', 'hasAttachment', 'keywords', 'threadId'] }, 'emails'] ] }; const response = await this.makeRequest(request); return this.getListResult(response, 1); } - src/index.ts:757-810 (schema)Input schema for 'advanced_search' tool: defines all filter properties and their types (string, boolean, number/string for limit, boolean for ascending).
inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Text to search for in subject/body', }, from: { type: 'string', description: 'Filter by sender email', }, to: { type: 'string', description: 'Filter by recipient email', }, subject: { type: 'string', description: 'Filter by subject', }, hasAttachment: { type: 'boolean', description: 'Filter emails with attachments', }, isUnread: { type: 'boolean', description: 'Filter unread emails', }, isPinned: { type: 'boolean', description: 'Filter pinned emails', }, mailboxId: { type: 'string', description: 'Search within specific mailbox', }, after: { type: 'string', description: 'Emails after this date (ISO 8601)', }, before: { type: 'string', description: 'Emails before this date (ISO 8601)', }, limit: { type: ['number', 'string'], description: 'Maximum results (default: 50)', default: 50, }, ascending: { type: 'boolean', description: 'Sort oldest first instead of newest first (default: false)', }, }, }, - src/jmap-client.ts:1181-1194 (helper)Type signature of advancedSearch: defines the filters parameter interface with all optional fields (query, from, to, subject, hasAttachment, isUnread, isPinned, mailboxId, after, before, limit, ascending).
async advancedSearch(filters: { query?: string; from?: string; to?: string; subject?: string; hasAttachment?: boolean; isUnread?: boolean; isPinned?: boolean; mailboxId?: string; after?: string; before?: string; limit?: number; ascending?: boolean; }): Promise<any[]> {