imap-trigger.js 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. /**
  2. * @node imap-trigger
  3. * @name IMAP Email Trigger
  4. * @category email
  5. * @version 1.3.0
  6. * @description Triggers when new emails are received. Polls the IMAP server for new messages.
  7. * @trigger
  8. * @scheduled
  9. * @icon mail
  10. */
  11. const configSchema = {
  12. type: 'object',
  13. properties: {
  14. credentialId: {
  15. type: 'string',
  16. title: 'IMAP Credential',
  17. description: 'Select the IMAP credential to use for authentication',
  18. dynamicOptions: {
  19. source: 'credentials',
  20. filter: { type: 'imap' }
  21. }
  22. },
  23. pollInterval: {
  24. type: 'integer',
  25. title: 'Poll Interval (minutes)',
  26. description: 'How often to check for new emails when workflow is active. Set to 0 for manual-only execution.',
  27. default: 5,
  28. minimum: 0,
  29. maximum: 1440
  30. },
  31. mailbox: {
  32. type: 'string',
  33. title: 'Mailbox',
  34. description: 'The mailbox to monitor (e.g., INBOX)',
  35. default: 'INBOX'
  36. },
  37. filterUnread: {
  38. type: 'boolean',
  39. title: 'Only Unread',
  40. description: 'Only trigger on unread (unseen) emails',
  41. default: true
  42. },
  43. filterFrom: {
  44. type: 'string',
  45. title: 'Filter by From',
  46. description: 'Only trigger on emails from this address (optional)'
  47. },
  48. filterSubject: {
  49. type: 'string',
  50. title: 'Filter by Subject',
  51. description: 'Only trigger on emails containing this text in subject (optional)'
  52. },
  53. markAsRead: {
  54. type: 'boolean',
  55. title: 'Mark as Read',
  56. description: 'Mark emails as read after processing',
  57. default: true
  58. },
  59. limit: {
  60. type: 'integer',
  61. title: 'Max Emails',
  62. description: 'Maximum number of emails to process per trigger',
  63. default: 10,
  64. minimum: 1,
  65. maximum: 100
  66. },
  67. fetchContent: {
  68. type: 'boolean',
  69. title: 'Fetch Full Content',
  70. description: 'Fetch full email content including body and raw data (required for attachment extraction)',
  71. default: true
  72. }
  73. },
  74. required: ['credentialId']
  75. };
  76. const outputSchema = {
  77. type: 'object',
  78. properties: {
  79. emails: {
  80. type: 'array',
  81. items: {
  82. type: 'object',
  83. properties: {
  84. uid: { type: 'string', description: 'Unique email identifier' },
  85. subject: { type: 'string' },
  86. from: { type: 'string' },
  87. to: { type: 'string' },
  88. date: { type: 'string' },
  89. body: { type: 'string', description: 'Email body (if fetchContent enabled)' },
  90. raw: { type: 'string', description: 'Raw email content (if fetchContent enabled)' },
  91. hasAttachments: { type: 'boolean', description: 'Whether email has attachments' }
  92. }
  93. }
  94. },
  95. uids: {
  96. type: 'array',
  97. items: { type: 'string' },
  98. description: 'Array of message UIDs'
  99. },
  100. count: { type: 'integer' },
  101. mailbox: { type: 'string' },
  102. timestamp: { type: 'number' }
  103. }
  104. };
  105. const outputs = [
  106. { name: 'main', displayName: 'Email Received', type: 'object', color: '#22c55e' }
  107. ];
  108. /**
  109. * Check if email has attachments by looking for Content-Disposition: attachment
  110. * or multipart content types
  111. */
  112. function detectAttachments(rawEmail) {
  113. if (!rawEmail) return false;
  114. const lower = rawEmail.toLowerCase();
  115. return lower.includes('content-disposition: attachment') ||
  116. lower.includes('content-disposition:attachment') ||
  117. (lower.includes('multipart/mixed') && lower.includes('boundary='));
  118. }
  119. module.exports = {
  120. configSchema,
  121. outputSchema,
  122. outputs,
  123. async execute(config, input, context) {
  124. const {
  125. credentialId,
  126. mailbox = 'INBOX',
  127. filterUnread = true,
  128. filterFrom,
  129. filterSubject,
  130. markAsRead = true,
  131. limit = 10,
  132. fetchContent = true
  133. } = config;
  134. if (!credentialId) {
  135. throw new Error('IMAP credential is required');
  136. }
  137. // Build search criteria
  138. let criteria = filterUnread ? 'UNSEEN' : 'ALL';
  139. if (filterFrom) {
  140. criteria += ` FROM "${filterFrom}"`;
  141. }
  142. if (filterSubject) {
  143. criteria += ` SUBJECT "${filterSubject}"`;
  144. }
  145. // Search for emails
  146. const searchResult = smartbotic.imap.search({
  147. credentialId,
  148. mailbox,
  149. criteria,
  150. limit
  151. });
  152. if (!searchResult.success) {
  153. throw new Error(`IMAP search failed: ${searchResult.error}`);
  154. }
  155. const emails = [];
  156. const uids = searchResult.uids || [];
  157. // Fetch each email if fetchContent is enabled
  158. if (fetchContent) {
  159. for (const uid of uids) {
  160. // Use peek: true to avoid marking as read during fetch
  161. // Only mark as read explicitly if markAsRead is enabled
  162. const fetchResult = smartbotic.imap.fetch({
  163. credentialId,
  164. mailbox,
  165. uid,
  166. peek: !markAsRead // peek=true prevents marking as read
  167. });
  168. if (fetchResult.success) {
  169. const email = {
  170. uid: fetchResult.uid,
  171. subject: fetchResult.subject,
  172. from: fetchResult.from,
  173. to: fetchResult.to,
  174. date: fetchResult.date,
  175. body: fetchResult.body,
  176. raw: fetchResult.raw,
  177. hasAttachments: detectAttachments(fetchResult.raw)
  178. };
  179. emails.push(email);
  180. }
  181. }
  182. } else {
  183. // Just return UIDs without fetching content
  184. for (const uid of uids) {
  185. emails.push({ uid });
  186. // Mark as read if configured (when not fetching content)
  187. if (markAsRead) {
  188. smartbotic.imap.modify({
  189. credentialId,
  190. mailbox,
  191. uid,
  192. action: 'markRead'
  193. });
  194. }
  195. }
  196. }
  197. return {
  198. emails,
  199. uids,
  200. count: emails.length,
  201. mailbox,
  202. timestamp: Date.now()
  203. };
  204. }
  205. };