@sendgrid/mail
Version:
Twilio SendGrid NodeJS mail service
233 lines (190 loc) • 4.97 kB
JavaScript
/**
* Dependencies
*/
const {Client} = require('@sendgrid/client');
const {classes: {Mail}} = require('@sendgrid/helpers');
/**
* Mail service class
*/
class MailService {
/**
* Constructor
*/
constructor() {
// Set client, initialize substitution wrappers and secret rules filter.
this.setClient(new Client());
this.setSubstitutionWrappers('{{', '}}');
this.secretRules = [];
}
/**
* Set client
*/
setClient(client) {
this.client = client;
return this;
}
/**
* SendGrid API key passthrough for convenience.
*/
setApiKey(apiKey) {
this.client.setApiKey(apiKey);
return this;
}
/**
* Twilio Email Auth passthrough for convenience.
*/
setTwilioEmailAuth(username, password) {
this.client.setTwilioEmailAuth(username, password);
}
/**
* Set client timeout
*/
setTimeout(timeout) {
if (typeof timeout === 'undefined') {
return;
}
this.client.setDefaultRequest('timeout', timeout);
}
/**
* Set substitution wrappers
*/
setSubstitutionWrappers(left, right) {
if (typeof left === 'undefined' || typeof right === 'undefined') {
throw new Error('Must provide both left and right side wrappers');
}
if (!Array.isArray(this.substitutionWrappers)) {
this.substitutionWrappers = [];
}
this.substitutionWrappers[0] = left;
this.substitutionWrappers[1] = right;
return this;
}
/**
* Set secret rules for filtering the e-mail content
*/
setSecretRules(rules) {
if (!(rules instanceof Array)) {
rules = [rules];
}
const tmpRules = rules.map(function (rule) {
const ruleType = typeof rule;
if (ruleType === 'string') {
return {
pattern: new RegExp(rule),
};
} else if (ruleType === 'object') {
// normalize rule object
if (rule instanceof RegExp) {
rule = {
pattern: rule,
};
} else if (rule.hasOwnProperty('pattern')
&& (typeof rule.pattern === 'string')
) {
rule.pattern = new RegExp(rule.pattern);
}
try {
// test if rule.pattern is a valid regex
rule.pattern.test('');
return rule;
} catch (err) {
// continue regardless of error
}
}
});
this.secretRules = tmpRules.filter(function (val) {
return val;
});
}
/**
* Check if the e-mail is safe to be sent
*/
filterSecrets(body) {
if ((typeof body === 'object') && !body.hasOwnProperty('content')) {
return;
}
const self = this;
body.content.forEach(function (data) {
self.secretRules.forEach(function (rule) {
if (rule.hasOwnProperty('pattern')
&& !rule.pattern.test(data.value)
) {
return;
}
let message = `The pattern '${rule.pattern}'`;
if (rule.name) {
message += `identified by '${rule.name}'`;
}
message += ' was found in the Mail content!';
throw new Error(message);
});
});
}
/**
* Send email
*/
send(data, isMultiple = false, cb) {
//Callback as second parameter
if (typeof isMultiple === 'function') {
cb = isMultiple;
isMultiple = false;
}
//Array? Send in parallel
if (Array.isArray(data)) {
//Create promise
const promise = Promise.all(data.map(item => {
return this.send(item, isMultiple);
}));
//Execute callback if provided
if (cb) {
promise
.then(result => cb(null, result))
.catch(error => cb(error, null));
}
//Return promise
return promise;
}
//Send mail
try {
//Append multiple flag to data if not set
if (typeof data.isMultiple === 'undefined') {
data.isMultiple = isMultiple;
}
//Append global substitution wrappers if not set in data
if (typeof data.substitutionWrappers === 'undefined') {
data.substitutionWrappers = this.substitutionWrappers;
}
//Create Mail instance from data and get JSON body for request
const mail = Mail.create(data);
const body = mail.toJSON();
//Filters the Mail body to avoid sensitive content leakage
this.filterSecrets(body);
//Create request
const request = {
method: 'POST',
url: '/v3/mail/send',
headers: mail.headers,
body,
};
//Send
return this.client.request(request, cb);
} catch (error) {
//Pass to callback if provided
if (cb) {
// eslint-disable-next-line callback-return
cb(error, null);
}
//Reject promise
return Promise.reject(error);
}
}
/**
* Send multiple emails (shortcut)
*/
sendMultiple(data, cb) {
return this.send(data, true, cb);
}
}
//Export class
module.exports = MailService;
;