nodemailer-mailgun-transport
Version:
A transport module to use with nodemailer to leverage Mailgun's REST API
170 lines (159 loc) • 4.78 kB
JavaScript
const Mailgun = require("mailgun.js");
const formData = require("form-data");
const consolidate = require("consolidate");
const packageData = require("../package.json");
const whitelist = [
["replyTo", "h:Reply-To"],
["messageId", "h:Message-Id"],
[/^h:/],
[/^v:/],
["from"],
["to"],
["cc"],
["bcc"],
["subject"],
["text"],
["template"],
["html"],
["attachment"],
["inline"],
["recipient-variables"],
["o:tag"],
["o:campaign"],
["o:dkim"],
["o:deliverytime"],
["o:testmode"],
["o:tracking"],
["o:tracking-clicks"],
["o:tracking-opens"],
["o:require-tls"],
["o:skip-verification"],
["X-Mailgun-Variables"]
];
const applyKeyWhitelist = mail =>
Object.keys(mail).reduce((acc, key) => {
const targetKey = whitelist.reduce((result, [cond, target]) => {
if (result) {
return result;
}
if ((cond.exec && cond.exec(key)) || cond === key) {
return target || key;
}
return null;
}, null);
if (!targetKey || !mail[key]) {
return acc;
}
return { ...acc, [targetKey]: mail[key] };
}, {});
const renderTemplate = async mail => {
if (mail.html) {
return { template: null, html: mail.html }
}
if (!mail.template || typeof mail.template === "string" || !mail.template.name || !mail.template.engine) {
// either there's no template or the caller is requesting a mailgun template
// so let everything through unaltered
return {};
}
const { engine, name, context = {} } = mail.template;
const html = await consolidate[engine](name, context);
return { template: null, html };
};
const makeMailgunAttachments = (attachments = []) => {
const [attachment, inline] = attachments.reduce(
(results, item) => {
const data =
typeof item.content === "string"
? Buffer.from(item.content, item.encoding)
: item.content || item.path || undefined;
const attachment = {
data,
filename: item.cid || item.filename || undefined,
contentType: item.contentType || undefined,
knownLength: item.knownLength || undefined
};
const [attachmentAttachments, inlineAttachments] = results;
return [
attachmentAttachments.concat(!item.cid ? attachment : []),
inlineAttachments.concat(item.cid ? attachment : [])
];
},
[[], []]
);
return {
...(attachment.length ? { attachment } : {}),
...(inline.length ? { inline } : {})
};
};
const makeAllTextAddresses = mail => {
const keys = ["from", "to", "cc", "bcc", "replyTo"];
const makeTextAddresses = addresses => {
const validAddresses = [].concat(addresses).filter(Boolean);
const textAddresses = validAddresses.map(item =>
item.address
? item.name
? item.name + " <" + item.address + ">"
: item.address
: typeof item === "string"
? item
: null
);
return textAddresses.filter(Boolean).join();
};
const result = keys.reduce((result, key) => {
const textAddresses = makeTextAddresses(mail[key]);
if (!textAddresses) {
return result;
}
return { ...result, [key]: textAddresses };
}, {});
return result;
};
const send = mailgunSend => async ({ data: mail }, callback) => {
try {
const addresses = makeAllTextAddresses(mail);
const attachments = makeMailgunAttachments(mail.attachments);
const template = await renderTemplate(mail);
const extendedMail = {
...mail,
...addresses,
...attachments,
...template
};
const whitelistedMail = applyKeyWhitelist(extendedMail);
const result = await mailgunSend(whitelistedMail);
callback(null, { ...result, messageId: result.id });
} catch (error) {
callback(error);
}
};
const transport = (options = {}) => {
const mailgun = new Mailgun(formData);
let url = options.url;
if (!options.url && options.host) {
const generatedUrl = new URL("https://" + (options.host || "api.mailgun.net"));
generatedUrl.protocol = options.protocol || "https:";
generatedUrl.port = options.port || 443;
url = generatedUrl.href;
}
const messages = mailgun.client({
username: 'api',
key: options.auth.api_key || options.auth.apiKey,
url,
timeout: options.timeout,
}).messages;
const mailgunSend = mail => messages.create(options.auth.domain || "", mail);
return {
name: "Mailgun",
version: packageData.version,
send: send(mailgunSend),
messages,
options
};
};
transport._send = send;
transport._makeAllTextAddresses = makeAllTextAddresses;
transport._makeMailgunAttachments = makeMailgunAttachments;
transport._renderTemplate = renderTemplate;
transport._applyKeyWhitelist = applyKeyWhitelist;
module.exports = transport;