Added native support for SendGrid.
Ylian Saint-Hilaire committed
Dec 16, 2020 at 14:55 UTC
ddd34f56f5eee18120cad6064f7d6cd609a4bc1f
4 files changed
+84
-37
meshcentral-config-schema.json
+13
-3
@@ -545,14 +545,24 @@
545
},
546
"required": [ "serverId", "servers" ]
547
},
548
+ "sendgrid": {
549
+ "title" : "SendGrid.com Email server",
550
+ "description": "Connects MeshCentral to the SendGrid email server, allows MeshCentral to send email messages for 2FA or user notification.",
551
+ "type": "object",
552
+ "properties": {
553
+ "from": { "type": "string", "format": "email", "description": "Email address used in the messages from field." },
554
+ "apikey": { "type": "string", "description": "The SendGrid API key." }
555
+ },
556
+ "required": [ "from", "apikey" ]
557
+ },
558
"smtp": {
549
- "title" : "Email server",
550
- "description": "Connects MeshCentral to a email server, allows MeshCentral to send email messages for 2FA or user notification.",
559
+ "title" : "SMTP email server",
560
+ "description": "Connects MeshCentral to a SMTP email server, allows MeshCentral to send email messages for 2FA or user notification.",
561
"type": "object",
562
"properties": {
563
"host": { "type": "string", "format": "hostname" },
564
"port": { "type": "integer", "minimum": 1, "maximum": 65535 },
555
- "from": { "type": "string", "format": "email" },
565
+ "from": { "type": "string", "format": "email", "description": "Email address used in the messages from field." },
566
"tls": { "type": "boolean" },
567
"tlscertcheck": { "type": "boolean" },
568
"tlsstrict": { "type": "boolean" },
meshcentral.js
+10
-2
@@ -678,7 +678,7 @@ function CreateMeshCentralServer(config, args) {
678
}
679
680
// Check top level configuration for any unreconized values
681
- if (config) { for (var i in config) { if ((typeof i == 'string') && (i.length > 0) && (i[0] != '_') && (['settings', 'domaindefaults', 'domains', 'configfiles', 'smtp', 'letsencrypt', 'peers', 'sms', '$schema'].indexOf(i) == -1)) { addServerWarning('Unrecognized configuration option \"' + i + '\".'); } } }
681
+ if (config) { for (var i in config) { if ((typeof i == 'string') && (i.length > 0) && (i[0] != '_') && (['settings', 'domaindefaults', 'domains', 'configfiles', 'smtp', 'letsencrypt', 'peers', 'sms', 'sendgrid', '$schema'].indexOf(i) == -1)) { addServerWarning('Unrecognized configuration option \"' + i + '\".'); } } }
682
683
if (typeof obj.args.userallowedip == 'string') { if (obj.args.userallowedip == '') { config.settings.userallowedip = obj.args.userallowedip = null; } else { config.settings.userallowedip = obj.args.userallowedip = obj.args.userallowedip.split(','); } }
684
if (typeof obj.args.userblockedip == 'string') { if (obj.args.userblockedip == '') { config.settings.userblockedip = obj.args.userblockedip = null; } else { config.settings.userblockedip = obj.args.userblockedip = obj.args.userblockedip.split(','); } }
@@ -1440,7 +1440,13 @@ function CreateMeshCentralServer(config, args) {
1440
}
1441
1442
// Setup email server
1443
- if ((obj.config.smtp != null) && (obj.config.smtp.host != null) && (obj.config.smtp.from != null)) {
1443
+ if (obj.config.sendgrid != null) {
1444
+ // Sendgrid server
1445
+ obj.mailserver = require('./meshmail.js').CreateMeshMail(obj);
1446
+ obj.mailserver.verify();
1447
+ if (obj.args.lanonly == true) { addServerWarning("SendGrid server has limited use in LAN mode."); }
1448
+ } else if ((obj.config.smtp != null) && (obj.config.smtp.host != null) && (obj.config.smtp.from != null)) {
1449
+ // SMTP server
1450
obj.mailserver = require('./meshmail.js').CreateMeshMail(obj);
1451
obj.mailserver.verify();
1452
if (obj.args.lanonly == true) { addServerWarning("SMTP server has limited use in LAN mode."); }
@@ -2751,6 +2757,7 @@ function InstallModules(modules, func) {
2757
var moduleInfo = moduleNameAndVersion.split('@', 2);
2758
var moduleName = moduleInfo[0];
2759
var moduleVersion = moduleInfo[1];
2760
+ if (moduleName == '') { moduleName = moduleNameAndVersion; moduleVersion = undefined; } // If the module name starts with @, don't use @ as a version seperator.
2761
try {
2762
// Does the module need a specific version?
2763
if (moduleVersion) {
@@ -2888,6 +2895,7 @@ function mainStart() {
2895
if ((config.settings.plugins != null) && (config.settings.plugins.proxy != null)) { modules.push('https-proxy-agent'); } // Required for HTTP/HTTPS proxy support
2896
else if (config.settings.xmongodb != null) { modules.push('mongojs'); } // Add MongoJS, old driver.
2897
if (config.smtp != null) { modules.push('nodemailer'); } // Add SMTP support
2898
+ if (config.sendgrid != null) { modules.push('@sendgrid/mail'); } // Add SendGrid support
2899
if (args.translate) { modules.push('jsdom'); modules.push('esprima'); modules.push('minify-js'); modules.push('html-minifier'); } // Translation support
2900
2901
// If running NodeJS < 8, install "util.promisify"
meshmail.js
+57
-32
@@ -26,19 +26,25 @@ module.exports.CreateMeshMail = function (parent) {
26
obj.mailCookieEncryptionKey = null;
27
//obj.mailTemplates = {};
28
const constants = (obj.parent.crypto.constants ? obj.parent.crypto.constants : require('constants')); // require('constants') is deprecated in Node 11.10, use require('crypto').constants instead.
29
- const nodemailer = require('nodemailer');
29
30
function EscapeHtml(x) { if (typeof x == "string") return x.replace(/&/g, '&').replace(/>/g, '>').replace(/</g, '<').replace(/"/g, '"').replace(/'/g, '''); if (typeof x == "boolean") return x; if (typeof x == "number") return x; }
31
//function EscapeHtmlBreaks(x) { if (typeof x == "string") return x.replace(/&/g, '&').replace(/>/g, '>').replace(/</g, '<').replace(/"/g, '"').replace(/'/g, ''').replace(/\r/g, '<br />').replace(/\n/g, '').replace(/\t/g, ' '); if (typeof x == "boolean") return x; if (typeof x == "number") return x; }
32
34
- // Setup mail server
35
- var options = { host: parent.config.smtp.host, secure: (parent.config.smtp.tls == true), tls: { } };
36
- //var options = { host: parent.config.smtp.host, secure: (parent.config.smtp.tls == true), tls: { secureProtocol: 'SSLv23_method', ciphers: 'RSA+AES:!aNULL:!MD5:!DSS', secureOptions: constants.SSL_OP_NO_SSLv2 | constants.SSL_OP_NO_SSLv3 | constants.SSL_OP_NO_COMPRESSION | constants.SSL_OP_CIPHER_SERVER_PREFERENCE, rejectUnauthorized: false } };
37
- if (parent.config.smtp.port != null) { options.port = parent.config.smtp.port; }
38
- if (parent.config.smtp.tlscertcheck === false) { options.tls.rejectUnauthorized = false; }
39
- if (parent.config.smtp.tlsstrict === true) { options.tls.secureProtocol = 'SSLv23_method'; options.tls.ciphers = 'RSA+AES:!aNULL:!MD5:!DSS'; options.tls.secureOptions = constants.SSL_OP_NO_SSLv2 | constants.SSL_OP_NO_SSLv3 | constants.SSL_OP_NO_COMPRESSION | constants.SSL_OP_CIPHER_SERVER_PREFERENCE; }
40
- if ((parent.config.smtp.user != null) && (parent.config.smtp.pass != null)) { options.auth = { user: parent.config.smtp.user, pass: parent.config.smtp.pass }; }
41
- obj.smtpServer = nodemailer.createTransport(options);
33
+ if (parent.config.sendgrid != null) {
34
+ // Setup SendGrid mail server
35
+ obj.sendGridServer = require('@sendgrid/mail');
36
+ obj.sendGridServer.setApiKey(parent.config.sendgrid.apikey);
37
+ } else if (parent.config.smtp != null) {
38
+ // Setup SMTP mail server
39
+ const nodemailer = require('nodemailer');
40
+ var options = { host: parent.config.smtp.host, secure: (parent.config.smtp.tls == true), tls: {} };
41
+ //var options = { host: parent.config.smtp.host, secure: (parent.config.smtp.tls == true), tls: { secureProtocol: 'SSLv23_method', ciphers: 'RSA+AES:!aNULL:!MD5:!DSS', secureOptions: constants.SSL_OP_NO_SSLv2 | constants.SSL_OP_NO_SSLv3 | constants.SSL_OP_NO_COMPRESSION | constants.SSL_OP_CIPHER_SERVER_PREFERENCE, rejectUnauthorized: false } };
42
+ if (parent.config.smtp.port != null) { options.port = parent.config.smtp.port; }
43
+ if (parent.config.smtp.tlscertcheck === false) { options.tls.rejectUnauthorized = false; }
44
+ if (parent.config.smtp.tlsstrict === true) { options.tls.secureProtocol = 'SSLv23_method'; options.tls.ciphers = 'RSA+AES:!aNULL:!MD5:!DSS'; options.tls.secureOptions = constants.SSL_OP_NO_SSLv2 | constants.SSL_OP_NO_SSLv3 | constants.SSL_OP_NO_COMPRESSION | constants.SSL_OP_CIPHER_SERVER_PREFERENCE; }
45
+ if ((parent.config.smtp.user != null) && (parent.config.smtp.pass != null)) { options.auth = { user: parent.config.smtp.user, pass: parent.config.smtp.pass }; }
46
+ obj.smtpServer = nodemailer.createTransport(options);
47
+ }
48
49
// Get the correct mail template object
50
function getTemplate(name, domain, lang) {
@@ -143,7 +149,11 @@ module.exports.CreateMeshMail = function (parent) {
149
150
// Send a generic email
151
obj.sendMail = function (to, subject, text, html) {
146
- obj.pendingMails.push({ to: to, from: parent.config.smtp.from, subject: subject, text: text, html: html });
152
+ if (parent.config.sendgrid != null) {
153
+ obj.pendingMails.push({ to: to, from: parent.config.sendgrid.from, subject: subject, text: text, html: html });
154
+ } else if (parent.config.smtp != null) {
155
+ obj.pendingMails.push({ to: to, from: parent.config.smtp.from, subject: subject, text: text, html: html });
156
+ }
157
sendNextMail();
158
};
159
@@ -300,36 +310,51 @@ module.exports.CreateMeshMail = function (parent) {
310
311
var mailToSend = obj.pendingMails[0];
312
obj.sendingMail = true;
303
- parent.debug('email', 'SMTP sending mail to ' + mailToSend.to + '.');
304
- obj.smtpServer.sendMail(mailToSend, function (err, info) {
305
- parent.debug('email', 'SMTP response: ' + JSON.stringify(err) + ', ' + JSON.stringify(info));
306
- obj.sendingMail = false;
307
- if (err == null) {
308
- // Send the next mail
309
- obj.pendingMails.shift();
310
- obj.retry = 0;
311
- sendNextMail();
312
- } else {
313
- obj.retry++;
314
- parent.debug('email', 'SMTP server failed (Retry:' + obj.retry + '): ' + JSON.stringify(err));
315
- console.log('SMTP server failed (Retry:' + obj.retry + '/3): ' + JSON.stringify(err));
316
- // Wait and try again
317
- if (obj.retry < 3) {
318
- setTimeout(sendNextMail, 10000);
319
- } else {
320
- // Failed, send the next mail
321
- parent.debug('email', 'SMTP server failed (Skipping): ' + JSON.stringify(err));
322
- console.log('SMTP server failed (Skipping): ' + JSON.stringify(err));
313
+
314
+ if (obj.sendGridServer != null) {
315
+ // SendGrid send
316
+ parent.debug('email', 'SendGrid sending mail to ' + mailToSend.to + '.');
317
+ obj.sendGridServer
318
+ .send(mailToSend)
319
+ .then(function () {
320
+ parent.debug('email', 'SendGrid sending success.');
321
+ }, function (error) {
322
+ parent.debug('email', 'SendGrid sending error: ' + JSON.stringify(error));
323
+ });
324
+ } else if (obj.smtpServer != null) {
325
+ // SMTP send
326
+ parent.debug('email', 'SMTP sending mail to ' + mailToSend.to + '.');
327
+ obj.smtpServer.sendMail(mailToSend, function (err, info) {
328
+ parent.debug('email', 'SMTP response: ' + JSON.stringify(err) + ', ' + JSON.stringify(info));
329
+ obj.sendingMail = false;
330
+ if (err == null) {
331
+ // Send the next mail
332
obj.pendingMails.shift();
333
obj.retry = 0;
334
sendNextMail();
335
+ } else {
336
+ obj.retry++;
337
+ parent.debug('email', 'SMTP server failed (Retry:' + obj.retry + '): ' + JSON.stringify(err));
338
+ console.log('SMTP server failed (Retry:' + obj.retry + '/3): ' + JSON.stringify(err));
339
+ // Wait and try again
340
+ if (obj.retry < 3) {
341
+ setTimeout(sendNextMail, 10000);
342
+ } else {
343
+ // Failed, send the next mail
344
+ parent.debug('email', 'SMTP server failed (Skipping): ' + JSON.stringify(err));
345
+ console.log('SMTP server failed (Skipping): ' + JSON.stringify(err));
346
+ obj.pendingMails.shift();
347
+ obj.retry = 0;
348
+ sendNextMail();
349
+ }
350
}
327
- }
328
- });
351
+ });
352
+ }
353
}
354
355
// Send out the next mail in the pending list
356
obj.verify = function () {
357
+ if (obj.smtpServer == null) return;
358
obj.smtpServer.verify(function (err, info) {
359
if (err == null) {
360
console.log('SMTP mail server ' + parent.config.smtp.host + ' working as expected.');
sample-config-advanced.json
+4
@@ -385,6 +385,10 @@
385
"__tlsstrict__": "When set to true, TLS cypher setup is more limited, SSLv2 and SSLv3 are not allowed.",
386
"_tlsstrict": true
387
},
388
+ "_sendgrid": {
389
+ "from": "myemail@myserver.com",
390
+ "apikey": "***********"
391
+ },
392
"_sms": {
393
"provider": "twilio",
394
"sid": "ACxxxxxxxxx",