Skip to main content
Glama
adamzaidi

icloud-mcp

by adamzaidi

get_email

Retrieve complete email content from iCloud Mail using the email's unique identifier (UID). Specify mailbox, character limits, and header inclusion for precise email access.

Instructions

Get full content of a specific email by UID

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
uidYesEmail UID
mailboxNoMailbox name (default INBOX)
maxCharsNoMax body characters to return (default 8000, max 50000)
includeHeadersNoIf true, include a headers object with to/cc/replyTo/messageId/inReplyTo/references/listUnsubscribe

Implementation Reference

  • The function 'getEmailContent' acts as the 'get_email' tool, responsible for fetching and parsing the content of an email (including body, metadata, and attachments) from an IMAP mailbox.
    export async function getEmailContent(uid, mailbox = 'INBOX', maxChars = 8000, includeHeaders = false, creds = null) {
      const client = createRateLimitedClient(creds);
      await client.connect();
      await client.mailboxOpen(mailbox);
    
      const fetchOpts = { envelope: true, flags: true, bodyStructure: true };
      if (includeHeaders) fetchOpts.headers = new Set(['references', 'list-unsubscribe']);
      const meta = await client.fetchOne(uid, fetchOpts, { uid: true });
      if (!meta) {
        await client.logout();
        return { uid, subject: null, from: null, date: null, flags: [], body: '(email not found)' };
      }
    
      let body = '(body unavailable)';
    
      try {
        const struct = meta.bodyStructure;
        if (!struct) throw new Error('no bodyStructure');
    
        const textPart = findTextPart(struct);
    
        if (!textPart) {
          body = '(no readable text — email may be image-only or have no text parts)';
        } else {
          // Single-part messages use 'TEXT'; multipart use dot-notation part id (e.g. '1', '1.1')
          const imapKey = textPart.partId ?? 'TEXT';
    
          // For large parts, cap the fetch at 12KB to avoid downloading multi-MB newsletters
          const fetchSpec = (textPart.size && textPart.size > 150_000)
            ? [{ key: imapKey, start: 0, maxLength: 12_000 }]
            : [imapKey];
    
          const partMsg = await Promise.race([
            client.fetchOne(uid, { bodyParts: fetchSpec }, { uid: true }),
            new Promise((_, reject) => setTimeout(() => reject(new Error('body fetch timeout')), 10_000))
          ]);
    
          // bodyParts is a Map — try the key as-is, then uppercase, then lowercase
          const partBuffer = partMsg?.bodyParts?.get(imapKey)
            ?? partMsg?.bodyParts?.get(imapKey.toUpperCase())
            ?? partMsg?.bodyParts?.get(imapKey.toLowerCase());
    
          if (!partBuffer || partBuffer.length === 0) throw new Error('empty body part');
    
          const decoded = decodeTransferEncoding(partBuffer, textPart.encoding);
          let text = await decodeCharset(decoded, textPart.charset);
    
          if (textPart.type === 'text/html') text = stripHtml(text);
    
          const clampedMaxChars = Math.min(maxChars, 50_000);
          if (text.length > clampedMaxChars) {
            text = text.slice(0, clampedMaxChars) + `\n\n[... truncated — ${text.length.toLocaleString()} chars total]`;
          }
    
          body = text.trim() || '(empty body)';
    
          if (textPart.size && textPart.size > 150_000) {
            body += `\n\n[Note: email body is large (${Math.round(textPart.size / 1024)}KB) — showing first 12KB]`;
          }
        }
      } catch {
        // Fallback: raw source slice (original behaviour)
        try {
          const sourceMsg = await Promise.race([
            client.fetchOne(uid, { source: true }, { uid: true }),
            new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), 5_000))
          ]);
          if (sourceMsg?.source) {
            const raw = sourceMsg.source.toString();
            const bodyStart = raw.indexOf('\r\n\r\n');
            body = '[raw fallback]\n' + (bodyStart > -1 ? raw.slice(bodyStart + 4, bodyStart + 2000) : raw.slice(0, 2000));
          }
        } catch { /* leave as unavailable */ }
      }
    
      await client.logout();
    
      const attachments = meta.bodyStructure ? findAttachments(meta.bodyStructure) : [];
      const result = {
        uid: meta.uid,
        subject: meta.envelope.subject,
        from: meta.envelope.from?.[0]?.address,
        date: meta.envelope.date,
        flags: [...meta.flags],
        attachments: {
          count: attachments.length,
          items: attachments.map(a => ({ partId: a.partId, filename: a.filename, mimeType: a.mimeType, size: a.size }))
        },
        body
      };
    
      if (includeHeaders) {
        // imapflow returns headers as a raw Buffer — parse it as text
        const rawRefs = extractRawHeader(meta.headers, 'references');
        const rawUnsub = extractRawHeader(meta.headers, 'list-unsubscribe');
        result.headers = {
          to: meta.envelope.to?.map(a => a.address) ?? [],
          cc: meta.envelope.cc?.map(a => a.address) ?? [],
          replyTo: meta.envelope.replyTo?.[0]?.address ?? null,
          messageId: meta.envelope.messageId ?? null,
          inReplyTo: meta.envelope.inReplyTo ?? null,
          references: rawRefs ? rawRefs.split(/\s+/).filter(s => s.startsWith('<')) : [],
          listUnsubscribe: rawUnsub || null
        };
      }
    
      return result;
    }

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