imap-fetch.js 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. /**
  2. * @node imap-fetch
  3. * @name IMAP Fetch Email
  4. * @category email
  5. * @version 1.2.0
  6. * @description Fetch the full content of an email from an IMAP server.
  7. * @icon download
  8. */
  9. const configSchema = {
  10. type: 'object',
  11. properties: {
  12. credentialId: {
  13. type: 'string',
  14. title: 'IMAP Credential',
  15. description: 'Select the IMAP credential to use for authentication',
  16. dynamicOptions: {
  17. source: 'credentials',
  18. filter: { type: 'imap' }
  19. }
  20. },
  21. mailbox: {
  22. type: 'string',
  23. title: 'Mailbox',
  24. description: 'The mailbox containing the email',
  25. default: 'INBOX'
  26. },
  27. uid: {
  28. type: 'string',
  29. title: 'Email UID',
  30. description: 'The unique identifier of the email to fetch (can be provided from input)'
  31. },
  32. peek: {
  33. type: 'boolean',
  34. title: 'Peek (Don\'t Mark as Read)',
  35. description: 'Fetch without marking the email as read',
  36. default: true
  37. }
  38. },
  39. required: ['credentialId']
  40. };
  41. const inputSchema = {
  42. type: 'object',
  43. properties: {
  44. uid: {
  45. type: 'string',
  46. description: 'Email UID to fetch'
  47. },
  48. mailbox: {
  49. type: 'string',
  50. description: 'Override mailbox from input'
  51. }
  52. }
  53. };
  54. const outputSchema = {
  55. type: 'object',
  56. properties: {
  57. uid: { type: 'string', description: 'Unique email identifier' },
  58. subject: { type: 'string', description: 'Decoded - RFC 2047 encoded words are resolved by the IMAP client' },
  59. from: { type: 'string', description: 'Decoded, in "Name <address>" form' },
  60. to: { type: 'string', description: 'Decoded, in "Name <address>" form' },
  61. cc: { type: 'string', description: 'Decoded, in "Name <address>" form. Empty when the message carries no Cc' },
  62. date: { type: 'string' },
  63. body: { type: 'string', description: 'Email body text' },
  64. raw: { type: 'string', description: 'Raw email content (for attachment extraction)' },
  65. hasAttachments: { type: 'boolean', description: 'Whether email has attachments' },
  66. mailbox: { type: 'string' }
  67. }
  68. };
  69. const outputs = [
  70. { name: 'main', displayName: 'Email Content', type: 'object', color: '#8b5cf6' }
  71. ];
  72. /**
  73. * Check if email has attachments by looking for Content-Disposition: attachment
  74. * or multipart content types
  75. */
  76. function detectAttachments(rawEmail) {
  77. if (!rawEmail) return false;
  78. const lower = rawEmail.toLowerCase();
  79. return lower.includes('content-disposition: attachment') ||
  80. lower.includes('content-disposition:attachment') ||
  81. (lower.includes('multipart/mixed') && lower.includes('boundary='));
  82. }
  83. module.exports = {
  84. configSchema,
  85. inputSchema,
  86. outputSchema,
  87. outputs,
  88. async execute(config, input, context) {
  89. const credentialId = config.credentialId;
  90. const mailbox = input.mailbox || config.mailbox || 'INBOX';
  91. const uid = input.uid || config.uid;
  92. const peek = config.peek !== false; // Default to true (don't mark as read)
  93. if (!credentialId) {
  94. throw new Error('IMAP credential is required');
  95. }
  96. if (!uid) {
  97. throw new Error('Email UID is required');
  98. }
  99. // Fetch the email
  100. const fetchResult = smartbotic.imap.fetch({
  101. credentialId,
  102. mailbox,
  103. uid,
  104. peek
  105. });
  106. if (!fetchResult.success) {
  107. throw new Error(`IMAP fetch failed: ${fetchResult.error}`);
  108. }
  109. // Spread, not a field-by-field copy. Listing them by name is how cc
  110. // went missing the day it was added to the fetch result: the C++ side
  111. // set it, this dropped it, and nothing failed. `success` is removed
  112. // because it belongs to the call, not to the email.
  113. const { success, ...email } = fetchResult;
  114. return {
  115. ...email,
  116. hasAttachments: detectAttachments(fetchResult.raw),
  117. mailbox
  118. };
  119. }
  120. };