get_attachment
Download email attachments from iCloud Mail by specifying the email UID and attachment part ID, returning base64-encoded file content for integration with other tools.
Instructions
Download a specific attachment from an email. Returns the file content as base64-encoded data. Use list_attachments first to get the partId. Maximum 20 MB per request; use offset+length for larger files.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| uid | Yes | Email UID | |
| partId | Yes | IMAP body part ID from list_attachments (e.g. "2", "1.2") | |
| mailbox | No | Mailbox name (default INBOX) | |
| offset | No | Byte offset for paginated download (returns raw encoded bytes, not decoded) | |
| length | No | Max bytes to return for paginated download (default 20 MB) |
Implementation Reference
- lib/imap.js:1439-1530 (handler)The 'get_attachment' function is implemented in `lib/imap.js`. It fetches an email's body structure to locate the specified attachment part, then retrieves the raw data (optionally in chunks for large files), decodes it based on its transfer-encoding, and returns the data as base64.
export async function getAttachment(uid, partId, mailbox = 'INBOX', offset = null, length = null, creds = null) { const client = createRateLimitedClient(creds); await client.connect(); await client.mailboxOpen(mailbox); // First fetch bodyStructure to find the attachment and validate size const meta = await client.fetchOne(uid, { bodyStructure: true }, { uid: true }); if (!meta) throw new Error(`Email UID ${uid} not found`); const attachments = meta.bodyStructure ? findAttachments(meta.bodyStructure) : []; const att = attachments.find(a => a.partId === partId); if (!att) throw new Error(`Part ID "${partId}" not found in email UID ${uid}. Use list_attachments to see available parts.`); const isPaginated = offset !== null || length !== null; if (!isPaginated && att.size > MAX_ATTACHMENT_BYTES) { await client.logout(); return { error: `Attachment too large to download in one request (${Math.round(att.size / 1024 / 1024 * 10) / 10} MB). Use offset and length params to download in chunks (max ${MAX_ATTACHMENT_BYTES / 1024 / 1024} MB per request).`, filename: att.filename, mimeType: att.mimeType, size: att.size, totalSize: att.size }; } // Build fetch spec let fetchSpec; if (isPaginated) { const start = offset ?? 0; const maxLength = length ?? MAX_ATTACHMENT_BYTES; fetchSpec = [{ key: partId, start, maxLength }]; } else { fetchSpec = [partId]; } // Fetch the raw body part bytes const rawChunks = []; for await (const msg of client.fetch({ uid }, { bodyParts: fetchSpec }, { uid: true })) { const buf = msg.bodyParts?.get(partId) ?? msg.bodyParts?.get(partId.toUpperCase()) ?? msg.bodyParts?.get(partId.toLowerCase()); if (buf) rawChunks.push(buf); } await client.logout(); if (rawChunks.length === 0) throw new Error(`No data returned for part "${partId}" of UID ${uid}`); const raw = Buffer.concat(rawChunks); if (isPaginated) { // Paginated: return raw encoded bytes without transfer-encoding decode const fetchOffset = offset ?? 0; const actualLength = raw.length; const hasMore = att.size ? (fetchOffset + actualLength < att.size) : false; return { uid, partId, filename: att.filename, mimeType: att.mimeType, encoding: att.encoding, totalSize: att.size, offset: fetchOffset, length: actualLength, hasMore, data: raw.toString('base64'), dataEncoding: 'base64' }; } // Full download: decode transfer encoding const encoding = att.encoding.toLowerCase(); let decoded; if (encoding === 'base64') { decoded = Buffer.from(raw.toString('ascii').replace(/\s/g, ''), 'base64'); } else if (encoding === 'quoted-printable') { const qp = raw.toString('binary').replace(/=\r?\n/g, '').replace(/=([0-9A-Fa-f]{2})/g, (_, h) => String.fromCharCode(parseInt(h, 16))); decoded = Buffer.from(qp, 'binary'); } else { decoded = raw; // 7bit / 8bit / binary } return { uid, partId, filename: att.filename, mimeType: att.mimeType, size: decoded.length, encoding: att.encoding, data: decoded.toString('base64'), dataEncoding: 'base64' }; }