@5minds/node-red-contrib-processcube-tools 1.2.0-develop-2eb127-mg68t7xt → 1.2.0-develop-59ef22-mg9d9ja5
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/.mocharc.json +5 -0
- package/package.json +26 -10
- package/src/custom-node-template/custom-node-template.html.template +45 -0
- package/src/custom-node-template/custom-node-template.ts.template +69 -0
- package/src/email-receiver/email-receiver.ts +439 -0
- package/src/email-sender/email-sender.ts +210 -0
- package/{processcube-html-to-text/processcube-html-to-text.html → src/html-to-text/html-to-text.html} +3 -3
- package/src/html-to-text/html-to-text.ts +53 -0
- package/src/index.ts +12 -0
- package/src/interfaces/EmailReceiverMessage.ts +22 -0
- package/src/interfaces/EmailSenderNodeProperties.ts +37 -0
- package/src/interfaces/FetchState.ts +9 -0
- package/src/interfaces/ImapConnectionConfig.ts +14 -0
- package/src/test/framework/advanced-test-patterns.ts +224 -0
- package/src/test/framework/generic-node-test-suite.ts +58 -0
- package/src/test/framework/index.ts +17 -0
- package/src/test/framework/integration-assertions.ts +67 -0
- package/src/test/framework/integration-scenario-builder.ts +77 -0
- package/src/test/framework/integration-test-runner.ts +101 -0
- package/src/test/framework/node-assertions.ts +63 -0
- package/src/test/framework/node-test-runner.ts +260 -0
- package/src/test/framework/test-scenario-builder.ts +74 -0
- package/src/test/framework/types.ts +61 -0
- package/src/test/helpers/email-receiver-test-configs.ts +67 -0
- package/src/test/helpers/email-receiver-test-flows.ts +16 -0
- package/src/test/helpers/email-sender-test-configs.ts +123 -0
- package/src/test/helpers/email-sender-test-flows.ts +16 -0
- package/src/test/integration/email-receiver.integration.test.ts +41 -0
- package/src/test/integration/email-sender.integration.test.ts +129 -0
- package/src/test/interfaces/email-data.ts +10 -0
- package/src/test/interfaces/email-receiver-config.ts +12 -0
- package/src/test/interfaces/email-sender-config.ts +26 -0
- package/src/test/interfaces/imap-config.ts +9 -0
- package/src/test/interfaces/imap-mailbox.ts +5 -0
- package/src/test/interfaces/mail-options.ts +20 -0
- package/src/test/interfaces/parsed-email.ts +11 -0
- package/src/test/interfaces/send-mail-result.ts +7 -0
- package/src/test/mocks/imap-mock.ts +147 -0
- package/src/test/mocks/mailparser-mock.ts +82 -0
- package/src/test/mocks/nodemailer-mock.ts +118 -0
- package/src/test/unit/email-receiver.unit.test.ts +471 -0
- package/src/test/unit/email-sender.unit.test.ts +550 -0
- package/tsconfig.json +23 -0
- package/email-receiver/email-receiver.js +0 -304
- package/email-sender/email-sender.js +0 -178
- package/examples/.gitkeep +0 -0
- package/processcube-html-to-text/processcube-html-to-text.js +0 -22
- package/test/helpers/email-receiver.mocks.js +0 -447
- package/test/helpers/email-sender.mocks.js +0 -368
- package/test/integration/email-receiver.integration.test.js +0 -515
- package/test/integration/email-sender.integration.test.js +0 -239
- package/test/unit/email-receiver.unit.test.js +0 -304
- package/test/unit/email-sender.unit.test.js +0 -570
- /package/{email-receiver → src/email-receiver}/email-receiver.html +0 -0
- /package/{email-sender → src/email-sender}/email-sender.html +0 -0
|
@@ -1,304 +0,0 @@
|
|
|
1
|
-
module.exports = function (RED) {
|
|
2
|
-
const Imap = require('node-imap');
|
|
3
|
-
const mailparser = require('mailparser');
|
|
4
|
-
|
|
5
|
-
function toBoolean(val, defaultValue = false) {
|
|
6
|
-
if (typeof val === "boolean") return val; // schon korrekt
|
|
7
|
-
if (typeof val === "number") return val !== 0; // 0 = false, sonst true
|
|
8
|
-
if (typeof val === "string") {
|
|
9
|
-
const v = val.trim().toLowerCase();
|
|
10
|
-
if (["true", "1", "yes", "on"].includes(v)) return true;
|
|
11
|
-
if (["false", "0", "no", "off"].includes(v)) return false;
|
|
12
|
-
}
|
|
13
|
-
return defaultValue; // fallback
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function EmailReceiverNode(config) {
|
|
17
|
-
RED.nodes.createNode(this, config);
|
|
18
|
-
const node = this;
|
|
19
|
-
|
|
20
|
-
node.on('input', function (msg) {
|
|
21
|
-
// Retrieve and validate configuration values
|
|
22
|
-
const imap_host = RED.util.evaluateNodeProperty(config.host, config.hostType, node, msg);
|
|
23
|
-
const imap_port = RED.util.evaluateNodeProperty(config.port, config.portType, node, msg);
|
|
24
|
-
const imap_tls = RED.util.evaluateNodeProperty(config.tls, config.tlsType, node, msg);
|
|
25
|
-
const imap_user = RED.util.evaluateNodeProperty(config.user, config.userType, node, msg);
|
|
26
|
-
const imap_password = RED.util.evaluateNodeProperty(config.password, config.passwordType, node, msg);
|
|
27
|
-
const sendstatus = config.sendstatus === true || config.sendstatus === 'true';
|
|
28
|
-
|
|
29
|
-
// Check if the folder is actually an array
|
|
30
|
-
const imap_folder = RED.util.evaluateNodeProperty(config.folder, config.folderType, node, msg);
|
|
31
|
-
let folders;
|
|
32
|
-
if (Array.isArray(imap_folder)) {
|
|
33
|
-
folders = imap_folder;
|
|
34
|
-
} else {
|
|
35
|
-
const errorMsg = "The 'folders' property must be an array of strings.";
|
|
36
|
-
node.status({ fill: 'red', shape: 'ring', text: errorMsg });
|
|
37
|
-
node.error(errorMsg, msg);
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
const imap_markSeen = RED.util.evaluateNodeProperty(config.markseen, config.markseenType, node, msg);
|
|
41
|
-
|
|
42
|
-
const finalConfig = {
|
|
43
|
-
host: imap_host,
|
|
44
|
-
port: typeof imap_port === 'string' ? parseInt(imap_port, 10) : imap_port,
|
|
45
|
-
tls: imap_tls,
|
|
46
|
-
user: imap_user,
|
|
47
|
-
password: imap_password,
|
|
48
|
-
folders: Array.isArray(imap_folder)
|
|
49
|
-
? imap_folder
|
|
50
|
-
: imap_folder
|
|
51
|
-
.split(',')
|
|
52
|
-
.map((f) => f.trim())
|
|
53
|
-
.filter((f) => f.length > 0),
|
|
54
|
-
markSeen: toBoolean(imap_markSeen, true),
|
|
55
|
-
connTimeout: msg.imap_connTimeout || 10000,
|
|
56
|
-
authTimeout: msg.imap_authTimeout || 5000,
|
|
57
|
-
keepalive: msg.imap_keepalive !== undefined ? msg.imap_keepalive : true,
|
|
58
|
-
autotls: msg.imap_autotls || 'never',
|
|
59
|
-
tlsOptions: msg.imap_tlsOptions || { rejectUnauthorized: false },
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
if (
|
|
63
|
-
!finalConfig.user ||
|
|
64
|
-
!finalConfig.password ||
|
|
65
|
-
!finalConfig.port ||
|
|
66
|
-
!finalConfig.host ||
|
|
67
|
-
!finalConfig.folders
|
|
68
|
-
) {
|
|
69
|
-
const missingFields = [];
|
|
70
|
-
if (!finalConfig.user) missingFields.push('user');
|
|
71
|
-
if (!finalConfig.password) missingFields.push('password');
|
|
72
|
-
if (!finalConfig.port) missingFields.push('port');
|
|
73
|
-
if (!finalConfig.host) missingFields.push('host');
|
|
74
|
-
if (!finalConfig.folders) missingFields.push('folders');
|
|
75
|
-
|
|
76
|
-
const errorMessage = `Missing required IMAP config: ${missingFields.join(', ')}. Aborting.`;
|
|
77
|
-
node.status({ fill: 'red', shape: 'ring', text: 'missing config' });
|
|
78
|
-
node.error(errorMessage);
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const fetchEmails = (
|
|
83
|
-
{
|
|
84
|
-
host,
|
|
85
|
-
port,
|
|
86
|
-
tls,
|
|
87
|
-
user,
|
|
88
|
-
password,
|
|
89
|
-
folders,
|
|
90
|
-
markSeen = true,
|
|
91
|
-
connTimeout = 10000,
|
|
92
|
-
authTimeout = 5000,
|
|
93
|
-
keepalive = true,
|
|
94
|
-
autotls = 'never',
|
|
95
|
-
tlsOptions = { rejectUnauthorized: false },
|
|
96
|
-
},
|
|
97
|
-
onMail,
|
|
98
|
-
) => {
|
|
99
|
-
const imap = new Imap({
|
|
100
|
-
user,
|
|
101
|
-
password,
|
|
102
|
-
host,
|
|
103
|
-
port,
|
|
104
|
-
tls,
|
|
105
|
-
connTimeout,
|
|
106
|
-
authTimeout,
|
|
107
|
-
keepalive,
|
|
108
|
-
autotls,
|
|
109
|
-
tlsOptions,
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
const state = {
|
|
113
|
-
totalFolders: folders.length,
|
|
114
|
-
processedFolders: 0,
|
|
115
|
-
folderCount: {},
|
|
116
|
-
successes: 0,
|
|
117
|
-
failures: 0,
|
|
118
|
-
totalMails: 0,
|
|
119
|
-
errors: [],
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
// Helper to update Node-RED status
|
|
123
|
-
const updateStatus = (color, text) => {
|
|
124
|
-
node.status({ fill: color, shape: 'dot', text });
|
|
125
|
-
};
|
|
126
|
-
|
|
127
|
-
// Helper to finalize status and clean up
|
|
128
|
-
const finalizeSession = (error = null) => {
|
|
129
|
-
if (error) {
|
|
130
|
-
node.error('IMAP session terminated: ' + error.message);
|
|
131
|
-
node.status({ fill: 'red', shape: 'ring', text: 'connection error' });
|
|
132
|
-
if (sendstatus) {
|
|
133
|
-
node.send([null, {
|
|
134
|
-
payload: {
|
|
135
|
-
status: 'error',
|
|
136
|
-
message: error.message,
|
|
137
|
-
errors: state.errors,
|
|
138
|
-
}
|
|
139
|
-
}]);
|
|
140
|
-
}
|
|
141
|
-
} else if (state.failures > 0) {
|
|
142
|
-
node.status({
|
|
143
|
-
fill: 'red',
|
|
144
|
-
shape: 'dot',
|
|
145
|
-
text: `Done, ${state.totalMails} mails from ${state.successes}/${state.totalFolders} folders. ${state.failures} failed.`,
|
|
146
|
-
});
|
|
147
|
-
if (sendstatus) {
|
|
148
|
-
node.send([null, {
|
|
149
|
-
payload: {
|
|
150
|
-
status: 'warning',
|
|
151
|
-
total: state.totalMails,
|
|
152
|
-
successes: state.successes,
|
|
153
|
-
failures: state.failures,
|
|
154
|
-
totalFolders: state.totalFolders,
|
|
155
|
-
errors: state.errors,
|
|
156
|
-
}
|
|
157
|
-
}]);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
} else {
|
|
161
|
-
node.status({
|
|
162
|
-
fill: 'green',
|
|
163
|
-
shape: 'dot',
|
|
164
|
-
text: `Done, fetched ${state.totalMails} mails from ${folders.join(', ')}.`,
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
if (sendstatus) {
|
|
168
|
-
node.send([null, {
|
|
169
|
-
payload: {
|
|
170
|
-
status: 'success',
|
|
171
|
-
total: state.totalMails,
|
|
172
|
-
folderCount: state.folderCount,
|
|
173
|
-
folders: folders.join(', '),
|
|
174
|
-
}
|
|
175
|
-
}]);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
}
|
|
179
|
-
if (imap && imap.state !== 'disconnected') {
|
|
180
|
-
imap.end();
|
|
181
|
-
}
|
|
182
|
-
};
|
|
183
|
-
|
|
184
|
-
const fetchFromFolder = (folder) => {
|
|
185
|
-
updateStatus('yellow', `Fetching from "${folder}"...`);
|
|
186
|
-
|
|
187
|
-
imap.openBox(folder, false, (err, box) => {
|
|
188
|
-
if (err) {
|
|
189
|
-
node.error(`Could not open folder "${folder}": ${err.message}`);
|
|
190
|
-
state.failures++;
|
|
191
|
-
state.processedFolders++;
|
|
192
|
-
return startNextFolder();
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
state.folderCount[folder] = 0;
|
|
196
|
-
|
|
197
|
-
imap.search(['UNSEEN'], (err, results) => {
|
|
198
|
-
if (err) {
|
|
199
|
-
node.error(`Search failed in folder "${folder}": ${err.message}`);
|
|
200
|
-
state.failures++;
|
|
201
|
-
state.processedFolders++;
|
|
202
|
-
return startNextFolder();
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
if (!results || !results.length) {
|
|
206
|
-
state.successes++;
|
|
207
|
-
state.processedFolders++;
|
|
208
|
-
return startNextFolder();
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
state.totalMails += results.length;
|
|
212
|
-
|
|
213
|
-
const fetch = imap.fetch(results, { bodies: '', markSeen: markSeen });
|
|
214
|
-
|
|
215
|
-
fetch.on('message', (msg) => {
|
|
216
|
-
msg.on('body', (stream) => {
|
|
217
|
-
mailparser.simpleParser(stream, (err, parsed) => {
|
|
218
|
-
if (err) {
|
|
219
|
-
node.error(`Parse error for email from folder "${folder}": ${err.message}`);
|
|
220
|
-
return;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
const outMsg = {
|
|
224
|
-
topic: parsed.subject,
|
|
225
|
-
payload: parsed.text,
|
|
226
|
-
html: parsed.html,
|
|
227
|
-
from: parsed.replyTo?.text || parsed.from?.text,
|
|
228
|
-
date: parsed.date,
|
|
229
|
-
folder,
|
|
230
|
-
header: parsed.headers,
|
|
231
|
-
attachments: parsed.attachments.map((att) => ({
|
|
232
|
-
contentType: att.contentType,
|
|
233
|
-
fileName: att.filename,
|
|
234
|
-
transferEncoding: att.transferEncoding,
|
|
235
|
-
contentDisposition: att.contentDisposition,
|
|
236
|
-
generatedFileName: att.cid || att.checksum,
|
|
237
|
-
contentId: att.cid,
|
|
238
|
-
checksum: att.checksum,
|
|
239
|
-
length: att.size,
|
|
240
|
-
content: att.content,
|
|
241
|
-
})),
|
|
242
|
-
};
|
|
243
|
-
state.folderCount[folder] = (state.folderCount[folder] || 0) + 1;
|
|
244
|
-
onMail(outMsg);
|
|
245
|
-
});
|
|
246
|
-
});
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
fetch.once('error', (err) => {
|
|
250
|
-
node.error(`Fetch error in folder "${folder}": ${err.message}`);
|
|
251
|
-
});
|
|
252
|
-
|
|
253
|
-
fetch.once('end', () => {
|
|
254
|
-
state.successes++;
|
|
255
|
-
state.processedFolders++;
|
|
256
|
-
updateStatus('green', `Fetched ${results.length} from "${folder}".`);
|
|
257
|
-
startNextFolder();
|
|
258
|
-
});
|
|
259
|
-
});
|
|
260
|
-
});
|
|
261
|
-
};
|
|
262
|
-
|
|
263
|
-
const startNextFolder = () => {
|
|
264
|
-
if (state.processedFolders >= state.totalFolders) {
|
|
265
|
-
finalizeSession();
|
|
266
|
-
} else {
|
|
267
|
-
fetchFromFolder(folders[state.processedFolders]);
|
|
268
|
-
}
|
|
269
|
-
};
|
|
270
|
-
|
|
271
|
-
// Centralized event listeners for the IMAP connection
|
|
272
|
-
imap.once('ready', () => {
|
|
273
|
-
node.status({ fill: 'green', shape: 'dot', text: 'connected' });
|
|
274
|
-
startNextFolder();
|
|
275
|
-
});
|
|
276
|
-
|
|
277
|
-
imap.once('error', (err) => {
|
|
278
|
-
finalizeSession(err);
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
imap.once('end', () => {
|
|
282
|
-
node.log('IMAP connection ended.');
|
|
283
|
-
});
|
|
284
|
-
|
|
285
|
-
try {
|
|
286
|
-
updateStatus('yellow', 'Connecting to IMAP...');
|
|
287
|
-
imap.connect();
|
|
288
|
-
} catch (err) {
|
|
289
|
-
updateStatus('red', 'Connection error: ' + err.message);
|
|
290
|
-
}
|
|
291
|
-
};
|
|
292
|
-
|
|
293
|
-
fetchEmails(finalConfig, (mail) => {
|
|
294
|
-
node.send(mail);
|
|
295
|
-
});
|
|
296
|
-
});
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
RED.nodes.registerType('email-receiver', EmailReceiverNode, {
|
|
300
|
-
credentials: {
|
|
301
|
-
password: { type: 'password' },
|
|
302
|
-
},
|
|
303
|
-
});
|
|
304
|
-
};
|
|
@@ -1,178 +0,0 @@
|
|
|
1
|
-
module.exports = function (RED) {
|
|
2
|
-
'use strict';
|
|
3
|
-
const nodemailer = require('nodemailer');
|
|
4
|
-
|
|
5
|
-
function EmailSenderNode(config) {
|
|
6
|
-
RED.nodes.createNode(this, config);
|
|
7
|
-
var node = this;
|
|
8
|
-
|
|
9
|
-
node.on('input', function (msg, send, done) {
|
|
10
|
-
send =
|
|
11
|
-
send ||
|
|
12
|
-
function () {
|
|
13
|
-
node.send.apply(node, arguments);
|
|
14
|
-
};
|
|
15
|
-
done =
|
|
16
|
-
done ||
|
|
17
|
-
function (err) {
|
|
18
|
-
if (err) node.error(err, msg);
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
// Retrieve and evaluate mail configuration values
|
|
22
|
-
const sender = RED.util.evaluateNodeProperty(config.sender, config.senderType, node, msg);
|
|
23
|
-
const address = RED.util.evaluateNodeProperty(config.address, config.addressType, node, msg);
|
|
24
|
-
const to = RED.util.evaluateNodeProperty(config.to, config.toType, node, msg);
|
|
25
|
-
const cc = RED.util.evaluateNodeProperty(config.cc, config.ccType, node, msg) || '';
|
|
26
|
-
const bcc = RED.util.evaluateNodeProperty(config.bcc, config.bccType, node, msg) || '';
|
|
27
|
-
const replyTo = RED.util.evaluateNodeProperty(config.replyTo, config.replyToType, node, msg) || '';
|
|
28
|
-
const subject =
|
|
29
|
-
RED.util.evaluateNodeProperty(config.subject, config.subjectType, node, msg) ||
|
|
30
|
-
msg.topic ||
|
|
31
|
-
'Message from Node-RED';
|
|
32
|
-
const htmlContent = RED.util.evaluateNodeProperty(config.htmlContent, config.htmlContentType, node, msg);
|
|
33
|
-
const attachments = safeEvaluatePropertyAttachment(config, node, msg);
|
|
34
|
-
|
|
35
|
-
// Retrieve and evaluate SMTP configuration values
|
|
36
|
-
const host = RED.util.evaluateNodeProperty(config.host, config.hostType, node, msg);
|
|
37
|
-
const port = RED.util.evaluateNodeProperty(config.port, config.portType, node, msg);
|
|
38
|
-
const user = RED.util.evaluateNodeProperty(config.user, config.userType, node, msg);
|
|
39
|
-
const password = RED.util.evaluateNodeProperty(config.password, config.passwordType, node, msg);
|
|
40
|
-
const secure = RED.util.evaluateNodeProperty(config.secure, config.secureType, node, msg);
|
|
41
|
-
const rejectUnauthorized = RED.util.evaluateNodeProperty(
|
|
42
|
-
config.rejectUnauthorized,
|
|
43
|
-
config.rejectUnauthorizedType,
|
|
44
|
-
node,
|
|
45
|
-
msg,
|
|
46
|
-
);
|
|
47
|
-
|
|
48
|
-
// Handle attachments and format them for Nodemailer
|
|
49
|
-
let processedAttachments = [];
|
|
50
|
-
|
|
51
|
-
let parsedAttachments = attachments;
|
|
52
|
-
|
|
53
|
-
if (config.attachmentsType === 'json' && typeof parsedAttachments === 'string') {
|
|
54
|
-
try {
|
|
55
|
-
parsedAttachments = JSON.parse(parsedAttachments);
|
|
56
|
-
} catch (e) {
|
|
57
|
-
node.error('Failed to parse attachments JSON: ' + e.message);
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
if (parsedAttachments) {
|
|
63
|
-
// Check if it's a single attachment or an array
|
|
64
|
-
const attachmentArray = Array.isArray(parsedAttachments) ? parsedAttachments : [parsedAttachments];
|
|
65
|
-
|
|
66
|
-
for (const attachment of attachmentArray) {
|
|
67
|
-
try {
|
|
68
|
-
// Assuming the attachment object has a 'filename' and 'content' property
|
|
69
|
-
if (attachment.filename && attachment.content) {
|
|
70
|
-
processedAttachments.push({
|
|
71
|
-
filename: attachment.filename,
|
|
72
|
-
content: attachment.content,
|
|
73
|
-
});
|
|
74
|
-
} else {
|
|
75
|
-
node.status({ fill: 'red', shape: 'dot', text: 'attachment error' });
|
|
76
|
-
node.error("Attachment object is missing 'filename' or 'content' property.");
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
} catch (e) {
|
|
80
|
-
node.error('Failed to process attachment: ' + e.message);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// Create SMTP transporter
|
|
86
|
-
const transporter = nodemailer.createTransport({
|
|
87
|
-
host: host,
|
|
88
|
-
port: port,
|
|
89
|
-
secure: secure,
|
|
90
|
-
auth: {
|
|
91
|
-
user: user,
|
|
92
|
-
pass: password,
|
|
93
|
-
},
|
|
94
|
-
tls: {
|
|
95
|
-
rejectUnauthorized: rejectUnauthorized,
|
|
96
|
-
},
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
// Create email object
|
|
100
|
-
const mailOptions = {
|
|
101
|
-
from: {
|
|
102
|
-
name: sender,
|
|
103
|
-
address: address,
|
|
104
|
-
},
|
|
105
|
-
to: to,
|
|
106
|
-
cc: cc,
|
|
107
|
-
bcc: bcc,
|
|
108
|
-
replyTo: replyTo,
|
|
109
|
-
subject: subject,
|
|
110
|
-
html: Buffer.from(htmlContent, 'utf-8'),
|
|
111
|
-
attachments: processedAttachments,
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
// Send email
|
|
115
|
-
transporter.sendMail(mailOptions, (error, info) => {
|
|
116
|
-
if (error) {
|
|
117
|
-
node.status({ fill: 'red', shape: 'dot', text: 'error sending' });
|
|
118
|
-
if (
|
|
119
|
-
error.message &&
|
|
120
|
-
error.message.includes('SSL routines') &&
|
|
121
|
-
error.message.includes('wrong version number')
|
|
122
|
-
) {
|
|
123
|
-
// Improved error message for SSL/TLS issues
|
|
124
|
-
done(
|
|
125
|
-
new Error(
|
|
126
|
-
'SSL/TLS connection failed: Wrong version number. ' +
|
|
127
|
-
'This usually means the wrong port or security settings are used. ' +
|
|
128
|
-
'For SMTP: use port 587 with secure=false (STARTTLS) or port 465 with secure=true (SSL/TLS).',
|
|
129
|
-
),
|
|
130
|
-
);
|
|
131
|
-
} else {
|
|
132
|
-
done(error);
|
|
133
|
-
}
|
|
134
|
-
} else {
|
|
135
|
-
node.log('Email sent: ' + info.response);
|
|
136
|
-
msg.payload = info;
|
|
137
|
-
|
|
138
|
-
if (msg.payload.accepted && msg.payload.accepted.length > 0) {
|
|
139
|
-
msg.payload = msg.input;
|
|
140
|
-
node.status({ fill: 'green', shape: 'dot', text: 'sent' });
|
|
141
|
-
send(msg);
|
|
142
|
-
done();
|
|
143
|
-
} else if (msg.payload.rejected && msg.payload.rejected.length > 0) {
|
|
144
|
-
msg.error = { result: msg.payload.rejected };
|
|
145
|
-
node.status({ fill: 'red', shape: 'dot', text: 'rejected' });
|
|
146
|
-
done(new Error('Email rejected: ' + msg.payload.rejected.join(', ')));
|
|
147
|
-
} else if (msg.payload.pending && msg.payload.pending.length > 0) {
|
|
148
|
-
msg.error = { result: msg.payload.pending };
|
|
149
|
-
node.status({ fill: 'yellow', shape: 'dot', text: 'pending' });
|
|
150
|
-
done(new Error('Email pending: ' + msg.payload.pending.join(', ')));
|
|
151
|
-
} else {
|
|
152
|
-
node.status({ fill: 'red', shape: 'dot', text: 'unknown error' });
|
|
153
|
-
done(new Error('Unknown error while sending email.'));
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
});
|
|
157
|
-
});
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
function safeEvaluatePropertyAttachment(config, node, msg) {
|
|
161
|
-
if (config.attachments && config.attachments.trim() !== '') {
|
|
162
|
-
try {
|
|
163
|
-
return RED.util.evaluateNodeProperty(config.attachments, config.attachmentsType, node, msg);
|
|
164
|
-
} catch (e) {
|
|
165
|
-
node.error('Failed to evaluate attachments property: ' + e.message, msg);
|
|
166
|
-
return null;
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
return null;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
RED.nodes.registerType('email-sender', EmailSenderNode, {
|
|
174
|
-
credentials: {
|
|
175
|
-
password: { type: 'password' },
|
|
176
|
-
},
|
|
177
|
-
});
|
|
178
|
-
};
|
package/examples/.gitkeep
DELETED
|
File without changes
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
module.exports = function (RED) {
|
|
2
|
-
const { compile } = require('html-to-text');
|
|
3
|
-
|
|
4
|
-
function ProcesscubeHtmlToText(config) {
|
|
5
|
-
RED.nodes.createNode(this, config);
|
|
6
|
-
const node = this;
|
|
7
|
-
|
|
8
|
-
const options = {
|
|
9
|
-
wordwrap: 130,
|
|
10
|
-
// ...
|
|
11
|
-
};
|
|
12
|
-
const compiledConvert = compile(options); // options passed here
|
|
13
|
-
|
|
14
|
-
node.on('input', async function (msg) {
|
|
15
|
-
msg.payload = compiledConvert(msg.payload);
|
|
16
|
-
|
|
17
|
-
node.send(msg);
|
|
18
|
-
});
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
RED.nodes.registerType('processcube-html-to-text', ProcesscubeHtmlToText);
|
|
22
|
-
};
|