2018-04-29 16:13:40 +00:00
'use strict' ;
2018-09-27 19:32:35 +00:00
const log = require ( './log' ) ;
2019-07-26 15:05:49 +00:00
const config = require ( './config' ) ;
2018-04-29 16:13:40 +00:00
const nodemailer = require ( 'nodemailer' ) ;
const aws = require ( 'aws-sdk' ) ;
const openpgpEncrypt = require ( 'nodemailer-openpgp' ) . openpgpEncrypt ;
const sendConfigurations = require ( '../models/send-configurations' ) ;
2018-12-21 18:09:18 +00:00
const { ZoneMTAType , MailerType } = require ( '../../shared/send-configurations' ) ;
const builtinZoneMta = require ( './builtin-zone-mta' ) ;
2018-04-29 16:13:40 +00:00
const contextHelpers = require ( './context-helpers' ) ;
const settings = require ( '../models/settings' ) ;
const bluebird = require ( 'bluebird' ) ;
const transports = new Map ( ) ;
2019-06-29 21:19:56 +00:00
class SendConfigurationError extends Error {
constructor ( sendConfigurationId , ... args ) {
super ( ... args ) ;
this . sendConfigurationId = sendConfigurationId ;
Error . captureStackTrace ( this , SendConfigurationError ) ;
}
}
2018-05-20 18:27:35 +00:00
async function getOrCreateMailer ( sendConfigurationId ) {
let sendConfiguration ;
2018-12-16 21:35:21 +00:00
if ( ! sendConfigurationId ) {
2018-05-20 18:27:35 +00:00
sendConfiguration = await sendConfigurations . getSystemSendConfiguration ( ) ;
} else {
2018-07-31 04:34:28 +00:00
sendConfiguration = await sendConfigurations . getById ( contextHelpers . getAdminContext ( ) , sendConfigurationId , false , true ) ;
2018-04-29 16:13:40 +00:00
}
const transport = transports . get ( sendConfiguration . id ) || await _createTransport ( sendConfiguration ) ;
return transport . mailer ;
}
2018-09-02 12:59:02 +00:00
function invalidateMailer ( sendConfigurationId ) {
transports . delete ( sendConfigurationId ) ;
2018-04-29 16:13:40 +00:00
}
2018-12-16 21:35:21 +00:00
function _addDkimKeys ( transport , mail ) {
const sendConfiguration = transport . mailer . sendConfiguration ;
2018-12-21 18:09:18 +00:00
if ( sendConfiguration . mailer _type === MailerType . ZONE _MTA ) {
const mailerSettings = sendConfiguration . mailer _settings ;
if ( mailerSettings . zoneMtaType === ZoneMTAType . WITH _MAILTRAIN _HEADER _CONF || mailerSettings . zoneMtaType === ZoneMTAType . BUILTIN ) {
if ( ! mail . headers ) {
mail . headers = { } ;
}
2018-12-16 21:35:21 +00:00
2018-12-21 18:09:18 +00:00
const dkimDomain = mailerSettings . dkimDomain ;
const dkimSelector = ( mailerSettings . dkimSelector || '' ) . trim ( ) ;
const dkimPrivateKey = ( mailerSettings . dkimPrivateKey || '' ) . trim ( ) ;
2018-12-16 21:35:21 +00:00
2018-12-21 18:09:18 +00:00
if ( dkimSelector && dkimPrivateKey ) {
const from = ( mail . from . address || '' ) . trim ( ) ;
const domain = from . split ( '@' ) . pop ( ) . toLowerCase ( ) . trim ( ) ;
2018-12-16 21:35:21 +00:00
2018-12-21 18:09:18 +00:00
mail . headers [ 'x-mailtrain-dkim' ] = JSON . stringify ( {
domainName : dkimDomain || domain ,
keySelector : dkimSelector ,
privateKey : dkimPrivateKey
} ) ;
}
2018-12-16 21:35:21 +00:00
}
}
}
2018-04-29 16:13:40 +00:00
async function _sendMail ( transport , mail , template ) {
2018-12-16 21:35:21 +00:00
_addDkimKeys ( transport , mail ) ;
2019-06-29 21:19:56 +00:00
try {
return await transport . sendMailAsync ( mail ) ;
2018-09-18 08:30:13 +00:00
2019-06-29 21:19:56 +00:00
} catch ( err ) {
if ( ( err . responseCode && err . responseCode >= 400 && err . responseCode < 500 ) ||
( err . code === 'ECONNECTION' && err . errno === 'ECONNREFUSED' )
) {
throw new SendConfigurationError ( transport . mailer . sendConfiguration . id , 'Cannot connect to service specified by send configuration ' + transport . mailer . sendConfiguration . id ) ;
}
throw err ;
}
2018-09-18 08:30:13 +00:00
}
2019-06-25 05:18:06 +00:00
async function _sendTransactionalMail ( transport , mail ) {
2018-04-29 16:13:40 +00:00
if ( ! mail . headers ) {
mail . headers = { } ;
}
mail . headers [ 'X-Sending-Zone' ] = 'transactional' ;
2019-06-25 05:18:06 +00:00
return await _sendMail ( transport , mail ) ;
}
2018-04-29 16:13:40 +00:00
async function _createTransport ( sendConfiguration ) {
const mailerSettings = sendConfiguration . mailer _settings ;
const mailerType = sendConfiguration . mailer _type ;
const configItems = await settings . get ( contextHelpers . getAdminContext ( ) , [ 'pgpPrivateKey' , 'pgpPassphrase' ] ) ;
const existingTransport = transports . get ( sendConfiguration . id ) ;
let existingListeners = [ ] ;
if ( existingTransport ) {
existingListeners = existingTransport . listeners ( 'idle' ) ;
existingTransport . removeAllListeners ( 'idle' ) ;
existingTransport . removeAllListeners ( 'stream' ) ;
2018-09-18 08:30:13 +00:00
existingTransport . throttleWait = null ;
2018-04-29 16:13:40 +00:00
}
const logFunc = ( ... args ) => {
const level = args . shift ( ) ;
args . shift ( ) ;
args . unshift ( 'Mail' ) ;
log [ level ] ( ... args ) ;
} ;
let transportOptions ;
2018-12-21 18:09:18 +00:00
if ( mailerType === MailerType . GENERIC _SMTP || mailerType === MailerType . ZONE _MTA ) {
2018-04-29 16:13:40 +00:00
transportOptions = {
pool : true ,
debug : mailerSettings . logTransactions ,
logger : mailerSettings . logTransactions ? {
debug : logFunc . bind ( null , 'verbose' ) ,
info : logFunc . bind ( null , 'info' ) ,
error : logFunc . bind ( null , 'error' )
} : false ,
maxConnections : mailerSettings . maxConnections ,
maxMessages : mailerSettings . maxMessages ,
tls : {
rejectUnauthorized : ! mailerSettings . allowSelfSigned
}
} ;
2019-02-17 17:47:27 +00:00
if ( mailerType === MailerType . ZONE _MTA && mailerSettings . zoneMtaType === ZoneMTAType . BUILTIN ) {
2018-12-21 18:09:18 +00:00
transportOptions . host = config . builtinZoneMTA . host ;
transportOptions . port = config . builtinZoneMTA . port ;
transportOptions . secure = false ;
transportOptions . ignoreTLS = true ;
transportOptions . auth = {
user : builtinZoneMta . getUsername ( ) ,
pass : builtinZoneMta . getPassword ( )
} ;
} else {
transportOptions . host = mailerSettings . hostname ;
transportOptions . port = mailerSettings . port || false ;
transportOptions . secure = mailerSettings . encryption === 'TLS' ;
transportOptions . ignoreTLS = mailerSettings . encryption === 'NONE' ;
transportOptions . auth = mailerSettings . useAuth ? {
user : mailerSettings . user ,
pass : mailerSettings . password
} : false ;
}
} else if ( mailerType === MailerType . AWS _SES ) {
2018-04-29 16:13:40 +00:00
const sendingRate = mailerSettings . throttling / 3600 ; // convert to messages/second
transportOptions = {
SES : new aws . SES ( {
apiVersion : '2010-12-01' ,
accessKeyId : mailerSettings . key ,
secretAccessKey : mailerSettings . secret ,
region : mailerSettings . region
} ) ,
debug : mailerSettings . logTransactions ,
logger : mailerSettings . logTransactions ? {
debug : logFunc . bind ( null , 'verbose' ) ,
info : logFunc . bind ( null , 'info' ) ,
error : logFunc . bind ( null , 'error' )
} : false ,
maxConnections : mailerSettings . maxConnections ,
sendingRate
} ;
} else {
throw new Error ( 'Invalid mail transport' ) ;
}
const transport = nodemailer . createTransport ( transportOptions , config . nodemailer ) ;
2019-06-29 21:19:56 +00:00
transport . sendMailAsync = bluebird . promisify ( transport . sendMail . bind ( transport ) ) ;
2018-04-29 16:13:40 +00:00
transport . use ( 'stream' , openpgpEncrypt ( {
signingKey : configItems . pgpPrivateKey ,
passphrase : configItems . pgpPassphrase
} ) ) ;
if ( existingListeners . length ) {
log . info ( 'Mail' , 'Reattaching %s idle listeners' , existingListeners . length ) ;
existingListeners . forEach ( listener => transport . on ( 'idle' , listener ) ) ;
}
2018-09-18 08:30:13 +00:00
let throttleWait ;
2018-04-29 16:13:40 +00:00
2018-12-21 18:09:18 +00:00
if ( mailerType === MailerType . GENERIC _SMTP || mailerType === MailerType . ZONE _MTA ) {
2018-04-29 16:13:40 +00:00
let throttling = mailerSettings . throttling ;
if ( throttling ) {
throttling = 1 / ( throttling / ( 3600 * 1000 ) ) ;
}
let lastCheck = Date . now ( ) ;
2018-09-18 08:30:13 +00:00
throttleWait = function ( next ) {
2018-04-29 16:13:40 +00:00
if ( ! throttling ) {
return next ( ) ;
}
let nextCheck = Date . now ( ) ;
let checkDiff = ( nextCheck - lastCheck ) ;
if ( checkDiff < throttling ) {
log . verbose ( 'Mail' , 'Throttling next message in %s sec.' , ( throttling - checkDiff ) / 1000 ) ;
setTimeout ( ( ) => {
lastCheck = Date . now ( ) ;
next ( ) ;
} , throttling - checkDiff ) ;
} else {
lastCheck = nextCheck ;
next ( ) ;
}
} ;
} else {
2018-09-18 08:30:13 +00:00
throttleWait = next => next ( ) ;
2018-04-29 16:13:40 +00:00
}
transport . mailer = {
2018-12-16 21:35:21 +00:00
sendConfiguration ,
2018-09-18 08:30:13 +00:00
throttleWait : bluebird . promisify ( throttleWait ) ,
2019-06-29 21:19:56 +00:00
sendTransactionalMail : async ( mail ) => await _sendTransactionalMail ( transport , mail ) ,
2018-09-18 08:30:13 +00:00
sendMassMail : async ( mail , template ) => await _sendMail ( transport , mail )
2018-04-29 16:13:40 +00:00
} ;
transports . set ( sendConfiguration . id , transport ) ;
return transport ;
}
2018-09-09 22:55:44 +00:00
class MailerError extends Error {
constructor ( msg , responseCode ) {
super ( msg ) ;
this . responseCode = responseCode ;
}
}
2018-09-02 12:59:02 +00:00
module . exports . getOrCreateMailer = getOrCreateMailer ;
module . exports . invalidateMailer = invalidateMailer ;
2018-09-09 22:55:44 +00:00
module . exports . MailerError = MailerError ;
2019-06-29 21:19:56 +00:00
module . exports . SendConfigurationError = SendConfigurationError ;