/** * @node imap-fetch * @name IMAP Fetch Email * @category email * @version 1.2.0 * @description Fetch the full content of an email from an IMAP server. * @icon download */ const configSchema = { type: 'object', properties: { credentialId: { type: 'string', title: 'IMAP Credential', description: 'Select the IMAP credential to use for authentication', dynamicOptions: { source: 'credentials', filter: { type: 'imap' } } }, mailbox: { type: 'string', title: 'Mailbox', description: 'The mailbox containing the email', default: 'INBOX' }, uid: { type: 'string', title: 'Email UID', description: 'The unique identifier of the email to fetch (can be provided from input)' }, peek: { type: 'boolean', title: 'Peek (Don\'t Mark as Read)', description: 'Fetch without marking the email as read', default: true } }, required: ['credentialId'] }; const inputSchema = { type: 'object', properties: { uid: { type: 'string', description: 'Email UID to fetch' }, mailbox: { type: 'string', description: 'Override mailbox from input' } } }; const outputSchema = { type: 'object', properties: { uid: { type: 'string', description: 'Unique email identifier' }, subject: { type: 'string', description: 'Decoded - RFC 2047 encoded words are resolved by the IMAP client' }, from: { type: 'string', description: 'Decoded, in "Name
" form' }, to: { type: 'string', description: 'Decoded, in "Name
" form' }, cc: { type: 'string', description: 'Decoded, in "Name
" form. Empty when the message carries no Cc' }, date: { type: 'string' }, body: { type: 'string', description: 'Email body text' }, raw: { type: 'string', description: 'Raw email content (for attachment extraction)' }, hasAttachments: { type: 'boolean', description: 'Whether email has attachments' }, mailbox: { type: 'string' } } }; const outputs = [ { name: 'main', displayName: 'Email Content', type: 'object', color: '#8b5cf6' } ]; /** * Check if email has attachments by looking for Content-Disposition: attachment * or multipart content types */ function detectAttachments(rawEmail) { if (!rawEmail) return false; const lower = rawEmail.toLowerCase(); return lower.includes('content-disposition: attachment') || lower.includes('content-disposition:attachment') || (lower.includes('multipart/mixed') && lower.includes('boundary=')); } module.exports = { configSchema, inputSchema, outputSchema, outputs, async execute(config, input, context) { const credentialId = config.credentialId; const mailbox = input.mailbox || config.mailbox || 'INBOX'; const uid = input.uid || config.uid; const peek = config.peek !== false; // Default to true (don't mark as read) if (!credentialId) { throw new Error('IMAP credential is required'); } if (!uid) { throw new Error('Email UID is required'); } // Fetch the email const fetchResult = smartbotic.imap.fetch({ credentialId, mailbox, uid, peek }); if (!fetchResult.success) { throw new Error(`IMAP fetch failed: ${fetchResult.error}`); } // Spread, not a field-by-field copy. Listing them by name is how cc // went missing the day it was added to the fetch result: the C++ side // set it, this dropped it, and nothing failed. `success` is removed // because it belongs to the call, not to the email. const { success, ...email } = fetchResult; return { ...email, hasAttachments: detectAttachments(fetchResult.raw), mailbox }; } };