n8n-nodes-imap-advanced 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +157 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/nodes/ImapAdvanced/ImapAdvanced.node.d.ts +5 -0
- package/dist/nodes/ImapAdvanced/ImapAdvanced.node.js +211 -0
- package/dist/nodes/ImapAdvancedTrigger/ImapAdvancedTrigger.node.d.ts +5 -0
- package/dist/nodes/ImapAdvancedTrigger/ImapAdvancedTrigger.node.js +164 -0
- package/dist/nodes/shared/ImapAdvancedClient.d.ts +45 -0
- package/dist/nodes/shared/ImapAdvancedClient.js +217 -0
- package/package.json +50 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Maxime Olivier
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# n8n-nodes-imap-advanced
|
|
2
|
+
|
|
3
|
+
Advanced, **generic IMAP** community nodes for n8n (Mailcow, Dovecot, Gmail IMAP, Outlook IMAP, etc.).
|
|
4
|
+
|
|
5
|
+
This package is focused on practical email automation patterns that are hard to build with the default n8n email nodes alone:
|
|
6
|
+
|
|
7
|
+
- message threading from headers (`References`, `In-Reply-To`)
|
|
8
|
+
- robust flag/tag updates (including custom IMAP keywords)
|
|
9
|
+
- optional attachment download as n8n binary data
|
|
10
|
+
- trigger with `auto | idle | poll` behavior
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Included nodes
|
|
15
|
+
|
|
16
|
+
### 1) IMAP Advanced
|
|
17
|
+
Resource-based action node with these operations:
|
|
18
|
+
|
|
19
|
+
- **Message**
|
|
20
|
+
- `get`
|
|
21
|
+
- `search`
|
|
22
|
+
- `updateFlags`
|
|
23
|
+
- `move`
|
|
24
|
+
- `copy`
|
|
25
|
+
- `delete` (adds `\Deleted`)
|
|
26
|
+
- `undelete` (removes `\Deleted`)
|
|
27
|
+
- `expunge`
|
|
28
|
+
- **Thread**
|
|
29
|
+
- `getByMessage`
|
|
30
|
+
- **Mailbox**
|
|
31
|
+
- `list`
|
|
32
|
+
- `status`
|
|
33
|
+
|
|
34
|
+
### 2) IMAP Advanced Trigger
|
|
35
|
+
New message trigger with:
|
|
36
|
+
|
|
37
|
+
- modes: `auto`, `idle`, `poll`
|
|
38
|
+
- output formats: `headersSnippet`, `full`, `raw`
|
|
39
|
+
- attachment modes: `none`, `metadataOnly`, `binary`
|
|
40
|
+
- optional post-processing: mark seen, add flags, move message
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Credentials
|
|
45
|
+
|
|
46
|
+
This package **reuses n8n built-in IMAP credentials**:
|
|
47
|
+
|
|
48
|
+
- credential type: `imap`
|
|
49
|
+
- no custom IMAP credential is required
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Installation
|
|
54
|
+
|
|
55
|
+
### Community package in n8n
|
|
56
|
+
Install as a regular npm package where n8n runs:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
npm install n8n-nodes-imap-advanced
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
or from a local tarball:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
npm install /path/to/n8n-nodes-imap-advanced-0.1.0.tgz
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Then restart n8n.
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Quick workflow example (thread + attachments)
|
|
73
|
+
|
|
74
|
+
1. **IMAP Advanced Trigger** (`mode=auto`, `mailbox=INBOX`)
|
|
75
|
+
2. **IMAP Advanced** (`resource=thread`, `operation=getByMessage`)
|
|
76
|
+
3. loop over thread messages
|
|
77
|
+
4. **IMAP Advanced** (`resource=message`, `operation=get`, `attachmentsMode=binary`)
|
|
78
|
+
5. process content / attachments
|
|
79
|
+
6. **IMAP Advanced** (`resource=message`, `operation=updateFlags` or `move`)
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## Threading behavior
|
|
84
|
+
|
|
85
|
+
`Thread:getByMessage` strategy:
|
|
86
|
+
|
|
87
|
+
1. parse `References`
|
|
88
|
+
2. fallback to `In-Reply-To`
|
|
89
|
+
3. optional subject fallback (`Subject Fallback = true`)
|
|
90
|
+
|
|
91
|
+
Notes:
|
|
92
|
+
|
|
93
|
+
- IMAP has no universal thread API like Gmail API.
|
|
94
|
+
- Header-based threading is best effort and may vary across servers/mailboxes.
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## Attachments behavior
|
|
99
|
+
|
|
100
|
+
For `Message:get` and Trigger output:
|
|
101
|
+
|
|
102
|
+
- `none`: no attachment metadata/content
|
|
103
|
+
- `metadataOnly`: metadata only (filename, mime, size)
|
|
104
|
+
- `binary`: adds n8n binary fields (`attachment_0`, `attachment_1`, ... by default)
|
|
105
|
+
|
|
106
|
+
Attachment filtering options:
|
|
107
|
+
|
|
108
|
+
- max size (MB)
|
|
109
|
+
- allowed MIME list (CSV)
|
|
110
|
+
- filename regex
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## Flags / tags
|
|
115
|
+
|
|
116
|
+
IMAP “tags” are flags/keywords:
|
|
117
|
+
|
|
118
|
+
- system flags: `\Seen`, `\Answered`, `\Flagged`, `\Deleted`, `\Draft`
|
|
119
|
+
- custom keywords: e.g. `$n8n_processed`, `ai-replied`
|
|
120
|
+
|
|
121
|
+
Use `Message:updateFlags` with:
|
|
122
|
+
|
|
123
|
+
- `add`
|
|
124
|
+
- `remove`
|
|
125
|
+
- `replace`
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## Move behavior
|
|
130
|
+
|
|
131
|
+
`Message:move`:
|
|
132
|
+
|
|
133
|
+
- uses IMAP `MOVE` capability when server supports it
|
|
134
|
+
- otherwise falls back to `COPY + \Deleted + expunge` equivalent flow
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Development
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
npm install
|
|
142
|
+
npm run build
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## Current limitations
|
|
148
|
+
|
|
149
|
+
- `expunge` is mailbox-level in current implementation.
|
|
150
|
+
- Threading is single-mailbox oriented.
|
|
151
|
+
- IDLE mode includes practical polling safety to remain robust across server behaviors.
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## License
|
|
156
|
+
|
|
157
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { IExecuteFunctions, INodeExecutionData, INodeType, INodeTypeDescription } from 'n8n-workflow';
|
|
2
|
+
export declare class ImapAdvanced implements INodeType {
|
|
3
|
+
description: INodeTypeDescription;
|
|
4
|
+
execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
|
|
5
|
+
}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ImapAdvanced = void 0;
|
|
4
|
+
const ImapAdvancedClient_1 = require("../shared/ImapAdvancedClient");
|
|
5
|
+
function asArray(value) {
|
|
6
|
+
return Array.isArray(value) ? value : [];
|
|
7
|
+
}
|
|
8
|
+
class ImapAdvanced {
|
|
9
|
+
constructor() {
|
|
10
|
+
this.description = {
|
|
11
|
+
displayName: 'IMAP Advanced',
|
|
12
|
+
name: 'imapAdvanced',
|
|
13
|
+
icon: 'file:imap.svg',
|
|
14
|
+
group: ['transform'],
|
|
15
|
+
version: 1,
|
|
16
|
+
description: 'Generic IMAP operations with threading and attachments',
|
|
17
|
+
defaults: { name: 'IMAP Advanced' },
|
|
18
|
+
inputs: ['main'],
|
|
19
|
+
outputs: ['main'],
|
|
20
|
+
credentials: [{ name: 'imap', required: true }],
|
|
21
|
+
properties: [
|
|
22
|
+
{ displayName: 'Resource', name: 'resource', type: 'options', default: 'message', options: [
|
|
23
|
+
{ name: 'Message', value: 'message' },
|
|
24
|
+
{ name: 'Thread', value: 'thread' },
|
|
25
|
+
{ name: 'Mailbox', value: 'mailbox' },
|
|
26
|
+
] },
|
|
27
|
+
{ displayName: 'Operation', name: 'operation', type: 'options', default: 'get', displayOptions: { show: { resource: ['message'] } }, options: [
|
|
28
|
+
{ name: 'Get', value: 'get' },
|
|
29
|
+
{ name: 'Search', value: 'search' },
|
|
30
|
+
{ name: 'Update Flags', value: 'updateFlags' },
|
|
31
|
+
{ name: 'Move', value: 'move' },
|
|
32
|
+
{ name: 'Copy', value: 'copy' },
|
|
33
|
+
{ name: 'Delete', value: 'delete' },
|
|
34
|
+
{ name: 'Undelete', value: 'undelete' },
|
|
35
|
+
{ name: 'Expunge', value: 'expunge' },
|
|
36
|
+
] },
|
|
37
|
+
{ displayName: 'Operation', name: 'operation', type: 'options', default: 'getByMessage', displayOptions: { show: { resource: ['thread'] } }, options: [
|
|
38
|
+
{ name: 'Get by Message', value: 'getByMessage' },
|
|
39
|
+
] },
|
|
40
|
+
{ displayName: 'Operation', name: 'operation', type: 'options', default: 'list', displayOptions: { show: { resource: ['mailbox'] } }, options: [
|
|
41
|
+
{ name: 'List', value: 'list' },
|
|
42
|
+
{ name: 'Status', value: 'status' },
|
|
43
|
+
] },
|
|
44
|
+
{ displayName: 'Mailbox', name: 'mailbox', type: 'string', default: 'INBOX' },
|
|
45
|
+
{ displayName: 'Identifier Type', name: 'identifierType', type: 'options', default: 'uid', displayOptions: { show: { resource: ['message', 'thread'], operation: ['get', 'getByMessage'] } }, options: [
|
|
46
|
+
{ name: 'UID', value: 'uid' },
|
|
47
|
+
{ name: 'Message-ID', value: 'messageId' },
|
|
48
|
+
] },
|
|
49
|
+
{ displayName: 'UID', name: 'uid', type: 'number', default: 0, required: true, displayOptions: { show: { resource: ['message', 'thread'], operation: ['get', 'getByMessage'], identifierType: ['uid'] } } },
|
|
50
|
+
{ displayName: 'Message-ID', name: 'messageId', type: 'string', default: '', required: true, displayOptions: { show: { resource: ['message', 'thread'], operation: ['get', 'getByMessage'], identifierType: ['messageId'] } } },
|
|
51
|
+
{ displayName: 'Attachments Mode', name: 'attachmentsMode', type: 'options', default: 'metadataOnly', displayOptions: { show: { resource: ['message'], operation: ['get'] } }, options: [
|
|
52
|
+
{ name: 'None', value: 'none' },
|
|
53
|
+
{ name: 'Metadata Only', value: 'metadataOnly' },
|
|
54
|
+
{ name: 'Binary', value: 'binary' },
|
|
55
|
+
] },
|
|
56
|
+
{ displayName: 'Binary Prefix', name: 'binaryPrefix', type: 'string', default: 'attachment_', displayOptions: { show: { resource: ['message'], operation: ['get'], attachmentsMode: ['binary'] } } },
|
|
57
|
+
{ displayName: 'Max Attachment Size (MB)', name: 'maxAttachmentSizeMb', type: 'number', default: 25, displayOptions: { show: { resource: ['message'], operation: ['get'], attachmentsMode: ['binary', 'metadataOnly'] } } },
|
|
58
|
+
{ displayName: 'Allowed MIME Types (CSV)', name: 'allowedMimeTypes', type: 'string', default: '', displayOptions: { show: { resource: ['message'], operation: ['get'], attachmentsMode: ['binary', 'metadataOnly'] } } },
|
|
59
|
+
{ displayName: 'Filename Regex', name: 'filenameRegex', type: 'string', default: '', displayOptions: { show: { resource: ['message'], operation: ['get'], attachmentsMode: ['binary', 'metadataOnly'] } } },
|
|
60
|
+
{ displayName: 'UID List (CSV)', name: 'uidList', type: 'string', default: '', displayOptions: { show: { resource: ['message'], operation: ['updateFlags', 'move', 'copy', 'delete', 'undelete'] } } },
|
|
61
|
+
{ displayName: 'Action', name: 'flagAction', type: 'options', default: 'add', displayOptions: { show: { resource: ['message'], operation: ['updateFlags'] } }, options: [
|
|
62
|
+
{ name: 'Add', value: 'add' },
|
|
63
|
+
{ name: 'Remove', value: 'remove' },
|
|
64
|
+
{ name: 'Replace', value: 'replace' },
|
|
65
|
+
] },
|
|
66
|
+
{ displayName: 'Flags (CSV)', name: 'flagsCsv', type: 'string', default: '\\Seen', displayOptions: { show: { resource: ['message'], operation: ['updateFlags'] } } },
|
|
67
|
+
{ displayName: 'Target Mailbox', name: 'targetMailbox', type: 'string', default: '', displayOptions: { show: { resource: ['message'], operation: ['move', 'copy'] } } },
|
|
68
|
+
{ displayName: 'Raw Search JSON', name: 'searchJson', type: 'json', default: '{"seen": false}', displayOptions: { show: { resource: ['message'], operation: ['search'] } } },
|
|
69
|
+
{ displayName: 'Limit', name: 'limit', type: 'number', default: 100, displayOptions: { show: { resource: ['message'], operation: ['search'] } } },
|
|
70
|
+
{ displayName: 'Subject Fallback', name: 'subjectFallback', type: 'boolean', default: false, displayOptions: { show: { resource: ['thread'], operation: ['getByMessage'] } } },
|
|
71
|
+
],
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
async execute() {
|
|
75
|
+
const items = this.getInputData();
|
|
76
|
+
const output = [];
|
|
77
|
+
for (let i = 0; i < items.length; i++) {
|
|
78
|
+
const creds = await this.getCredentials('imap');
|
|
79
|
+
const client = new ImapAdvancedClient_1.ImapAdvancedClient(creds);
|
|
80
|
+
try {
|
|
81
|
+
await client.connect();
|
|
82
|
+
const resource = this.getNodeParameter('resource', i);
|
|
83
|
+
const operation = this.getNodeParameter('operation', i);
|
|
84
|
+
const mailbox = this.getNodeParameter('mailbox', i);
|
|
85
|
+
if (resource === 'mailbox' && operation === 'list') {
|
|
86
|
+
output.push({ json: { mailboxes: await client.listMailboxes() } });
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (resource === 'mailbox' && operation === 'status') {
|
|
90
|
+
output.push({ json: { status: await client.mailboxStatus(mailbox) } });
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
if (resource === 'message' && operation === 'get') {
|
|
94
|
+
const identifierType = this.getNodeParameter('identifierType', i);
|
|
95
|
+
let uid = Number(this.getNodeParameter('uid', i, 0));
|
|
96
|
+
if (identifierType === 'messageId') {
|
|
97
|
+
const messageId = this.getNodeParameter('messageId', i);
|
|
98
|
+
await client.openMailbox(mailbox);
|
|
99
|
+
const found = await client.search({ header: ['message-id', messageId] });
|
|
100
|
+
const foundList = Array.isArray(found) ? found : [];
|
|
101
|
+
uid = Number(foundList[0] || 0);
|
|
102
|
+
}
|
|
103
|
+
if (!uid)
|
|
104
|
+
throw new Error('Message not found');
|
|
105
|
+
const attachmentsMode = this.getNodeParameter('attachmentsMode', i);
|
|
106
|
+
const binaryPrefix = this.getNodeParameter('binaryPrefix', i, 'attachment_');
|
|
107
|
+
const maxAttachmentSizeMb = this.getNodeParameter('maxAttachmentSizeMb', i, 25);
|
|
108
|
+
const allowedMimeTypes = this.getNodeParameter('allowedMimeTypes', i, '');
|
|
109
|
+
const filenameRegex = this.getNodeParameter('filenameRegex', i, '');
|
|
110
|
+
const enriched = await (0, ImapAdvancedClient_1.enrichMessage)(this, i, client, mailbox, uid, true, attachmentsMode, binaryPrefix, { maxAttachmentSizeMb, allowedMimeTypes, filenameRegex });
|
|
111
|
+
output.push(enriched);
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
if (resource === 'message' && operation === 'search') {
|
|
115
|
+
const searchJson = this.getNodeParameter('searchJson', i, {});
|
|
116
|
+
const limit = this.getNodeParameter('limit', i, 100);
|
|
117
|
+
await client.openMailbox(mailbox);
|
|
118
|
+
const uids = await client.search(searchJson);
|
|
119
|
+
const uidList = Array.isArray(uids) ? uids : [];
|
|
120
|
+
output.push({ json: { uids: uidList.slice(0, limit), total: uidList.length } });
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
if (resource === 'message' && ['updateFlags', 'move', 'copy', 'delete', 'undelete'].includes(operation)) {
|
|
124
|
+
const uidList = (0, ImapAdvancedClient_1.parseUidList)(String(this.getNodeParameter('uidList', i, '')));
|
|
125
|
+
if (!uidList.length)
|
|
126
|
+
throw new Error('uidList is required');
|
|
127
|
+
if (operation === 'updateFlags') {
|
|
128
|
+
const flagAction = this.getNodeParameter('flagAction', i);
|
|
129
|
+
const flags = String(this.getNodeParameter('flagsCsv', i, ''))
|
|
130
|
+
.split(',')
|
|
131
|
+
.map((f) => f.trim())
|
|
132
|
+
.filter(Boolean);
|
|
133
|
+
output.push({ json: await client.updateFlags(uidList, mailbox, flagAction, flags) });
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
if (operation === 'move') {
|
|
137
|
+
const targetMailbox = this.getNodeParameter('targetMailbox', i);
|
|
138
|
+
output.push({ json: await client.move(uidList, mailbox, targetMailbox) });
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
if (operation === 'copy') {
|
|
142
|
+
const targetMailbox = this.getNodeParameter('targetMailbox', i);
|
|
143
|
+
output.push({ json: await client.copy(uidList, mailbox, targetMailbox) });
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
if (operation === 'delete') {
|
|
147
|
+
output.push({ json: await client.updateFlags(uidList, mailbox, 'add', ['\\Deleted']) });
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
if (operation === 'undelete') {
|
|
151
|
+
output.push({ json: await client.updateFlags(uidList, mailbox, 'remove', ['\\Deleted']) });
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (resource === 'message' && operation === 'expunge') {
|
|
156
|
+
output.push({ json: await client.expunge(mailbox) });
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
if (resource === 'thread' && operation === 'getByMessage') {
|
|
160
|
+
const identifierType = this.getNodeParameter('identifierType', i);
|
|
161
|
+
const subjectFallback = this.getNodeParameter('subjectFallback', i, false);
|
|
162
|
+
let uid = Number(this.getNodeParameter('uid', i, 0));
|
|
163
|
+
if (identifierType === 'messageId') {
|
|
164
|
+
const messageId = this.getNodeParameter('messageId', i);
|
|
165
|
+
await client.openMailbox(mailbox);
|
|
166
|
+
const found = await client.search({ header: ['message-id', messageId] });
|
|
167
|
+
const foundList = Array.isArray(found) ? found : [];
|
|
168
|
+
uid = Number(foundList[0] || 0);
|
|
169
|
+
}
|
|
170
|
+
if (!uid)
|
|
171
|
+
throw new Error('Base message not found');
|
|
172
|
+
const base = await (0, ImapAdvancedClient_1.enrichMessage)(this, i, client, mailbox, uid, true, 'none', 'attachment_', {});
|
|
173
|
+
const refs = (0, ImapAdvancedClient_1.parseReferences)(base.json.headers);
|
|
174
|
+
const uids = new Set([uid]);
|
|
175
|
+
for (const ref of refs) {
|
|
176
|
+
const found = await client.search({ header: ['message-id', ref] });
|
|
177
|
+
for (const u of (Array.isArray(found) ? found : []))
|
|
178
|
+
uids.add(Number(u));
|
|
179
|
+
}
|
|
180
|
+
if (subjectFallback && refs.length <= 1) {
|
|
181
|
+
const subj = String(base.json.subject || '').replace(/^(re|fwd):\s*/i, '').trim();
|
|
182
|
+
if (subj) {
|
|
183
|
+
const found = await client.search({ subject: subj });
|
|
184
|
+
for (const u of (Array.isArray(found) ? found : []))
|
|
185
|
+
uids.add(Number(u));
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
const messages = await client.fetchByUids(Array.from(uids).filter(Boolean), mailbox);
|
|
189
|
+
const normalized = messages
|
|
190
|
+
.map((m) => { var _a, _b, _c; return ({ uid: m.uid, subject: ((_a = m.envelope) === null || _a === void 0 ? void 0 : _a.subject) || '', date: ((_c = (_b = m.internalDate) === null || _b === void 0 ? void 0 : _b.toISOString) === null || _c === void 0 ? void 0 : _c.call(_b)) || null, flags: Array.from(m.flags || []) }); })
|
|
191
|
+
.sort((a, b) => String(a.date || '').localeCompare(String(b.date || '')));
|
|
192
|
+
output.push({ json: { messageUid: uid, references: refs, messages: normalized } });
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
throw new Error(`Unsupported combination ${resource}/${operation}`);
|
|
196
|
+
}
|
|
197
|
+
catch (error) {
|
|
198
|
+
if (this.continueOnFail()) {
|
|
199
|
+
output.push({ json: { error: error.message }, pairedItem: i });
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
throw error;
|
|
203
|
+
}
|
|
204
|
+
finally {
|
|
205
|
+
await client.logout();
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return [output];
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
exports.ImapAdvanced = ImapAdvanced;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { INodeType, INodeTypeDescription, ITriggerFunctions, ITriggerResponse } from 'n8n-workflow';
|
|
2
|
+
export declare class ImapAdvancedTrigger implements INodeType {
|
|
3
|
+
description: INodeTypeDescription;
|
|
4
|
+
trigger(this: ITriggerFunctions): Promise<ITriggerResponse>;
|
|
5
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ImapAdvancedTrigger = void 0;
|
|
4
|
+
const ImapAdvancedClient_1 = require("../shared/ImapAdvancedClient");
|
|
5
|
+
class ImapAdvancedTrigger {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.description = {
|
|
8
|
+
displayName: 'IMAP Advanced Trigger',
|
|
9
|
+
name: 'imapAdvancedTrigger',
|
|
10
|
+
icon: 'file:imap.svg',
|
|
11
|
+
group: ['trigger'],
|
|
12
|
+
version: 1,
|
|
13
|
+
description: 'Trigger for new emails with IMAP IDLE/poll modes',
|
|
14
|
+
defaults: { name: 'IMAP Advanced Trigger' },
|
|
15
|
+
inputs: [],
|
|
16
|
+
outputs: ['main'],
|
|
17
|
+
credentials: [{ name: 'imap', required: true }],
|
|
18
|
+
properties: [
|
|
19
|
+
{ displayName: 'Mailbox', name: 'mailbox', type: 'string', default: 'INBOX' },
|
|
20
|
+
{ displayName: 'Mode', name: 'mode', type: 'options', default: 'auto', options: [
|
|
21
|
+
{ name: 'Auto', value: 'auto' },
|
|
22
|
+
{ name: 'IDLE', value: 'idle' },
|
|
23
|
+
{ name: 'Polling', value: 'poll' },
|
|
24
|
+
] },
|
|
25
|
+
{ displayName: 'Poll Interval (seconds)', name: 'pollInterval', type: 'number', default: 60, displayOptions: { show: { mode: ['poll'] } } },
|
|
26
|
+
{ displayName: 'Output Format', name: 'outputFormat', type: 'options', default: 'headersSnippet', options: [
|
|
27
|
+
{ name: 'Headers + Snippet', value: 'headersSnippet' },
|
|
28
|
+
{ name: 'Full', value: 'full' },
|
|
29
|
+
{ name: 'Raw MIME', value: 'raw' },
|
|
30
|
+
] },
|
|
31
|
+
{ displayName: 'Attachments', name: 'attachmentsMode', type: 'options', default: 'none', options: [
|
|
32
|
+
{ name: 'None', value: 'none' },
|
|
33
|
+
{ name: 'Metadata Only', value: 'metadataOnly' },
|
|
34
|
+
{ name: 'Binary', value: 'binary' },
|
|
35
|
+
] },
|
|
36
|
+
{ displayName: 'Binary Prefix', name: 'binaryPrefix', type: 'string', default: 'attachment_', displayOptions: { show: { attachmentsMode: ['binary'] } } },
|
|
37
|
+
{ displayName: 'Mark as Seen', name: 'markSeen', type: 'boolean', default: false },
|
|
38
|
+
{ displayName: 'Add Flags (CSV)', name: 'addFlagsCsv', type: 'string', default: '' },
|
|
39
|
+
{ displayName: 'Move To Mailbox', name: 'moveToMailbox', type: 'string', default: '' },
|
|
40
|
+
{ displayName: 'Max Attachment Size (MB)', name: 'maxAttachmentSizeMb', type: 'number', default: 25, displayOptions: { show: { attachmentsMode: ['binary', 'metadataOnly'] } } },
|
|
41
|
+
{ displayName: 'Allowed MIME Types (CSV)', name: 'allowedMimeTypes', type: 'string', default: '', displayOptions: { show: { attachmentsMode: ['binary', 'metadataOnly'] } } },
|
|
42
|
+
{ displayName: 'Filename Regex', name: 'filenameRegex', type: 'string', default: '', displayOptions: { show: { attachmentsMode: ['binary', 'metadataOnly'] } } },
|
|
43
|
+
],
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
async trigger() {
|
|
47
|
+
const creds = await this.getCredentials('imap');
|
|
48
|
+
const mailbox = this.getNodeParameter('mailbox');
|
|
49
|
+
const mode = this.getNodeParameter('mode');
|
|
50
|
+
const pollInterval = Number(this.getNodeParameter('pollInterval', 60));
|
|
51
|
+
const outputFormat = this.getNodeParameter('outputFormat');
|
|
52
|
+
const attachmentsMode = this.getNodeParameter('attachmentsMode');
|
|
53
|
+
const binaryPrefix = this.getNodeParameter('binaryPrefix', 'attachment_');
|
|
54
|
+
const markSeen = this.getNodeParameter('markSeen', false);
|
|
55
|
+
const addFlagsCsv = this.getNodeParameter('addFlagsCsv', '');
|
|
56
|
+
const moveToMailbox = this.getNodeParameter('moveToMailbox', '');
|
|
57
|
+
const maxAttachmentSizeMb = this.getNodeParameter('maxAttachmentSizeMb', 25);
|
|
58
|
+
const allowedMimeTypes = this.getNodeParameter('allowedMimeTypes', '');
|
|
59
|
+
const filenameRegex = this.getNodeParameter('filenameRegex', '');
|
|
60
|
+
const staticData = this.getWorkflowStaticData('node');
|
|
61
|
+
let running = false;
|
|
62
|
+
let timer = null;
|
|
63
|
+
let idleClient = null;
|
|
64
|
+
const emitForUid = async (uid) => {
|
|
65
|
+
var _a;
|
|
66
|
+
const emitClient = new ImapAdvancedClient_1.ImapAdvancedClient(creds);
|
|
67
|
+
try {
|
|
68
|
+
await emitClient.connect();
|
|
69
|
+
const includeRaw = outputFormat === 'raw' || outputFormat === 'full' || attachmentsMode !== 'none';
|
|
70
|
+
const enriched = await (0, ImapAdvancedClient_1.enrichMessage)(this, 0, emitClient, mailbox, uid, includeRaw, attachmentsMode, binaryPrefix, { maxAttachmentSizeMb, allowedMimeTypes, filenameRegex });
|
|
71
|
+
if (outputFormat === 'headersSnippet') {
|
|
72
|
+
enriched.json.body = {
|
|
73
|
+
snippet: String(((_a = enriched.json.body) === null || _a === void 0 ? void 0 : _a.text) || '').slice(0, 500),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
if (outputFormat === 'raw') {
|
|
77
|
+
enriched.json.body = {};
|
|
78
|
+
}
|
|
79
|
+
const updates = [];
|
|
80
|
+
if (markSeen)
|
|
81
|
+
updates.push('\\Seen');
|
|
82
|
+
for (const f of addFlagsCsv.split(',').map((v) => v.trim()).filter(Boolean))
|
|
83
|
+
updates.push(f);
|
|
84
|
+
if (updates.length)
|
|
85
|
+
await emitClient.updateFlags([uid], mailbox, 'add', updates);
|
|
86
|
+
if (moveToMailbox)
|
|
87
|
+
await emitClient.move([uid], mailbox, moveToMailbox);
|
|
88
|
+
const executionData = { json: enriched.json };
|
|
89
|
+
if (enriched.binary)
|
|
90
|
+
executionData.binary = enriched.binary;
|
|
91
|
+
this.emit([[executionData]]);
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
this.logger.error(`IMAP Advanced Trigger emit failed: ${error.message}`);
|
|
95
|
+
}
|
|
96
|
+
finally {
|
|
97
|
+
await emitClient.logout();
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
const pollFn = async () => {
|
|
101
|
+
if (running)
|
|
102
|
+
return;
|
|
103
|
+
running = true;
|
|
104
|
+
try {
|
|
105
|
+
const pollClient = new ImapAdvancedClient_1.ImapAdvancedClient(creds);
|
|
106
|
+
await pollClient.connect();
|
|
107
|
+
await pollClient.openMailbox(mailbox);
|
|
108
|
+
const status = await pollClient.mailboxStatus(mailbox);
|
|
109
|
+
const lastUid = Number(staticData.lastUid || 0);
|
|
110
|
+
const maxUid = Number(status.uidNext || 0) - 1;
|
|
111
|
+
if (maxUid > lastUid) {
|
|
112
|
+
for (let uid = Math.max(lastUid + 1, 1); uid <= maxUid; uid++) {
|
|
113
|
+
await emitForUid(uid);
|
|
114
|
+
}
|
|
115
|
+
staticData.lastUid = maxUid;
|
|
116
|
+
}
|
|
117
|
+
else if (!lastUid && maxUid > 0) {
|
|
118
|
+
staticData.lastUid = maxUid;
|
|
119
|
+
}
|
|
120
|
+
await pollClient.logout();
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
this.logger.error(`IMAP Advanced Trigger poll failed: ${error.message}`);
|
|
124
|
+
}
|
|
125
|
+
finally {
|
|
126
|
+
running = false;
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
const setupIdle = async () => {
|
|
130
|
+
idleClient = new ImapAdvancedClient_1.ImapAdvancedClient(creds);
|
|
131
|
+
await idleClient.connect();
|
|
132
|
+
await idleClient.openMailbox(mailbox);
|
|
133
|
+
const baseStatus = await idleClient.mailboxStatus(mailbox);
|
|
134
|
+
if (!staticData.lastUid && Number(baseStatus.uidNext || 0) > 1)
|
|
135
|
+
staticData.lastUid = Number(baseStatus.uidNext) - 1;
|
|
136
|
+
idleClient.rawClient.on('exists', async () => {
|
|
137
|
+
await pollFn();
|
|
138
|
+
});
|
|
139
|
+
timer = setInterval(async () => pollFn(), 5 * 60 * 1000);
|
|
140
|
+
};
|
|
141
|
+
const initialClient = new ImapAdvancedClient_1.ImapAdvancedClient(creds);
|
|
142
|
+
await initialClient.connect();
|
|
143
|
+
const capabilities = initialClient.rawClient.capabilities || new Set();
|
|
144
|
+
await initialClient.logout();
|
|
145
|
+
const useIdle = mode === 'idle' || (mode === 'auto' && capabilities.has('IDLE'));
|
|
146
|
+
if (useIdle) {
|
|
147
|
+
await setupIdle();
|
|
148
|
+
await pollFn();
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
await pollFn();
|
|
152
|
+
timer = setInterval(async () => pollFn(), Math.max(10, pollInterval) * 1000);
|
|
153
|
+
}
|
|
154
|
+
return {
|
|
155
|
+
closeFunction: async () => {
|
|
156
|
+
if (timer)
|
|
157
|
+
clearInterval(timer);
|
|
158
|
+
if (idleClient)
|
|
159
|
+
await idleClient.logout();
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
exports.ImapAdvancedTrigger = ImapAdvancedTrigger;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { ImapFlow } from 'imapflow';
|
|
2
|
+
import type { IBinaryKeyData, IExecuteFunctions, IDataObject } from 'n8n-workflow';
|
|
3
|
+
export type AttachmentMode = 'none' | 'metadataOnly' | 'binary';
|
|
4
|
+
type AttachmentFilter = {
|
|
5
|
+
maxAttachmentSizeMb?: number;
|
|
6
|
+
allowedMimeTypes?: string;
|
|
7
|
+
filenameRegex?: string;
|
|
8
|
+
};
|
|
9
|
+
export declare class ImapAdvancedClient {
|
|
10
|
+
private readonly credentials;
|
|
11
|
+
private client;
|
|
12
|
+
constructor(credentials: IDataObject);
|
|
13
|
+
connect(): Promise<void>;
|
|
14
|
+
logout(): Promise<void>;
|
|
15
|
+
get rawClient(): ImapFlow;
|
|
16
|
+
openMailbox(mailbox: string): Promise<void>;
|
|
17
|
+
listMailboxes(): Promise<IDataObject[]>;
|
|
18
|
+
mailboxStatus(mailbox: string): Promise<import("imapflow").StatusObject>;
|
|
19
|
+
search(criteria: Record<string, unknown>): Promise<false | number[]>;
|
|
20
|
+
fetchOneByUid(uid: number, mailbox: string, includeRaw?: boolean): Promise<false | import("imapflow").FetchMessageObject>;
|
|
21
|
+
fetchByUids(uids: number[], mailbox: string): Promise<any[]>;
|
|
22
|
+
move(uids: number[], sourceMailbox: string, targetMailbox: string): Promise<{
|
|
23
|
+
method: string;
|
|
24
|
+
moved: number;
|
|
25
|
+
}>;
|
|
26
|
+
copy(uids: number[], sourceMailbox: string, targetMailbox: string): Promise<{
|
|
27
|
+
copied: number;
|
|
28
|
+
}>;
|
|
29
|
+
updateFlags(uids: number[], mailbox: string, action: 'add' | 'remove' | 'replace', flags: string[]): Promise<{
|
|
30
|
+
updated: number;
|
|
31
|
+
action: "add" | "remove" | "replace";
|
|
32
|
+
flags: string[];
|
|
33
|
+
}>;
|
|
34
|
+
expunge(mailbox: string): Promise<{
|
|
35
|
+
expunged: boolean;
|
|
36
|
+
}>;
|
|
37
|
+
}
|
|
38
|
+
export declare function normalizeAddresses(input: any): IDataObject[];
|
|
39
|
+
export declare function parseReferences(headers: Record<string, unknown>): string[];
|
|
40
|
+
export declare function parseUidList(input: string): number[];
|
|
41
|
+
export declare function enrichMessage(ctx: IExecuteFunctions, itemIndex: number, client: ImapAdvancedClient, mailbox: string, uid: number, includeRaw: boolean, attachmentsMode: AttachmentMode, binaryPrefix: string, filters: AttachmentFilter): Promise<{
|
|
42
|
+
json: IDataObject;
|
|
43
|
+
binary?: IBinaryKeyData;
|
|
44
|
+
}>;
|
|
45
|
+
export {};
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ImapAdvancedClient = void 0;
|
|
4
|
+
exports.normalizeAddresses = normalizeAddresses;
|
|
5
|
+
exports.parseReferences = parseReferences;
|
|
6
|
+
exports.parseUidList = parseUidList;
|
|
7
|
+
exports.enrichMessage = enrichMessage;
|
|
8
|
+
const imapflow_1 = require("imapflow");
|
|
9
|
+
const mailparser_1 = require("mailparser");
|
|
10
|
+
class ImapAdvancedClient {
|
|
11
|
+
constructor(credentials) {
|
|
12
|
+
var _a, _b;
|
|
13
|
+
this.credentials = credentials;
|
|
14
|
+
this.client = new imapflow_1.ImapFlow({
|
|
15
|
+
host: String(credentials.host || ''),
|
|
16
|
+
port: Number(credentials.port || 993),
|
|
17
|
+
secure: Boolean((_a = credentials.secure) !== null && _a !== void 0 ? _a : true),
|
|
18
|
+
auth: {
|
|
19
|
+
user: String(credentials.user || ''),
|
|
20
|
+
pass: String(credentials.password || ''),
|
|
21
|
+
},
|
|
22
|
+
tls: {
|
|
23
|
+
rejectUnauthorized: !Boolean((_b = credentials.allowUnauthorizedCerts) !== null && _b !== void 0 ? _b : false),
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
async connect() {
|
|
28
|
+
if (!this.client.usable)
|
|
29
|
+
await this.client.connect();
|
|
30
|
+
}
|
|
31
|
+
async logout() {
|
|
32
|
+
if (this.client.usable)
|
|
33
|
+
await this.client.logout();
|
|
34
|
+
}
|
|
35
|
+
get rawClient() {
|
|
36
|
+
return this.client;
|
|
37
|
+
}
|
|
38
|
+
async openMailbox(mailbox) {
|
|
39
|
+
await this.client.mailboxOpen(mailbox || 'INBOX');
|
|
40
|
+
}
|
|
41
|
+
async listMailboxes() {
|
|
42
|
+
const out = [];
|
|
43
|
+
const boxes = await this.client.list();
|
|
44
|
+
for (const box of boxes) {
|
|
45
|
+
out.push({
|
|
46
|
+
path: box.path,
|
|
47
|
+
delimiter: box.delimiter,
|
|
48
|
+
specialUse: box.specialUse,
|
|
49
|
+
listed: box.listed,
|
|
50
|
+
subscribed: box.subscribed,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
return out;
|
|
54
|
+
}
|
|
55
|
+
async mailboxStatus(mailbox) {
|
|
56
|
+
return this.client.status(mailbox || 'INBOX', { messages: true, unseen: true, uidNext: true, uidValidity: true, highestModseq: true });
|
|
57
|
+
}
|
|
58
|
+
async search(criteria) {
|
|
59
|
+
const query = criteria;
|
|
60
|
+
return this.client.search(query, { uid: true });
|
|
61
|
+
}
|
|
62
|
+
async fetchOneByUid(uid, mailbox, includeRaw = false) {
|
|
63
|
+
await this.openMailbox(mailbox);
|
|
64
|
+
return this.client.fetchOne(String(uid), {
|
|
65
|
+
envelope: true,
|
|
66
|
+
flags: true,
|
|
67
|
+
internalDate: true,
|
|
68
|
+
source: includeRaw,
|
|
69
|
+
bodyStructure: true,
|
|
70
|
+
headers: true,
|
|
71
|
+
}, { uid: true });
|
|
72
|
+
}
|
|
73
|
+
async fetchByUids(uids, mailbox) {
|
|
74
|
+
await this.openMailbox(mailbox);
|
|
75
|
+
const out = [];
|
|
76
|
+
if (!uids.length)
|
|
77
|
+
return out;
|
|
78
|
+
for await (const msg of this.client.fetch(uids, {
|
|
79
|
+
envelope: true,
|
|
80
|
+
flags: true,
|
|
81
|
+
internalDate: true,
|
|
82
|
+
headers: true,
|
|
83
|
+
}, { uid: true })) {
|
|
84
|
+
out.push(msg);
|
|
85
|
+
}
|
|
86
|
+
return out;
|
|
87
|
+
}
|
|
88
|
+
async move(uids, sourceMailbox, targetMailbox) {
|
|
89
|
+
await this.openMailbox(sourceMailbox);
|
|
90
|
+
const capabilities = this.client.capabilities || new Set();
|
|
91
|
+
if (capabilities.has('MOVE')) {
|
|
92
|
+
await this.client.messageMove(uids, targetMailbox, { uid: true });
|
|
93
|
+
return { method: 'move', moved: uids.length };
|
|
94
|
+
}
|
|
95
|
+
await this.client.messageCopy(uids, targetMailbox, { uid: true });
|
|
96
|
+
await this.client.messageFlagsAdd(uids, ['\\Deleted'], { uid: true });
|
|
97
|
+
await this.client.messageDelete(uids, { uid: true });
|
|
98
|
+
return { method: 'copy-store-expunge', moved: uids.length };
|
|
99
|
+
}
|
|
100
|
+
async copy(uids, sourceMailbox, targetMailbox) {
|
|
101
|
+
await this.openMailbox(sourceMailbox);
|
|
102
|
+
await this.client.messageCopy(uids, targetMailbox, { uid: true });
|
|
103
|
+
return { copied: uids.length };
|
|
104
|
+
}
|
|
105
|
+
async updateFlags(uids, mailbox, action, flags) {
|
|
106
|
+
await this.openMailbox(mailbox);
|
|
107
|
+
if (action === 'add')
|
|
108
|
+
await this.client.messageFlagsAdd(uids, flags, { uid: true });
|
|
109
|
+
if (action === 'remove')
|
|
110
|
+
await this.client.messageFlagsRemove(uids, flags, { uid: true });
|
|
111
|
+
if (action === 'replace')
|
|
112
|
+
await this.client.messageFlagsSet(uids, flags, { uid: true });
|
|
113
|
+
return { updated: uids.length, action, flags };
|
|
114
|
+
}
|
|
115
|
+
async expunge(mailbox) {
|
|
116
|
+
await this.openMailbox(mailbox);
|
|
117
|
+
await this.client.messageDelete('1:*');
|
|
118
|
+
return { expunged: true };
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
exports.ImapAdvancedClient = ImapAdvancedClient;
|
|
122
|
+
function normalizeAddresses(input) {
|
|
123
|
+
if (!Array.isArray(input))
|
|
124
|
+
return [];
|
|
125
|
+
const out = [];
|
|
126
|
+
for (const group of input) {
|
|
127
|
+
const list = Array.isArray(group === null || group === void 0 ? void 0 : group.addresses) ? group.addresses : [];
|
|
128
|
+
for (const a of list) {
|
|
129
|
+
out.push({ name: a.name || '', address: a.address || '' });
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return out;
|
|
133
|
+
}
|
|
134
|
+
function parseReferences(headers) {
|
|
135
|
+
const candidates = [headers.references, headers['in-reply-to'], headers['message-id']]
|
|
136
|
+
.filter(Boolean)
|
|
137
|
+
.map((v) => String(v));
|
|
138
|
+
const set = new Set();
|
|
139
|
+
for (const line of candidates) {
|
|
140
|
+
const matches = line.match(/<[^>]+>/g) || [];
|
|
141
|
+
for (const m of matches)
|
|
142
|
+
set.add(m.trim());
|
|
143
|
+
}
|
|
144
|
+
return Array.from(set);
|
|
145
|
+
}
|
|
146
|
+
function parseUidList(input) {
|
|
147
|
+
return input
|
|
148
|
+
.split(',')
|
|
149
|
+
.map((s) => Number(s.trim()))
|
|
150
|
+
.filter((n) => Number.isFinite(n) && n > 0);
|
|
151
|
+
}
|
|
152
|
+
async function enrichMessage(ctx, itemIndex, client, mailbox, uid, includeRaw, attachmentsMode, binaryPrefix, filters) {
|
|
153
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
|
|
154
|
+
const data = await client.fetchOneByUid(uid, mailbox, includeRaw || attachmentsMode !== 'none');
|
|
155
|
+
if (!data)
|
|
156
|
+
throw new Error(`Message not found for UID ${uid}`);
|
|
157
|
+
const headersObj = Object.fromEntries(((_b = (_a = (data.headers || new Map())).entries) === null || _b === void 0 ? void 0 : _b.call(_a)) || []);
|
|
158
|
+
const sourceBuffer = data.source ? Buffer.from(data.source) : undefined;
|
|
159
|
+
const parsed = sourceBuffer ? await (0, mailparser_1.simpleParser)(sourceBuffer) : null;
|
|
160
|
+
const json = {
|
|
161
|
+
uid: data.uid,
|
|
162
|
+
seq: data.seq,
|
|
163
|
+
messageId: (parsed === null || parsed === void 0 ? void 0 : parsed.messageId) || String(headersObj['message-id'] || ''),
|
|
164
|
+
subject: (parsed === null || parsed === void 0 ? void 0 : parsed.subject) || ((_c = data.envelope) === null || _c === void 0 ? void 0 : _c.subject) || '',
|
|
165
|
+
date: ((_e = (_d = parsed === null || parsed === void 0 ? void 0 : parsed.date) === null || _d === void 0 ? void 0 : _d.toISOString) === null || _e === void 0 ? void 0 : _e.call(_d)) || ((_g = (_f = data.internalDate) === null || _f === void 0 ? void 0 : _f.toISOString) === null || _g === void 0 ? void 0 : _g.call(_f)) || null,
|
|
166
|
+
from: normalizeAddresses(((_h = data.envelope) === null || _h === void 0 ? void 0 : _h.from) || []),
|
|
167
|
+
to: normalizeAddresses(((_j = data.envelope) === null || _j === void 0 ? void 0 : _j.to) || []),
|
|
168
|
+
cc: normalizeAddresses(((_k = data.envelope) === null || _k === void 0 ? void 0 : _k.cc) || []),
|
|
169
|
+
flags: Array.from(data.flags || []),
|
|
170
|
+
headers: headersObj,
|
|
171
|
+
thread: {
|
|
172
|
+
references: parseReferences(headersObj),
|
|
173
|
+
inReplyTo: String(headersObj['in-reply-to'] || ''),
|
|
174
|
+
},
|
|
175
|
+
body: {
|
|
176
|
+
text: (parsed === null || parsed === void 0 ? void 0 : parsed.text) || '',
|
|
177
|
+
html: (parsed === null || parsed === void 0 ? void 0 : parsed.html) ? String(parsed.html) : '',
|
|
178
|
+
},
|
|
179
|
+
attachments: [],
|
|
180
|
+
};
|
|
181
|
+
if (attachmentsMode === 'none')
|
|
182
|
+
return { json };
|
|
183
|
+
const binary = {};
|
|
184
|
+
const attachmentList = (parsed === null || parsed === void 0 ? void 0 : parsed.attachments) || [];
|
|
185
|
+
const maxBytes = Number(filters.maxAttachmentSizeMb || 25) * 1024 * 1024;
|
|
186
|
+
const allowedMime = String(filters.allowedMimeTypes || '')
|
|
187
|
+
.split(',')
|
|
188
|
+
.map((s) => s.trim().toLowerCase())
|
|
189
|
+
.filter(Boolean);
|
|
190
|
+
const filenameRegex = String(filters.filenameRegex || '').trim();
|
|
191
|
+
const matcher = filenameRegex ? new RegExp(filenameRegex) : null;
|
|
192
|
+
for (let i = 0; i < attachmentList.length; i++) {
|
|
193
|
+
const att = attachmentList[i];
|
|
194
|
+
const mimeType = (att.contentType || '').toLowerCase();
|
|
195
|
+
const filename = att.filename || `attachment_${i}`;
|
|
196
|
+
const size = Number(att.size || ((_l = att.content) === null || _l === void 0 ? void 0 : _l.length) || 0);
|
|
197
|
+
if (size > maxBytes)
|
|
198
|
+
continue;
|
|
199
|
+
if (allowedMime.length && !allowedMime.includes(mimeType))
|
|
200
|
+
continue;
|
|
201
|
+
if (matcher && !matcher.test(filename))
|
|
202
|
+
continue;
|
|
203
|
+
const meta = {
|
|
204
|
+
filename,
|
|
205
|
+
contentType: att.contentType || 'application/octet-stream',
|
|
206
|
+
size,
|
|
207
|
+
};
|
|
208
|
+
if (attachmentsMode === 'binary' && att.content) {
|
|
209
|
+
const key = `${binaryPrefix || 'attachment_'}${Object.keys(binary).length}`;
|
|
210
|
+
const prepared = await ctx.helpers.prepareBinaryData(Buffer.from(att.content), filename, att.contentType || 'application/octet-stream');
|
|
211
|
+
binary[key] = prepared;
|
|
212
|
+
meta.binaryProperty = key;
|
|
213
|
+
}
|
|
214
|
+
json.attachments.push(meta);
|
|
215
|
+
}
|
|
216
|
+
return Object.keys(binary).length ? { json, binary } : { json };
|
|
217
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "n8n-nodes-imap-advanced",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "n8n community nodes for advanced generic IMAP workflows",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Maxime Olivier",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"n8n-community-node-package",
|
|
9
|
+
"n8n",
|
|
10
|
+
"imap",
|
|
11
|
+
"mail",
|
|
12
|
+
"email"
|
|
13
|
+
],
|
|
14
|
+
"main": "dist/index.js",
|
|
15
|
+
"types": "dist/index.d.ts",
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsc",
|
|
21
|
+
"dev": "tsc -w",
|
|
22
|
+
"prepublishOnly": "npm run build"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"imapflow": "^1.2.10",
|
|
26
|
+
"mailparser": "^3.9.3"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/mailparser": "^3.4.6",
|
|
30
|
+
"@types/node": "^20.17.19",
|
|
31
|
+
"n8n-workflow": "^1.82.0",
|
|
32
|
+
"typescript": "^5.7.3"
|
|
33
|
+
},
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"n8n-workflow": ">=1.82.0"
|
|
36
|
+
},
|
|
37
|
+
"n8n": {
|
|
38
|
+
"n8nNodesApiVersion": 1,
|
|
39
|
+
"credentials": [],
|
|
40
|
+
"nodes": [
|
|
41
|
+
"dist/nodes/ImapAdvanced/ImapAdvanced.node.js",
|
|
42
|
+
"dist/nodes/ImapAdvancedTrigger/ImapAdvancedTrigger.node.js"
|
|
43
|
+
]
|
|
44
|
+
},
|
|
45
|
+
"homepage": "https://github.com/ClawBow/n8n-nodes-imap-advanced",
|
|
46
|
+
"repository": {
|
|
47
|
+
"type": "git",
|
|
48
|
+
"url": "git+https://github.com/ClawBow/n8n-nodes-imap-advanced.git"
|
|
49
|
+
}
|
|
50
|
+
}
|