Skip to main content
Glama
adamzaidi

icloud-mcp

by adamzaidi

get_thread

Retrieve all emails in the same conversation thread as a specified email using subject matching and header analysis. Works with iCloud Mail to group related messages for better email organization.

Instructions

Find all emails in the same thread as a given email. Uses subject matching + References/In-Reply-To header filtering. Note: iCloud does not support server-side threading — results are approximate.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
uidYesEmail UID to find the thread for
mailboxNoMailbox to search (default INBOX)

Implementation Reference

  • Implementation of the get_thread function which retrieves email threads by matching Message-ID, In-Reply-To, and References headers within a set of candidates found via subject search.
    export async function getThread(uid, mailbox = 'INBOX', creds = null) {
      const THREAD_CANDIDATE_CAP = 100;
      const client = createRateLimitedClient(creds);
      await client.connect();
      await client.mailboxOpen(mailbox);
    
      // Fetch target email's envelope + raw headers for threading
      const meta = await client.fetchOne(uid, {
        envelope: true,
        flags: true,
        headers: new Set(['references', 'in-reply-to'])
      }, { uid: true });
      if (!meta) throw new Error(`Email UID ${uid} not found`);
    
      const targetMessageId = meta.envelope?.messageId ?? null;
      const rawRefs = extractRawHeader(meta.headers, 'references');
      const rawInReplyTo = extractRawHeader(meta.headers, 'in-reply-to');
    
      // Build full reference set for this email
      const threadRefs = new Set();
      if (targetMessageId) threadRefs.add(targetMessageId.trim());
      if (rawInReplyTo) threadRefs.add(rawInReplyTo.trim());
      if (rawRefs) {
        rawRefs.split(/\s+/).filter(s => s.startsWith('<') && s.endsWith('>')).forEach(r => threadRefs.add(r));
      }
    
      const normalizedSubject = stripSubjectPrefixes(meta.envelope?.subject ?? '');
    
      // SEARCH SUBJECT for candidates (iCloud doesn't support SEARCH HEADER)
      let candidateUids = [];
      if (normalizedSubject) {
        const raw = await client.search({ subject: normalizedSubject }, { uid: true });
        candidateUids = Array.isArray(raw) ? raw : [];
      }
    
      const candidatesCapped = candidateUids.length > THREAD_CANDIDATE_CAP;
      if (candidatesCapped) candidateUids = candidateUids.slice(-THREAD_CANDIDATE_CAP);
    
      // Fetch envelopes + headers for candidates to filter by References overlap
      const threadEmails = [];
      if (candidateUids.length > 0) {
        for await (const msg of client.fetch(candidateUids, {
          envelope: true,
          flags: true,
          headers: new Set(['references', 'in-reply-to'])
        }, { uid: true })) {
          const msgId = msg.envelope?.messageId ?? null;
          const msgRefs = extractRawHeader(msg.headers, 'references');
          const msgInReplyTo = extractRawHeader(msg.headers, 'in-reply-to');
    
          // Build this message's reference set
          const msgRefSet = new Set();
          if (msgId) msgRefSet.add(msgId.trim());
          if (msgInReplyTo) msgRefSet.add(msgInReplyTo.trim());
          if (msgRefs) msgRefs.split(/\s+/).filter(s => s.startsWith('<')).forEach(r => msgRefSet.add(r));
    
          // Include if there's any Reference chain overlap
          const hasOverlap = (msgId && threadRefs.has(msgId.trim())) ||
            [...threadRefs].some(r => msgRefSet.has(r));
    
          if (hasOverlap) {
            threadEmails.push({
              uid: msg.uid,
              subject: msg.envelope?.subject,
              from: msg.envelope?.from?.[0]?.address,
              date: msg.envelope?.date,
              seen: msg.flags?.has('\\Seen') ?? false,
              flagged: msg.flags?.has('\\Flagged') ?? false,
              messageId: msgId
            });
          }
        }
      }
    
      await client.logout();
    
      // Sort by date ascending
      threadEmails.sort((a, b) => {
        const da = a.date ? new Date(a.date).getTime() : 0;
        const db = b.date ? new Date(b.date).getTime() : 0;
        return da - db;
      });
    
      return {
        uid,
        subject: normalizedSubject || meta.envelope?.subject,
        count: threadEmails.length,
        emails: threadEmails,
        ...(candidatesCapped && {
          candidatesCapped: true,
          note: `Subject search returned more than ${THREAD_CANDIDATE_CAP} candidates — thread results may be incomplete`
        })
      };
    }

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