@5minds/node-red-contrib-processcube-tools 1.0.1-feature-f506be-mfe3agh6 → 1.0.1-feature-7fb5cf-mff3dkae
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/email-receiver/email-receiver.html +187 -0
- package/email-receiver/email-receiver.js +236 -0
- package/package.json +26 -5
- package/test/helpers/email-receiver.mocks.js +432 -0
- package/test/integration/email-receiver.integration.test.js +479 -0
- package/test/unit/email-receiver.unit.test.js +337 -0
- package/email-sender/email-sender.html +0 -257
- package/email-sender/email-sender.js +0 -91
- /package/{processcube-html-to-text/processcube-html-to-text.html → processcube-html-to-text.html} +0 -0
- /package/{processcube-html-to-text/processcube-html-to-text.js → processcube-html-to-text.js} +0 -0
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
const should = require('should');
|
|
2
|
+
const {
|
|
3
|
+
createMockImap,
|
|
4
|
+
createMockMailparser,
|
|
5
|
+
createMockNodeRED,
|
|
6
|
+
setupModuleMocks,
|
|
7
|
+
testConfigs,
|
|
8
|
+
testUtils
|
|
9
|
+
} = require('../helpers/email-receiver.mocks.js');
|
|
10
|
+
|
|
11
|
+
describe('Email Receiver Node - Unit Tests with Helpers', function() {
|
|
12
|
+
this.timeout(10000);
|
|
13
|
+
|
|
14
|
+
let emailReceiverNode;
|
|
15
|
+
let cleanupMocks;
|
|
16
|
+
|
|
17
|
+
before(function() {
|
|
18
|
+
// Set up module mocks using helper
|
|
19
|
+
cleanupMocks = setupModuleMocks();
|
|
20
|
+
|
|
21
|
+
// Load the node with mocked dependencies
|
|
22
|
+
emailReceiverNode = require('../../email-receiver/email-receiver.js');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
after(function() {
|
|
26
|
+
// Clean up mocks
|
|
27
|
+
if (cleanupMocks) {
|
|
28
|
+
cleanupMocks();
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe('Module Export', function() {
|
|
33
|
+
it('should export a function', function() {
|
|
34
|
+
emailReceiverNode.should.be.type('function');
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe('Node Registration', function() {
|
|
39
|
+
it('should register node type without errors', function() {
|
|
40
|
+
// ARRANGE: Create mock Node-RED with tracking
|
|
41
|
+
const mockRED = createMockNodeRED();
|
|
42
|
+
|
|
43
|
+
// ACT: Register the node
|
|
44
|
+
emailReceiverNode(mockRED);
|
|
45
|
+
|
|
46
|
+
// ASSERT: Verify registration
|
|
47
|
+
mockRED.nodes.lastRegisteredType.should.equal('email-receiver');
|
|
48
|
+
mockRED.nodes.lastRegisteredConstructor.should.be.type('function');
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe('Node Instantiation', function() {
|
|
53
|
+
it('should handle node instantiation with valid config', function() {
|
|
54
|
+
// ARRANGE: Track node creation
|
|
55
|
+
let createdNode = null;
|
|
56
|
+
const mockRED = createMockNodeRED({
|
|
57
|
+
onHandler: function(event, callback) {
|
|
58
|
+
createdNode = this;
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// ACT: Register and create node instance
|
|
63
|
+
emailReceiverNode(mockRED);
|
|
64
|
+
new mockRED.nodes.lastRegisteredConstructor(testConfigs.valid);
|
|
65
|
+
|
|
66
|
+
// ASSERT: Verify node was created with correct properties
|
|
67
|
+
should.exist(createdNode);
|
|
68
|
+
createdNode.should.have.property('name', testConfigs.valid.name);
|
|
69
|
+
createdNode.should.have.property('id', testConfigs.valid.id);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('should handle minimal config', function() {
|
|
73
|
+
// ARRANGE: Use minimal test config
|
|
74
|
+
let createdNode = null;
|
|
75
|
+
const mockRED = createMockNodeRED({
|
|
76
|
+
onHandler: function(event, callback) {
|
|
77
|
+
createdNode = this;
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// ACT: Register and create node with minimal config
|
|
82
|
+
emailReceiverNode(mockRED);
|
|
83
|
+
new mockRED.nodes.lastRegisteredConstructor(testConfigs.minimal);
|
|
84
|
+
|
|
85
|
+
// ASSERT: Verify node creation
|
|
86
|
+
should.exist(createdNode);
|
|
87
|
+
createdNode.should.have.property('id', testConfigs.minimal.id);
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe('Folder Configuration', function() {
|
|
92
|
+
it('should handle array of folders', async function() {
|
|
93
|
+
// ARRANGE: Set up message tracking
|
|
94
|
+
let sentMessage = null;
|
|
95
|
+
const mockRED = createMockNodeRED({
|
|
96
|
+
sendHandler: function(msg) {
|
|
97
|
+
sentMessage = msg;
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// ACT: Register node and create instance with array folders
|
|
102
|
+
emailReceiverNode(mockRED);
|
|
103
|
+
const nodeConstructor = mockRED.nodes.lastRegisteredConstructor;
|
|
104
|
+
const nodeInstance = new nodeConstructor(testConfigs.arrayFolders);
|
|
105
|
+
|
|
106
|
+
// Wait for processing
|
|
107
|
+
await testUtils.wait(50);
|
|
108
|
+
|
|
109
|
+
// ASSERT: Should handle array folders without error
|
|
110
|
+
should.exist(nodeInstance);
|
|
111
|
+
nodeInstance.should.have.property('name', testConfigs.arrayFolders.name);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
describe('Error Handling', function() {
|
|
116
|
+
it('should call node.error for invalid folder type', function(done) {
|
|
117
|
+
// ARRANGE: Set up error tracking
|
|
118
|
+
const mockRED = createMockNodeRED({
|
|
119
|
+
onHandler: function(event, callback) {
|
|
120
|
+
if (event === 'input') {
|
|
121
|
+
this.inputCallback = callback;
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
errorHandler: function(err) {
|
|
125
|
+
// ASSERT: Should receive appropriate error message
|
|
126
|
+
err.should.containEql("The 'folders' property must be an array of strings.");
|
|
127
|
+
done();
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// ACT: Register node and create instance with invalid config
|
|
132
|
+
emailReceiverNode(mockRED);
|
|
133
|
+
const nodeConstructor = mockRED.nodes.lastRegisteredConstructor;
|
|
134
|
+
const nodeInstance = new nodeConstructor(testConfigs.invalidFolderType);
|
|
135
|
+
|
|
136
|
+
// Trigger the error by sending an input message
|
|
137
|
+
// Use a small delay to ensure the constructor has completed
|
|
138
|
+
setTimeout(() => {
|
|
139
|
+
if (nodeInstance.inputCallback) {
|
|
140
|
+
nodeInstance.inputCallback({ payload: "test" });
|
|
141
|
+
} else {
|
|
142
|
+
done(new Error('inputCallback was not set on the node instance'));
|
|
143
|
+
}
|
|
144
|
+
}, 10);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('should call node.error for missing config', function(done) {
|
|
148
|
+
// ARRANGE: Set up error and status tracking
|
|
149
|
+
let statusCalled = false;
|
|
150
|
+
const mockRED = createMockNodeRED({
|
|
151
|
+
onHandler: function(event, callback) {
|
|
152
|
+
if (event === 'input') {
|
|
153
|
+
this.inputCallback = callback;
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
statusHandler: function(status) {
|
|
157
|
+
statusCalled = true;
|
|
158
|
+
if (status.fill) {
|
|
159
|
+
status.fill.should.equal('red');
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
errorHandler: function(err) {
|
|
163
|
+
// ASSERT: Should receive config error
|
|
164
|
+
err.should.containEql('Missing required IMAP config');
|
|
165
|
+
statusCalled.should.be.true();
|
|
166
|
+
done();
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// ACT: Register node and create instance with invalid config
|
|
171
|
+
emailReceiverNode(mockRED);
|
|
172
|
+
const nodeConstructor = mockRED.nodes.lastRegisteredConstructor;
|
|
173
|
+
const nodeInstance = new nodeConstructor(testConfigs.invalidConfig);
|
|
174
|
+
|
|
175
|
+
// Trigger the error by sending an input message
|
|
176
|
+
// Use a small delay to ensure the constructor has completed
|
|
177
|
+
setTimeout(() => {
|
|
178
|
+
if (nodeInstance.inputCallback) {
|
|
179
|
+
nodeInstance.inputCallback({ payload: "test" });
|
|
180
|
+
} else {
|
|
181
|
+
done(new Error('inputCallback was not set on the node instance'));
|
|
182
|
+
}
|
|
183
|
+
}, 10);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('should handle connection errors gracefully', function(done) {
|
|
187
|
+
// ARRANGE: Set up connection error scenario
|
|
188
|
+
const mockRED = createMockNodeRED({
|
|
189
|
+
onHandler: function(event, callback) {
|
|
190
|
+
if (event === 'input') {
|
|
191
|
+
this.inputCallback = callback;
|
|
192
|
+
}
|
|
193
|
+
},
|
|
194
|
+
statusHandler: function(status) {
|
|
195
|
+
if (status.fill === 'red' && status.text && status.text.includes('error')) {
|
|
196
|
+
done(); // Success - error status was set
|
|
197
|
+
}
|
|
198
|
+
},
|
|
199
|
+
errorHandler: function(err) {
|
|
200
|
+
// Also accept errors as valid completion
|
|
201
|
+
should.exist(err);
|
|
202
|
+
done();
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
// ACT: Create node and trigger connection attempt
|
|
207
|
+
emailReceiverNode(mockRED);
|
|
208
|
+
const nodeConstructor = mockRED.nodes.lastRegisteredConstructor;
|
|
209
|
+
|
|
210
|
+
// Use a config that should cause connection issues
|
|
211
|
+
const badConfig = {
|
|
212
|
+
...testConfigs.valid,
|
|
213
|
+
host: 'nonexistent.invalid.host.com',
|
|
214
|
+
port: 12345 // Invalid port
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
const nodeInstance = new nodeConstructor(badConfig);
|
|
218
|
+
|
|
219
|
+
// Trigger the error by sending an input message
|
|
220
|
+
// Use a small delay to ensure the constructor has completed
|
|
221
|
+
setTimeout(() => {
|
|
222
|
+
if (nodeInstance.inputCallback) {
|
|
223
|
+
nodeInstance.inputCallback({ payload: "test" });
|
|
224
|
+
} else {
|
|
225
|
+
done(new Error('inputCallback was not set on the node instance'));
|
|
226
|
+
}
|
|
227
|
+
}, 10);
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
describe('IMAP Connection', function() {
|
|
232
|
+
it('should handle connection success', function(done) {
|
|
233
|
+
// ARRANGE: Set up connection tracking
|
|
234
|
+
const mockRED = createMockNodeRED({
|
|
235
|
+
onHandler: function(event, callback) {
|
|
236
|
+
if (event === 'input') {
|
|
237
|
+
// Store the callback on the node instance
|
|
238
|
+
this.inputCallback = callback;
|
|
239
|
+
}
|
|
240
|
+
},
|
|
241
|
+
statusHandler: function(status) {
|
|
242
|
+
if (status.fill === 'green') {
|
|
243
|
+
// ASSERT: Should show connected status
|
|
244
|
+
status.text.should.containEql('connected');
|
|
245
|
+
done();
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
// ACT: Create node with config that should fail
|
|
251
|
+
emailReceiverNode(mockRED);
|
|
252
|
+
const nodeConstructor = mockRED.nodes.lastRegisteredConstructor;
|
|
253
|
+
const nodeInstance = new nodeConstructor(testConfigs.valid);
|
|
254
|
+
|
|
255
|
+
// Trigger the connection attempt by sending an input message
|
|
256
|
+
setTimeout(() => {
|
|
257
|
+
if (nodeInstance.inputCallback) {
|
|
258
|
+
nodeInstance.inputCallback({ payload: "test" });
|
|
259
|
+
} else {
|
|
260
|
+
done(new Error('inputCallback was not set on the node instance'));
|
|
261
|
+
}
|
|
262
|
+
}, 10);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it('should handle connection errors', function(done) {
|
|
266
|
+
// ARRANGE: Set up error tracking
|
|
267
|
+
const mockRED = createMockNodeRED({
|
|
268
|
+
onHandler: function(event, callback) {
|
|
269
|
+
if (event === 'input') {
|
|
270
|
+
// Store the callback on the node instance
|
|
271
|
+
this.inputCallback = callback;
|
|
272
|
+
}
|
|
273
|
+
},
|
|
274
|
+
errorHandler: function(err) {
|
|
275
|
+
// ASSERT: Should handle connection errors gracefully
|
|
276
|
+
should.exist(err);
|
|
277
|
+
done();
|
|
278
|
+
},
|
|
279
|
+
statusHandler: function(status) {
|
|
280
|
+
if (status.fill === 'red') {
|
|
281
|
+
// Connection failed status
|
|
282
|
+
status.text.should.containEql('error');
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
// ACT: Create node with config that should fail
|
|
288
|
+
emailReceiverNode(mockRED);
|
|
289
|
+
const nodeConstructor = mockRED.nodes.lastRegisteredConstructor;
|
|
290
|
+
|
|
291
|
+
// Use invalid config to trigger connection error
|
|
292
|
+
const invalidConfig = { ...testConfigs.valid, host: 'invalid.host.com' };
|
|
293
|
+
const nodeInstance = new nodeConstructor(invalidConfig);
|
|
294
|
+
|
|
295
|
+
// Trigger the connection attempt by sending an input message
|
|
296
|
+
setTimeout(() => {
|
|
297
|
+
if (nodeInstance.inputCallback) {
|
|
298
|
+
nodeInstance.inputCallback({ payload: "test" });
|
|
299
|
+
} else {
|
|
300
|
+
done(new Error('inputCallback was not set on the node instance'));
|
|
301
|
+
}
|
|
302
|
+
}, 10);
|
|
303
|
+
});
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
describe('Message Verification Utilities', function() {
|
|
307
|
+
it('should verify message properties using testUtils', function() {
|
|
308
|
+
// ARRANGE: Create a test message
|
|
309
|
+
const testMessage = {
|
|
310
|
+
payload: 'test content',
|
|
311
|
+
topic: 'email/received',
|
|
312
|
+
from: 'test@example.com'
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
// ACT & ASSERT: Use helper to verify message properties
|
|
316
|
+
testUtils.verifyMessage(testMessage, {
|
|
317
|
+
payload: 'test content',
|
|
318
|
+
topic: 'email/received'
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
// Should not throw any errors if verification passes
|
|
322
|
+
testMessage.should.have.property('from', 'test@example.com');
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
it('should use wait utility for async operations', async function() {
|
|
326
|
+
// ARRANGE: Record start time
|
|
327
|
+
const startTime = Date.now();
|
|
328
|
+
|
|
329
|
+
// ACT: Use the wait utility
|
|
330
|
+
await testUtils.wait(100);
|
|
331
|
+
|
|
332
|
+
// ASSERT: Should have waited approximately the right amount of time
|
|
333
|
+
const elapsed = Date.now() - startTime;
|
|
334
|
+
elapsed.should.be.approximately(100, 50); // Allow 50ms tolerance
|
|
335
|
+
});
|
|
336
|
+
});
|
|
337
|
+
});
|
|
@@ -1,257 +0,0 @@
|
|
|
1
|
-
<script type="text/javascript">
|
|
2
|
-
RED.nodes.registerType('email-sender', {
|
|
3
|
-
category: 'ProcessCube Tools',
|
|
4
|
-
color: '#02AFD6',
|
|
5
|
-
defaults: {
|
|
6
|
-
name: { value: "" },
|
|
7
|
-
// Mail fields
|
|
8
|
-
sender: { value: "Stoelting Ticket-System", required: true, validate: RED.validators.typedInput("senderType") },
|
|
9
|
-
senderType: { value: "str" },
|
|
10
|
-
address: { value: "", required: true, validate: RED.validators.typedInput("addressType") },
|
|
11
|
-
addressType: { value: "str" },
|
|
12
|
-
to: { value: "", required: true, validate: RED.validators.typedInput("toType") },
|
|
13
|
-
toType: { value: "str" },
|
|
14
|
-
cc: { value: "", validate: RED.validators.typedInput("ccType") },
|
|
15
|
-
ccType: { value: "str" },
|
|
16
|
-
bcc: { value: "", validate: RED.validators.typedInput("bccType") },
|
|
17
|
-
bccType: { value: "str" },
|
|
18
|
-
subject: { value: "", required: true, validate: RED.validators.typedInput("subjectType") },
|
|
19
|
-
subjectType: { value: "str" },
|
|
20
|
-
attachments: { value: "", validate: RED.validators.typedInput("attachmentsType") },
|
|
21
|
-
attachmentsType: { value: "msg" },
|
|
22
|
-
htmlContent: { value: "", required: true, validate: RED.validators.typedInput("htmlContentType") },
|
|
23
|
-
htmlContentType: { value: "str" },
|
|
24
|
-
|
|
25
|
-
// SMTP-Fields
|
|
26
|
-
host: { value: "", required: true, validate: RED.validators.typedInput("hostType") },
|
|
27
|
-
hostType: { value: "str" },
|
|
28
|
-
port: { value: "", required: true, validate: RED.validators.typedInput("portType") },
|
|
29
|
-
portType: { value: "num" },
|
|
30
|
-
user: { value: "", required: true, validate: RED.validators.typedInput("userType") },
|
|
31
|
-
userType: { value: "str" },
|
|
32
|
-
password: { value: "", required: true, type: "password" },
|
|
33
|
-
passwordType: { value: "env", required: true },
|
|
34
|
-
secure: { value: "", required: true, validate: RED.validators.typedInput("secureType") },
|
|
35
|
-
secureType: { value: "bool" },
|
|
36
|
-
rejectUnauthorized: { value: "", required: true, validate: RED.validators.typedInput("rejectUnauthorizedType") },
|
|
37
|
-
rejectUnauthorizedType: { value: "bool" }
|
|
38
|
-
},
|
|
39
|
-
inputs: 1,
|
|
40
|
-
outputs: 1,
|
|
41
|
-
icon: "font-awesome/fa-paper-plane",
|
|
42
|
-
label: function() {
|
|
43
|
-
return this.name || "Email Sender";
|
|
44
|
-
},
|
|
45
|
-
oneditprepare: function() {
|
|
46
|
-
// Mail Fields
|
|
47
|
-
$('#node-input-sender').typedInput({
|
|
48
|
-
default: 'str',
|
|
49
|
-
types: ['str', 'msg', 'flow', 'global', 'env'],
|
|
50
|
-
typeField: '#node-input-senderType'
|
|
51
|
-
});
|
|
52
|
-
$('#node-input-address').typedInput({
|
|
53
|
-
default: 'str',
|
|
54
|
-
types: ['str', 'msg', 'flow', 'global', 'env'],
|
|
55
|
-
typeField: '#node-input-addressType'
|
|
56
|
-
});
|
|
57
|
-
$('#node-input-to').typedInput({
|
|
58
|
-
default: 'str',
|
|
59
|
-
types: ['str', 'msg', 'flow', 'global', 'env'],
|
|
60
|
-
typeField: '#node-input-toType'
|
|
61
|
-
});
|
|
62
|
-
$('#node-input-cc').typedInput({
|
|
63
|
-
default: 'str',
|
|
64
|
-
types: ['str', 'msg', 'flow', 'global', 'env'],
|
|
65
|
-
typeField: '#node-input-ccType'
|
|
66
|
-
});
|
|
67
|
-
$('#node-input-bcc').typedInput({
|
|
68
|
-
default: 'str',
|
|
69
|
-
types: ['str', 'msg', 'flow', 'global', 'env'],
|
|
70
|
-
typeField: '#node-input-bccType'
|
|
71
|
-
});
|
|
72
|
-
$('#node-input-subject').typedInput({
|
|
73
|
-
default: 'str',
|
|
74
|
-
types: ['str', 'msg', 'flow', 'global', 'env'],
|
|
75
|
-
typeField: '#node-input-subjectType'
|
|
76
|
-
});
|
|
77
|
-
$('#node-input-attachments').typedInput({
|
|
78
|
-
default: 'msg',
|
|
79
|
-
types: ['msg', 'flow', 'global'],
|
|
80
|
-
typeField: '#node-input-attachmentsType'
|
|
81
|
-
});
|
|
82
|
-
$('#node-input-htmlContent').typedInput({
|
|
83
|
-
default: 'str',
|
|
84
|
-
types: ['str', 'msg', 'flow', 'global', 'json'],
|
|
85
|
-
typeField: '#node-input-htmlContentType'
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
// SMTP Fields
|
|
89
|
-
$('#node-input-host').typedInput({
|
|
90
|
-
default: 'str',
|
|
91
|
-
types: ['str', 'msg', 'flow', 'global', 'env'],
|
|
92
|
-
typeField: '#node-input-hostType'
|
|
93
|
-
});
|
|
94
|
-
$('#node-input-port').typedInput({
|
|
95
|
-
default: 'num',
|
|
96
|
-
types: ['num', 'msg', 'flow', 'global', 'env'],
|
|
97
|
-
typeField: '#node-input-portType'
|
|
98
|
-
});
|
|
99
|
-
$('#node-input-user').typedInput({
|
|
100
|
-
default: 'str',
|
|
101
|
-
types: ['str', 'msg', 'flow', 'global', 'env'],
|
|
102
|
-
typeField: '#node-input-userType'
|
|
103
|
-
});
|
|
104
|
-
$('#node-input-password').typedInput({
|
|
105
|
-
default: 'env',
|
|
106
|
-
types: ['msg', 'flow', 'global', 'env'],
|
|
107
|
-
typeField: '#node-input-passwordType'
|
|
108
|
-
});
|
|
109
|
-
$('#node-input-secure').typedInput({
|
|
110
|
-
default: 'bool',
|
|
111
|
-
types: ['bool', 'msg', 'flow', 'global', 'env'],
|
|
112
|
-
typeField: '#node-input-secureType'
|
|
113
|
-
});
|
|
114
|
-
$('#node-input-rejectUnauthorized').typedInput({
|
|
115
|
-
default: 'bool',
|
|
116
|
-
types: ['bool', 'msg', 'flow', 'global', 'env'],
|
|
117
|
-
typeField: '#node-input-rejectUnauthorizedType'
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
});
|
|
121
|
-
</script>
|
|
122
|
-
|
|
123
|
-
<script type="text/html" data-template-name="email-sender">
|
|
124
|
-
<div class="form-row">
|
|
125
|
-
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
|
126
|
-
<input type="text" id="node-input-name" placeholder="Name">
|
|
127
|
-
</div>
|
|
128
|
-
|
|
129
|
-
<h3>Mail Configuration</h3>
|
|
130
|
-
<div class="form-row">
|
|
131
|
-
<label for="node-input-sender"><i class="fa fa-user"></i> Sender</label>
|
|
132
|
-
<input type="text" id="node-input-sender" placeholder="John Doe">
|
|
133
|
-
<input type="hidden" id="node-input-senderType">
|
|
134
|
-
</div>
|
|
135
|
-
<div class="form-row">
|
|
136
|
-
<label for="node-input-address"><i class="fa fa-envelope"></i> Address</label>
|
|
137
|
-
<input type="text" id="node-input-address" placeholder="john.doe@example.com">
|
|
138
|
-
<input type="hidden" id="node-input-addressType">
|
|
139
|
-
</div>
|
|
140
|
-
<div class="form-row">
|
|
141
|
-
<label for="node-input-to"><i class="fa fa-user"></i> To</label>
|
|
142
|
-
<input type="text" id="node-input-to" placeholder="my.test@example.com">
|
|
143
|
-
<input type="hidden" id="node-input-toType">
|
|
144
|
-
</div>
|
|
145
|
-
<div class="form-row">
|
|
146
|
-
<label for="node-input-cc"><i class="fa fa-user-plus"></i> CC</label>
|
|
147
|
-
<input type="text" id="node-input-cc">
|
|
148
|
-
<input type="hidden" id="node-input-ccType">
|
|
149
|
-
</div>
|
|
150
|
-
<div class="form-row">
|
|
151
|
-
<label for="node-input-bcc"><i class="fa fa-user-secret"></i> BCC</label>
|
|
152
|
-
<input type="text" id="node-input-bcc">
|
|
153
|
-
<input type="hidden" id="node-input-bccType">
|
|
154
|
-
</div>
|
|
155
|
-
<div class="form-row">
|
|
156
|
-
<label for="node-input-subject"><i class="fa fa-info-circle"></i> Subject</label>
|
|
157
|
-
<input type="text" id="node-input-subject">
|
|
158
|
-
<input type="hidden" id="node-input-subjectType">
|
|
159
|
-
</div>
|
|
160
|
-
<div class="form-row">
|
|
161
|
-
<label for="node-input-attachments"><i class="fa fa-paperclip"></i> Attachments</label>
|
|
162
|
-
<input type="text" id="node-input-attachments">
|
|
163
|
-
<input type="hidden" id="node-input-attachmentsType">
|
|
164
|
-
</div>
|
|
165
|
-
<div class="form-row">
|
|
166
|
-
<label for="node-input-htmlContent"><i class="fa fa-file-code-o"></i> HTML Content</label>
|
|
167
|
-
<input type="text" id="node-input-htmlContent">
|
|
168
|
-
<input type="hidden" id="node-input-htmlContentType">
|
|
169
|
-
</div>
|
|
170
|
-
|
|
171
|
-
<h3>SMTP Configuration</h3>
|
|
172
|
-
<div class="form-row">
|
|
173
|
-
<label for="node-input-host"><i class="fa fa-server"></i> Host</label>
|
|
174
|
-
<input type="text" id="node-input-host" placeholder="smtp.gmail.com">
|
|
175
|
-
<input type="hidden" id="node-input-hostType">
|
|
176
|
-
</div>
|
|
177
|
-
<div class="form-row">
|
|
178
|
-
<label for="node-input-port"><i class="fa fa-plug"></i> Port</label>
|
|
179
|
-
<input type="text" id="node-input-port" placeholder="587">
|
|
180
|
-
<input type="hidden" id="node-input-portType">
|
|
181
|
-
</div>
|
|
182
|
-
<div class="form-row">
|
|
183
|
-
<label for="node-input-user"><i class="fa fa-user"></i> User</label>
|
|
184
|
-
<input type="text" id="node-input-user" placeholder="test.user@config.com">
|
|
185
|
-
<input type="hidden" id="node-input-userType">
|
|
186
|
-
</div>
|
|
187
|
-
<div class="form-row">
|
|
188
|
-
<label for="node-input-password"><i class="fa fa-lock"></i> Password</label>
|
|
189
|
-
<input type="password" id="node-input-password">
|
|
190
|
-
<input type="hidden" id="node-input-passwordType">
|
|
191
|
-
</div>
|
|
192
|
-
<div class="form-row">
|
|
193
|
-
<label for="node-input-secure"><i class="fa fa-shield"></i> SSL/TLS (Secure)</label>
|
|
194
|
-
<input type="text" id="node-input-secure">
|
|
195
|
-
<input type="hidden" id="node-input-secureType">
|
|
196
|
-
</div>
|
|
197
|
-
<div class="form-row">
|
|
198
|
-
<label for="node-input-rejectUnauthorized"><i class="fa fa-ban"></i> Reject Unauthorized</label>
|
|
199
|
-
<input type="text" id="node-input-rejectUnauthorized">
|
|
200
|
-
<input type="hidden" id="node-input-rejectUnauthorizedType">
|
|
201
|
-
</div>
|
|
202
|
-
</script>
|
|
203
|
-
|
|
204
|
-
<script type="text/html" data-help-name="email-sender">
|
|
205
|
-
<p>A custom node to send emails using an SMTP server. The configuration for each field can be sourced from a fixed value, or from a <code>msg</code>, <code>flow</code>, <code>global</code>, or <code>env</code> variable.</p>
|
|
206
|
-
|
|
207
|
-
<h3>Configuration</h3>
|
|
208
|
-
<p>Every field on the configuration panel is a <b>typed input</b>. Use the dropdown menu next to each field to select the source of its value. Note that <b>Sender</b>, <b>Address</b>, <b>To</b>, <b>Subject</b>, and <b>HTML Content</b> are required fields and must be configured before the node can be deployed.</p>
|
|
209
|
-
|
|
210
|
-
<h4>Mail Configuration</h4>
|
|
211
|
-
<dl class="message-properties">
|
|
212
|
-
<dt>Sender <span class="property-type">string | variable</span></dt>
|
|
213
|
-
<dd>The name of the sender, as displayed to the recipient.</dd>
|
|
214
|
-
|
|
215
|
-
<dt>Address <span class="property-type">string | variable</span></dt>
|
|
216
|
-
<dd>The sender's email address.</dd>
|
|
217
|
-
|
|
218
|
-
<dt>To <span class="property-type">string | variable</span></dt>
|
|
219
|
-
<dd>The primary recipient's email address. Separate multiple addresses with a comma.</dd>
|
|
220
|
-
|
|
221
|
-
<dt>CC <span class="property-type">string | variable</span></dt>
|
|
222
|
-
<dd>Addresses to be carbon-copied on the email.</dd>
|
|
223
|
-
|
|
224
|
-
<dt>BCC <span class="property-type">string | variable</span></dt>
|
|
225
|
-
<dd>Addresses to be blind-carbon-copied on the email.</dd>
|
|
226
|
-
|
|
227
|
-
<dt>Subject <span class="property-type">string | variable</span></dt>
|
|
228
|
-
<dd>The subject line of the email.</dd>
|
|
229
|
-
|
|
230
|
-
<dt>Attachments <span class="property-type">array | variable</span></dt>
|
|
231
|
-
<dd>A list of file attachments. This should be a variable containing an array of attachment objects.</dd>
|
|
232
|
-
|
|
233
|
-
<dt>HTML Content <span class="property-type">string | variable</span></dt>
|
|
234
|
-
<dd>The HTML body of the email.</dd>
|
|
235
|
-
</dl>
|
|
236
|
-
|
|
237
|
-
<h4>SMTP Configuration</h4>
|
|
238
|
-
<dl class="message-properties">
|
|
239
|
-
<dt>Host <span class="property-type">string | variable</span></dt>
|
|
240
|
-
<dd>The hostname or IP address of the SMTP server.</dd>
|
|
241
|
-
|
|
242
|
-
<dt>Port <span class="property-type">number | variable</span></dt>
|
|
243
|
-
<dd>The port number of the SMTP server.</dd>
|
|
244
|
-
|
|
245
|
-
<dt>User <span class="property-type">string | variable</span></dt>
|
|
246
|
-
<dd>The username for SMTP authentication.</dd>
|
|
247
|
-
|
|
248
|
-
<dt>Password <span class="property-type">string | variable</span></dt>
|
|
249
|
-
<dd>The password for SMTP authentication.</dd>
|
|
250
|
-
|
|
251
|
-
<dt>SSL/TLS (Secure) <span class="property-type">boolean | variable</span></dt>
|
|
252
|
-
<dd>Use a secure connection. Set to <code>true</code> for SSL/TLS, <code>false</code> for a non-secure connection.</dd>
|
|
253
|
-
|
|
254
|
-
<dt>Reject Unauthorized <span class="property-type">boolean | variable</span></dt>
|
|
255
|
-
<dd>If <code>true</code>, the server's certificate is rejected if it's not authorized by a trusted CA.</dd>
|
|
256
|
-
</dl>
|
|
257
|
-
</script>
|
|
@@ -1,91 +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 = send || function() { node.send.apply(node, arguments); };
|
|
11
|
-
done = done || function(err) { if (err) node.error(err, msg); };
|
|
12
|
-
|
|
13
|
-
// Retrieve and evaluate mail configuration values
|
|
14
|
-
const sender = RED.util.evaluateNodeProperty(config.sender, config.senderType, node, msg);
|
|
15
|
-
const address = RED.util.evaluateNodeProperty(config.address, config.addressType, node, msg);
|
|
16
|
-
const to = RED.util.evaluateNodeProperty(config.to, config.toType, node, msg);
|
|
17
|
-
const cc = RED.util.evaluateNodeProperty(config.cc, config.ccType, node, msg) || "";
|
|
18
|
-
const bcc = RED.util.evaluateNodeProperty(config.bcc, config.bccType, node, msg) || "";
|
|
19
|
-
const subject = RED.util.evaluateNodeProperty(config.subject, config.subjectType, node, msg) || msg.topic || "Message from Node-RED";
|
|
20
|
-
const attachments = RED.util.evaluateNodeProperty(config.attachments, config.attachmentsType, node, msg);
|
|
21
|
-
const htmlContent = RED.util.evaluateNodeProperty(config.htmlContent, config.htmlContentType, node, msg);
|
|
22
|
-
|
|
23
|
-
// Retrieve and evaluate SMTP configuration values
|
|
24
|
-
const host = RED.util.evaluateNodeProperty(config.host, config.hostType, node, msg);
|
|
25
|
-
const port = RED.util.evaluateNodeProperty(config.port, config.portType, node, msg);
|
|
26
|
-
const user = RED.util.evaluateNodeProperty(config.user, config.userType, node, msg);
|
|
27
|
-
const password = RED.util.evaluateNodeProperty(config.password, config.passwordType, node, msg);
|
|
28
|
-
const secure = RED.util.evaluateNodeProperty(config.secure, config.secureType, node, msg);
|
|
29
|
-
const rejectUnauthorized = RED.util.evaluateNodeProperty(config.rejectUnauthorized, config.rejectUnauthorizedType, node, msg);
|
|
30
|
-
|
|
31
|
-
// Create SMTP transporter
|
|
32
|
-
const transporter = nodemailer.createTransport({
|
|
33
|
-
host: host,
|
|
34
|
-
port: port,
|
|
35
|
-
secure: secure,
|
|
36
|
-
auth: {
|
|
37
|
-
user: user,
|
|
38
|
-
pass: password
|
|
39
|
-
},
|
|
40
|
-
tls: {
|
|
41
|
-
rejectUnauthorized: rejectUnauthorized
|
|
42
|
-
}
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
// Create email object
|
|
46
|
-
const mailOptions = {
|
|
47
|
-
from: {
|
|
48
|
-
name: sender,
|
|
49
|
-
address: address
|
|
50
|
-
},
|
|
51
|
-
to: to,
|
|
52
|
-
cc: cc,
|
|
53
|
-
bcc: bcc,
|
|
54
|
-
subject: subject,
|
|
55
|
-
html: Buffer.from(htmlContent, 'utf-8'),
|
|
56
|
-
attachments: attachments
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
// Send email
|
|
60
|
-
transporter.sendMail(mailOptions, (error, info) => {
|
|
61
|
-
if (error) {
|
|
62
|
-
node.status({ fill: "red", shape: "dot", text: "error sending" });
|
|
63
|
-
done(error);
|
|
64
|
-
} else {
|
|
65
|
-
node.log('Email sent: ' + info.response);
|
|
66
|
-
msg.payload = info;
|
|
67
|
-
|
|
68
|
-
if (msg.payload.accepted && msg.payload.accepted.length > 0) {
|
|
69
|
-
msg.payload = msg.input;
|
|
70
|
-
node.status({ fill: "green", shape: "dot", text: "sent" });
|
|
71
|
-
send(msg);
|
|
72
|
-
done();
|
|
73
|
-
} else if (msg.payload.rejected && msg.payload.rejected.length > 0) {
|
|
74
|
-
msg.error = { result: msg.payload.rejected };
|
|
75
|
-
node.status({ fill: "red", shape: "dot", text: "rejected" });
|
|
76
|
-
done(new Error('Email rejected: ' + msg.payload.rejected.join(', ')));
|
|
77
|
-
} else if (msg.payload.pending && msg.payload.pending.length > 0) {
|
|
78
|
-
msg.error = { result: msg.payload.pending };
|
|
79
|
-
node.status({ fill: "yellow", shape: "dot", text: "pending" });
|
|
80
|
-
done(new Error('Email pending: ' + msg.payload.pending.join(', ')));
|
|
81
|
-
} else {
|
|
82
|
-
node.status({ fill: "red", shape: "dot", text: "unknown error" });
|
|
83
|
-
done(new Error('Unknown error while sending email.'));
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
});
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
RED.nodes.registerType("email-sender", EmailSenderNode);
|
|
91
|
-
};
|