@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
JavaScript
;
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);
}