imap-search.js 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. /**
  2. * @node imap-search
  3. * @name IMAP Search
  4. * @category email
  5. * @version 1.2.0
  6. * @description Search and list emails from an IMAP server with various filters.
  7. * @icon search
  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 to search (e.g., INBOX, Sent, Drafts)',
  25. default: 'INBOX'
  26. },
  27. criteria: {
  28. type: 'string',
  29. title: 'Search Criteria',
  30. description: 'IMAP search criteria (e.g., ALL, UNSEEN, FROM user@example.com)',
  31. default: 'ALL'
  32. },
  33. filterUnread: {
  34. type: 'boolean',
  35. title: 'Only Unread',
  36. description: 'Add UNSEEN filter to search criteria',
  37. default: false
  38. },
  39. filterFrom: {
  40. type: 'string',
  41. title: 'Filter by From',
  42. description: 'Filter emails from this address (optional)'
  43. },
  44. filterSubject: {
  45. type: 'string',
  46. title: 'Filter by Subject',
  47. description: 'Filter emails containing this text in subject (optional)'
  48. },
  49. filterSince: {
  50. type: 'string',
  51. title: 'Since Date',
  52. description: 'Filter emails since this date (DD-Mon-YYYY format, e.g., 01-Jan-2024)'
  53. },
  54. limit: {
  55. type: 'integer',
  56. title: 'Limit',
  57. description: 'Maximum number of emails to return',
  58. default: 50,
  59. minimum: 1,
  60. maximum: 1000
  61. },
  62. fetchContent: {
  63. type: 'boolean',
  64. title: 'Fetch Full Content',
  65. description: 'Fetch full email content including body and raw data (required for attachment extraction)',
  66. default: false
  67. },
  68. markAsRead: {
  69. type: 'boolean',
  70. title: 'Mark as Read',
  71. description: 'Mark emails as read after fetching (only applies when Fetch Full Content is enabled)',
  72. default: false
  73. }
  74. },
  75. required: ['credentialId']
  76. };
  77. const inputSchema = {
  78. type: 'object',
  79. properties: {
  80. criteria: {
  81. type: 'string',
  82. description: 'Override search criteria from input'
  83. },
  84. mailbox: {
  85. type: 'string',
  86. description: 'Override mailbox from input'
  87. }
  88. }
  89. };
  90. const outputSchema = {
  91. type: 'object',
  92. properties: {
  93. uids: {
  94. type: 'array',
  95. items: { type: 'string' },
  96. description: 'Array of message UIDs'
  97. },
  98. count: {
  99. type: 'integer',
  100. description: 'Number of emails found'
  101. },
  102. emails: {
  103. type: 'array',
  104. description: 'Array of email objects (if fetchContent is enabled)',
  105. items: {
  106. type: 'object',
  107. properties: {
  108. uid: { type: 'string', description: 'Unique email identifier' },
  109. subject: { type: 'string' },
  110. from: { type: 'string' },
  111. to: { type: 'string' },
  112. date: { type: 'string' },
  113. body: { type: 'string', description: 'Email body' },
  114. raw: { type: 'string', description: 'Raw email content' },
  115. hasAttachments: { type: 'boolean', description: 'Whether email has attachments' }
  116. }
  117. }
  118. },
  119. mailbox: { type: 'string' },
  120. criteria: { type: 'string' }
  121. }
  122. };
  123. const outputs = [
  124. { name: 'main', displayName: 'Search Results', type: 'object', color: '#3b82f6' }
  125. ];
  126. /**
  127. * Check if email has attachments by looking for Content-Disposition: attachment
  128. * or multipart content types
  129. */
  130. function detectAttachments(rawEmail) {
  131. if (!rawEmail) return false;
  132. const lower = rawEmail.toLowerCase();
  133. return lower.includes('content-disposition: attachment') ||
  134. lower.includes('content-disposition:attachment') ||
  135. (lower.includes('multipart/mixed') && lower.includes('boundary='));
  136. }
  137. module.exports = {
  138. configSchema,
  139. inputSchema,
  140. outputSchema,
  141. outputs,
  142. async execute(config, input, context) {
  143. const credentialId = config.credentialId;
  144. const mailbox = input.mailbox || config.mailbox || 'INBOX';
  145. const limit = config.limit || 50;
  146. const fetchContent = config.fetchContent || false;
  147. const markAsRead = config.markAsRead || false;
  148. if (!credentialId) {
  149. throw new Error('IMAP credential is required');
  150. }
  151. // Build search criteria
  152. let criteria = input.criteria || config.criteria || 'ALL';
  153. if (config.filterUnread && criteria.indexOf('UNSEEN') === -1) {
  154. criteria = criteria === 'ALL' ? 'UNSEEN' : `${criteria} UNSEEN`;
  155. }
  156. if (config.filterFrom) {
  157. criteria += ` FROM "${config.filterFrom}"`;
  158. }
  159. if (config.filterSubject) {
  160. criteria += ` SUBJECT "${config.filterSubject}"`;
  161. }
  162. if (config.filterSince) {
  163. criteria += ` SINCE "${config.filterSince}"`;
  164. }
  165. // Perform search
  166. const searchResult = smartbotic.imap.search({
  167. credentialId,
  168. mailbox,
  169. criteria,
  170. limit
  171. });
  172. if (!searchResult.success) {
  173. throw new Error(`IMAP search failed: ${searchResult.error}`);
  174. }
  175. const result = {
  176. uids: searchResult.uids,
  177. count: searchResult.count,
  178. mailbox,
  179. criteria
  180. };
  181. // Optionally fetch content for each email
  182. if (fetchContent && searchResult.uids.length > 0) {
  183. result.emails = [];
  184. for (const uid of searchResult.uids) {
  185. // Use peek: true to avoid marking as read during fetch
  186. // Only mark as read explicitly if markAsRead is enabled
  187. const fetchResult = smartbotic.imap.fetch({
  188. credentialId,
  189. mailbox,
  190. uid,
  191. peek: !markAsRead // peek=true prevents marking as read
  192. });
  193. if (fetchResult.success) {
  194. result.emails.push({
  195. uid: fetchResult.uid,
  196. subject: fetchResult.subject,
  197. from: fetchResult.from,
  198. to: fetchResult.to,
  199. date: fetchResult.date,
  200. body: fetchResult.body,
  201. raw: fetchResult.raw,
  202. hasAttachments: detectAttachments(fetchResult.raw)
  203. });
  204. }
  205. }
  206. }
  207. return result;
  208. }
  209. };