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
| Name | Required | Description | Default |
|---|---|---|---|
| uid | Yes | Email UID | |
| mailbox | No | Mailbox name (default INBOX) | |
| maxChars | No | Max body characters to return (default 8000, max 50000) | |
| includeHeaders | No | If true, include a headers object with to/cc/replyTo/messageId/inReplyTo/references/listUnsubscribe |
Implementation Reference
- lib/imap.js:1280-1387 (handler)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; }