UNPKG

@timshel_npm/maildev

Version:

SMTP Server with async API and Web Interface for viewing and testing emails during development

162 lines (161 loc) 6.78 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Outgoing = void 0; const SMTPConnection = require("nodemailer/lib/smtp-connection"); const async = require("async"); const fs = require("fs"); const logger = require("./logger"); const wildstring = require("wildstring"); class Outgoing { constructor(options) { var _a, _b, _c; this.autoRelay = false; wildstring.caseSensitive = false; this.secure = (_a = options === null || options === void 0 ? void 0 : options.secure) !== null && _a !== void 0 ? _a : false; this.host = (_b = options === null || options === void 0 ? void 0 : options.host) !== null && _b !== void 0 ? _b : "localhost"; this.port = (_c = options === null || options === void 0 ? void 0 : options.port) !== null && _c !== void 0 ? _c : (this.secure ? 465 : 25); this.auth = options === null || options === void 0 ? void 0 : options.auth; createClient(this); // Create a queue to sent out the emails // We use a queue so we don't run into concurrency issues this.emailQueue = async.queue((task, callback) => { const relayCallback = function (err, result) { task.callback && task.callback(err, result); callback(err, result); }; relayMail(this, task.emailObject, task.emailStream, task.isAutoRelay, relayCallback); }, 1); if ((options === null || options === void 0 ? void 0 : options.autoRelayAddress) || (options === null || options === void 0 ? void 0 : options.autoRelayRules)) { this.setAutoRelayMode(true, options === null || options === void 0 ? void 0 : options.autoRelayAddress, options === null || options === void 0 ? void 0 : options.autoRelayRules); } } getOutgoingHost() { return this.host; } close() { if (this.client) { this.client.close(); } } setAutoRelayMode(enabled, emailAddress, rules) { this.autoRelay = enabled; this.autoRelayAddress = emailAddress; if (rules) { if (typeof rules === "string") { try { rules = JSON.parse(fs.readFileSync(rules, "utf8")); } catch (err) { logger.error(`Error reading rules file at ${rules}: ${err}`); throw err; } } if (Array.isArray(rules)) { this.autoRelayRules = rules; } } if (this.autoRelay) { const msg = ["Auto-Relay mode on"]; if (this.autoRelayAddress) { msg.push(`Relaying all emails to ${this.autoRelayAddress}`); } if (this.autoRelayRules) { msg.push(`Relay rules: ${JSON.stringify(this.autoRelayRules)}`); } logger.info(msg.join(", ")); } } isAutoRelayEnabled() { return this.autoRelay; } relayMail(emailObject, emailStream, isAutoRelay, callback) { this.emailQueue.push({ emailObject, emailStream, isAutoRelay, callback }); } } exports.Outgoing = Outgoing; function createClient(outgoing) { var _a, _b; try { outgoing.client = new SMTPConnection({ port: outgoing.port, host: outgoing.host, secure: outgoing.secure, auth: outgoing.auth || false, tls: { rejectUnauthorized: false }, debug: true, }); outgoing.client.on("error", function (err) { logger.error("SMTP Connection error for outgoing email: ", err); }); logger.info("MailDev outgoing SMTP Server %s:%d (user:%s, pass:%s, secure:%s)", outgoing.host, outgoing.port, (_a = outgoing === null || outgoing === void 0 ? void 0 : outgoing.auth) === null || _a === void 0 ? void 0 : _a.user, ((_b = outgoing === null || outgoing === void 0 ? void 0 : outgoing.auth) === null || _b === void 0 ? void 0 : _b.pass) ? "####" : undefined, outgoing.secure ? "yes" : "no"); } catch (err) { logger.error("Error during configuration of SMTP Server for outgoing email", err); } } function relayMail(outgoing, emailObject, emailStream, isAutoRelay, done) { if (isAutoRelay && outgoing.autoRelayAddress) { emailObject.to = [{ address: outgoing.autoRelayAddress, name: "Auto-Relay" }]; emailObject.envelope.to = [{ address: outgoing.autoRelayAddress, name: "Auto-Relay" }]; } let recipients = emailObject.envelope.to.map(getAddressFromAddressObject); if (isAutoRelay && outgoing.autoRelayRules) { recipients = getAutoRelayableRecipients(recipients, outgoing.autoRelayRules); } if (recipients.length === 0) { return done("Email had no recipients"); } if (emailObject.envelope.from.length === 0) { return done("Email had no sender"); } const mailSendCallback = function (err) { if (err) { logger.error("Outgoing client login error: ", err); return done(err); } const sender = getAddressFromAddressObject(emailObject.envelope.from); outgoing.client.send({ from: emailObject.envelope.from[0].address, to: recipients, }, emailStream, function (err) { outgoing.client.quit(); createClient(outgoing); if (err) { logger.error("Mail Delivery Error: ", err, { sender, recipients }); return done(err); } logger.log("Mail Delivered: ", emailObject.subject); return done(); }); }; const mailConnectCallback = function (err) { if (err) { logger.error("Outgoing connection error: ", err); return done(err); } if (outgoing.auth) { outgoing.client.login(outgoing.auth, mailSendCallback); } else { mailSendCallback(err); } }; outgoing.client.connect(mailConnectCallback); } // Fallback to the object if the address key isn't defined function getAddressFromAddressObject(addressObj) { return typeof addressObj.address !== "undefined" ? addressObj.address : addressObj; } function getAutoRelayableRecipients(recipients, rules) { return recipients.filter(function (email) { return validateAutoRelayRules(email, rules); }); } function validateAutoRelayRules(email, rules) { return rules.reduce(function (result, rule) { const toMatch = rule.allow || rule.deny || ""; const isMatch = wildstring.match(toMatch, email); // Override previous rule if it matches return isMatch ? !!rule.allow : result; }, true); }