Skip to main content
Glama
adamzaidi

icloud-mcp

by adamzaidi

search_emails

Search iCloud Mail by keywords or specific fields like sender, subject, or date with filters for unread status, attachments, and domain.

Instructions

Search emails by keyword or targeted field queries, with optional filters for date, read status, domain, and more

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
queryNoSearch keyword (matches subject, sender, body — use OR across all fields)
subjectQueryNoMatch only in subject field
bodyQueryNoMatch only in body field
fromQueryNoMatch only in from/sender field
queryModeNoHow to combine subjectQuery/bodyQuery/fromQuery: or (default) or and
mailboxNoMailbox to search (default INBOX)
limitNoMax results (default 10)
includeSnippetNoIf true, include a 200-char body preview snippet for each result (max 10 emails)
senderNoMatch exact sender email address
domainNoMatch any sender from this domain (e.g. substack.com)
subjectNoKeyword to match in subject
beforeNoOnly emails before this date (YYYY-MM-DD)
sinceNoOnly emails since this date (YYYY-MM-DD)
unreadNoTrue for unread only, false for read only
flaggedNoTrue for flagged only, false for unflagged only
largerNoOnly emails larger than this size in KB
smallerNoOnly emails smaller than this size in KB
hasAttachmentNoOnly emails with attachments (client-side BODYSTRUCTURE scan — must be combined with other filters that narrow results to under 500 emails first)
accountNoAccount name to use (e.g. 'icloud', 'gmail'). Defaults to first configured account. Use list_accounts to see available accounts.

Implementation Reference

  • The handler function 'searchEmails' implements searching for emails using IMAP based on queries and filters.
    export async function searchEmails(query, mailbox = 'INBOX', limit = 10, filters = {}, options = {}, creds = null) {
      const { queryMode = 'or', subjectQuery, bodyQuery, fromQuery, includeSnippet = false } = options;
      const client = createRateLimitedClient(creds);
      await client.connect();
      await client.mailboxOpen(mailbox);
    
      // Build text query
      let textQuery;
      const targetedParts = [];
      if (subjectQuery) targetedParts.push({ subject: subjectQuery });
      if (bodyQuery) targetedParts.push({ body: bodyQuery });
      if (fromQuery) targetedParts.push({ from: fromQuery });
    
      if (targetedParts.length > 0) {
        // Targeted field queries
        if (queryMode === 'and') {
          textQuery = Object.assign({}, ...targetedParts); // IMAP AND is implicit
        } else {
          textQuery = targetedParts.length === 1 ? targetedParts[0] : { or: targetedParts };
        }
      } else if (query) {
        // Original OR across subject/from/body
        textQuery = { or: [{ subject: query }, { from: query }, { body: query }] };
      } else {
        textQuery = null;
      }
    
      const extraQuery = buildQuery(filters);
      const hasExtra = Object.keys(extraQuery).length > 0 && !extraQuery.all;
      const finalQuery = textQuery
        ? (hasExtra ? { ...textQuery, ...extraQuery } : textQuery)
        : (hasExtra ? extraQuery : { all: true });
    
      let uids = (await client.search(finalQuery, { uid: true })) ?? [];
      if (!Array.isArray(uids)) uids = [];
    
      if (filters.hasAttachment) {
        if (uids.length > ATTACHMENT_SCAN_LIMIT) {
          await client.logout();
          return { total: null, showing: 0, emails: [], error: `hasAttachment requires narrower filters first — ${uids.length} candidates exceeds scan limit of ${ATTACHMENT_SCAN_LIMIT}. Add from/since/before/subject filters to reduce the set.` };
        }
        uids = await filterUidsByAttachment(client, uids);
      }
    
      const emails = [];
      const recentUids = uids.slice(-limit).reverse();
      for (const uid of recentUids) {
        const msg = await client.fetchOne(uid, { envelope: true, flags: true }, { uid: true });
        if (msg) {
          emails.push({
            uid,
            subject: msg.envelope.subject,
            from: msg.envelope.from?.[0]?.address,
            date: msg.envelope.date,
            flagged: msg.flags.has('\\Flagged'),
            seen: msg.flags.has('\\Seen')
          });
        }
      }
    
      // Fetch body snippets if requested (max 10 emails to avoid timeout)
      if (includeSnippet && emails.length > 0) {
        for (const email of emails.slice(0, 10)) {
          try {
            const meta = await client.fetchOne(email.uid, { bodyStructure: true }, { uid: true });
            if (!meta?.bodyStructure) continue;
            const textPart = findTextPart(meta.bodyStructure);
            if (!textPart) continue;
            const imapKey = textPart.partId ?? 'TEXT';
            const partMsg = await client.fetchOne(email.uid, {
              bodyParts: [{ key: imapKey, start: 0, maxLength: 400 }]
            }, { uid: true });
            const buf = partMsg?.bodyParts?.get(imapKey)
              ?? partMsg?.bodyParts?.get(imapKey.toUpperCase())
              ?? partMsg?.bodyParts?.get(imapKey.toLowerCase());
            if (!buf) continue;
            const decoded = decodeTransferEncoding(buf, textPart.encoding);
            let text = await decodeCharset(decoded, textPart.charset);
            if (textPart.type === 'text/html') text = stripHtml(text);
            email.snippet = text.replace(/\s+/g, ' ').slice(0, 200).trim();
          } catch { /* skip snippet on error */ }
        }
      }
    
      await client.logout();
      return { total: uids.length, showing: emails.length, emails };
    }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/adamzaidi/icloud-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server