maildev
Version:
SMTP Server and Web Interface for reading and testing emails during development
238 lines (192 loc) • 5.71 kB
JavaScript
/**
* MailDev - outgoing.js
*/
const SMTPConnection = require('nodemailer/lib/smtp-connection')
const async = require('async')
const fs = require('fs')
const logger = require('./logger')
const wildstring = require('wildstring')
wildstring.caseSensitive = false
const config = {
autoRelay: false
}
// The SMTP connection client
let client
let emailQueue
/**
* Outgoing exports
*/
const outgoing = module.exports = {}
outgoing.setup = function (host, port, user, pass, secure) {
// defaults
port = port || (secure ? 465 : 25)
host = host || 'localhost'
secure = secure || false
config.host = host
config.port = port
config.user = user
config.pass = pass
config.secure = secure
this._createClient()
// Create a queue to sent out the emails
// We use a queue so we don't run into concurrency issues
emailQueue = async.queue(relayWorker, 1)
}
outgoing._createClient = function () {
try {
client = new SMTPConnection({
port: config.port,
host: config.host,
secure: config.secure,
auth: (config.pass && config.user) ? { user: config.user, pass: config.pass } : false,
tls: { rejectUnauthorized: false },
debug: true
})
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)',
config.host,
config.port,
config.user,
config.pass ? '####' : config.pass,
config.secure ? 'yes' : 'no'
)
} catch (err) {
logger.error('Error during configuration of SMTP Server for outgoing email', err)
}
}
outgoing.close = function () {
if (this.isEnabled()) { client.close() }
}
outgoing.isEnabled = function () {
return !!client
}
outgoing.getOutgoingHost = function () {
return config.host
}
outgoing.isAutoRelayEnabled = function () {
return config.autoRelay
}
outgoing.setAutoRelayMode = function (enabled, rules, emailAddress) {
if (!client) {
config.autoRelay = false
logger.info('Outgoing mail not configured - Auto relay mode ignored')
return
}
if (rules) {
if (typeof rules === 'string') {
try {
rules = JSON.parse(fs.readFileSync(rules, 'utf8'))
} catch (err) {
logger.error('Error reading config file at ' + rules)
throw err
}
}
if (Array.isArray(rules)) {
config.autoRelayRules = rules
}
}
config.autoRelay = enabled
if (enabled && emailAddress) {
config.autoRelayAddress = emailAddress
}
if (config.autoRelay) {
const msg = ['Auto-Relay mode on']
if (config.autoRelayAddress) {
msg.push('Relaying all emails to ' + config.autoRelayAddress)
}
if (config.autoRelayRules) {
msg.push('Relay rules: ' + JSON.stringify(config.autoRelayRules))
}
logger.info(msg.join(', '))
}
}
outgoing.relayMail = function (emailObject, emailStream, isAutoRelay, callback) {
emailQueue.push({
emailObject,
emailStream,
isAutoRelay,
callback
})
}
outgoing.getClient = function () {
return client
}
outgoing.getConfig = function () {
return config
}
function relayMail (emailObject, emailStream, isAutoRelay, done) {
if (!client) { return done(new Error('Outgoing mail not configured')) }
if (isAutoRelay && config.autoRelayAddress) {
emailObject.to = [{ address: config.autoRelayAddress }]
emailObject.envelope.to = [{ address: config.autoRelayAddress, args: false }]
}
let recipients = emailObject?.envelope?.to ? emailObject.envelope.to.map(getAddressFromAddressObject) : [];
if (isAutoRelay && config.autoRelayRules) {
recipients = getAutoRelayableRecipients(recipients)
}
// Fail silently with auth relay mode on
if (recipients.length === 0) {
return done('Email had no recipients')
}
const mailSendCallback = function (err) {
if (err) {
logger.error('Outgoing client login error: ', err)
return done(err)
}
const sender = getAddressFromAddressObject(emailObject.envelope.from)
client.send({
from: sender,
to: recipients
}, emailStream, function (err) {
client.quit()
outgoing._createClient()
if (err) {
logger.error('Mail Delivery Error: ', err)
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 (client.options.auth) {
client.login(client.options.auth, mailSendCallback)
} else {
mailSendCallback(err)
}
}
client.connect(mailConnectCallback)
}
function relayWorker (task, callback) {
const relayCallback = function (err, result) {
task.callback && task.callback(err, result)
callback(err, result)
}
relayMail(task.emailObject, task.emailStream, task.isAutoRelay, relayCallback)
}
// 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) {
return recipients.filter(function (email) {
return validateAutoRelayRules(email)
})
}
function validateAutoRelayRules (email) {
if (!config.autoRelayRules) {
return true
}
return config.autoRelayRules.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)
}