Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

maildev

Package Overview
Dependencies
Maintainers
1
Versions
46
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

maildev - npm Package Compare versions

Comparing version 0.14.0 to 1.0.0-rc1

lib/helpers/smtp.js

76

index.js

@@ -9,14 +9,12 @@

var program = require('commander');
var async = require('async');
var pkg = require('./package.json');
var web = require('./lib/web');
var mailserver = require('./lib/mailserver');
var logger = require('./lib/logger');
const program = require('commander')
const async = require('async')
const pkg = require('./package.json')
const web = require('./lib/web')
const mailserver = require('./lib/mailserver')
const logger = require('./lib/logger')
module.exports = function (config) {
const version = pkg.version
module.exports = function(config) {
var version = pkg.version;
if (!config) {

@@ -42,16 +40,21 @@ // CLI

.option('--base-pathname <path>', 'base path for URLs')
.option('--disable-web', 'Disable the use of the web interface. Useful for unit testing')
.option('--hide-extensions <extensions>',
'Comma separated list of SMTP extensions to NOT advertise (STARTTLS, SMTPUTF8, PIPELINING, 8BITMIME)',
function (val) { return val.split(',') }
)
.option('-o, --open', 'Open the Web GUI after startup')
.option('-v, --verbose')
.option('--silent')
.parse(process.argv);
.parse(process.argv)
}
if (config.verbose) {
logger.setLevel(2);
logger.setLevel(2)
} else if (config.silent) {
logger.setLevel(0);
logger.setLevel(0)
}
// Start the Mailserver & Web GUI
mailserver.create(config.smtp, config.ip, config.incomingUser, config.incomingPass);
mailserver.create(config.smtp, config.ip, config.incomingUser, config.incomingPass, config.hideExtensions)

@@ -63,3 +66,2 @@ if (config.outgoingHost ||

config.outgoingSecure) {
mailserver.setupOutgoing(

@@ -71,29 +73,37 @@ config.outgoingHost,

config.outgoingSecure
);
)
}
if (config.autoRelay){
mailserver.setAutoRelayMode(true, config.autoRelayRules);
if (config.autoRelay) {
mailserver.setAutoRelayMode(true, config.autoRelayRules)
}
// Default to run on same IP as smtp
var webIp = config.webIp ? config.webIp : config.ip;
web.start(config.web, webIp, mailserver, config.webUser, config.webPass, config.basePathname);
if (!config.disableWeb) {
// Default to run on same IP as smtp
const webIp = config.webIp ? config.webIp : config.ip
web.start(config.web, webIp, mailserver, config.webUser, config.webPass, config.basePathname)
if (config.open){
var open = require('open');
open('http://' + (config.ip === '0.0.0.0' ? 'localhost' : config.ip) + ':' + config.web);
if (config.open) {
const open = require('open')
open('http://' + (config.ip === '0.0.0.0' ? 'localhost' : config.ip) + ':' + config.web)
}
// Close the web server when the mailserver closes
mailserver.on('close', web.close)
}
process.on('SIGTERM', function () {
function shutdown () {
logger.info(`Recieved shutdown signal, shutting down now...`)
async.parallel([
mailserver.end,
mailserver.close,
web.close
], function(err, results) {
logger.info('Shutting down...');
process.exit(0);
});
});
], function () {
process.exit(0)
})
}
return mailserver;
};
process.on('SIGTERM', shutdown)
process.on('SIGINT', shutdown)
return mailserver
}

@@ -0,1 +1,2 @@

'use strict'

@@ -6,19 +7,17 @@ /**

module.exports = function(user, password) {
return function(req, res, next) {
var auth;
module.exports = function (user, password) {
return function (req, res, next) {
var auth
if (req.headers.authorization) {
auth = new Buffer(req.headers.authorization.substring(6), 'base64').toString().split(':');
auth = Buffer.from(req.headers.authorization.substring(6), 'base64').toString().split(':')
}
if (!auth || auth[0] !== user || auth[1] !== password) {
res.statusCode = 401;
res.setHeader('WWW-Authenticate', 'Basic realm="Authentication required"');
res.send('Unauthorized');
res.statusCode = 401
res.setHeader('WWW-Authenticate', 'Basic realm="Authentication required"')
res.send('Unauthorized')
} else {
next();
next()
}
};
};
}
}

@@ -0,1 +1,2 @@

'use strict'

@@ -6,7 +7,6 @@ /**

var logLevel = 1;
let logLevel = 1
module.exports = {};
module.exports = {}
/**

@@ -16,5 +16,5 @@ * Initialize the logger

module.exports.setLevel = function(level) {
logLevel = level;
};
module.exports.setLevel = function (level) {
logLevel = level
}

@@ -25,8 +25,6 @@ /**

module.exports.info = function() {
if (logLevel > 0)
console.info.apply(console, arguments);
module.exports.info = function () {
if (logLevel > 0) { console.info.apply(console, arguments) }
};
/**

@@ -36,10 +34,8 @@ * Extend the basic console.x functions, checking if the logging is on

['log', 'dir', 'warn', 'error'].forEach(function(fn){
module.exports[ fn ] = function(){
['log', 'dir', 'warn', 'error'].forEach(function (fn) {
module.exports[ fn ] = function () {
if (logLevel > 1) {
console[ fn ].apply(console, arguments);
console[ fn ].apply(console, arguments)
}
};
});
}
})

@@ -0,1 +1,2 @@

'use strict'

@@ -6,27 +7,19 @@ /**

// NOTE - simplesmtp is for backwards compatibility with 0.10.x
var simplesmtp = require('simplesmtp');
var SMTPServer = require('smtp-server').SMTPServer;
var MailParser = require('mailparser').MailParser;
var events = require('events');
var fs = require('fs');
var os = require('os');
var path = require('path');
var logger = require('./logger');
var outgoing = require('./outgoing');
const SMTPServer = require('smtp-server').SMTPServer
const MailParser = require('mailparser').MailParser
const events = require('events')
const fs = require('fs')
const os = require('os')
const path = require('path')
const utils = require('./utils')
const logger = require('./logger')
const smtpHelpers = require('./helpers/smtp')
const outgoing = require('./outgoing')
const defaultPort = 1025
const defaultHost = '0.0.0.0'
const store = []
const tempDir = path.join(os.tmpdir(), 'maildev', process.pid.toString())
const eventEmitter = new events.EventEmitter()
var version = process.version.replace(/v/, '')
.split(/\./)
.map(function(n) { return parseInt(n, 10); });
var legacy = version[0] === 0 && version[1] <= 10;
var defaultPort = 1025;
var defaultHost = '0.0.0.0';
var config = {};
var store = [];
var tempDir = path.join(os.tmpdir(), 'maildev', process.pid.toString());
var eventEmitter = new events.EventEmitter();
var smtp;
/**

@@ -36,7 +29,6 @@ * Mail Server exports

var mailServer = module.exports = {};
const mailServer = module.exports = {}
mailServer.store = store;
mailServer.store = store
/**

@@ -47,97 +39,58 @@ * SMTP Server stream and helper functions

// Clone object
function clone(object){
return JSON.parse(JSON.stringify(object));
function clone (object) {
return JSON.parse(JSON.stringify(object))
}
// Create an unique id, length 8 characters
function makeId(){
var text = '';
var possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
for (var i = 0; i < 8; i++){
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
}
// Save an email object on stream end
function saveEmail(id, envelope, mailObject){
function saveEmail (id, envelope, mailObject) {
// remove stream object from attachments (fix the JSON.stringify)
if (mailObject.attachments instanceof Array) {
mailObject.attachments.forEach(function(attachment) {
delete attachment.stream;
});
mailObject.attachments.forEach(function (attachment) {
delete attachment.stream
})
}
var object = clone(mailObject);
var object = clone(mailObject)
object.id = id;
object.time = new Date();
object.read = false;
object.envelope = envelope;
object.source = path.join(tempDir, id + '.eml');
object.id = id
object.time = new Date()
object.read = false
object.envelope = envelope
object.source = path.join(tempDir, id + '.eml')
store.push(object);
store.push(object)
logger.log('Saving email: ', mailObject.subject);
logger.log('Saving email: ', mailObject.subject)
if (outgoing.isAutoRelayEnabled()) {
mailServer.relayMail(object, true, function (err) {
if (err) logger.error('Error when relaying email', err);
});
if (err) logger.error('Error when relaying email', err)
})
}
eventEmitter.emit('new', object);
eventEmitter.emit('new', object)
}
// Save an attachment
function saveAttachment(attachment){
var output = fs.createWriteStream(path.join(tempDir, attachment.contentId));
attachment.stream.pipe(output);
function saveAttachment (attachment) {
var output = fs.createWriteStream(path.join(tempDir, attachment.contentId))
attachment.stream.pipe(output)
}
function createSaveStream(id, emailData) {
function createSaveStream (id, emailData) {
var parseStream = new MailParser({
streamAttachments: true
});
parseStream.on('end', saveEmail.bind(null, id, emailData));
parseStream.on('attachment', saveAttachment);
return parseStream;
})
parseStream.on('end', saveEmail.bind(null, id, emailData))
parseStream.on('attachment', saveAttachment)
return parseStream
}
function createRawStream(id) {
return fs.createWriteStream(path.join(tempDir, id + '.eml'));
function createRawStream (id) {
return fs.createWriteStream(path.join(tempDir, id + '.eml'))
}
// Legacy methods are used with the Node 0.10 compatible smtpserver
function handleLegacyDataStream(connection) {
var id = makeId();
function handleDataStream (stream, session, callback) {
const id = utils.makeId()
connection.saveStream = createSaveStream(id, {
from: connection.from,
to: connection.to,
host: connection.host,
remoteAddress: connection.remoteAddress
});
connection.saveRawStream = createRawStream(id);
}
function handleLegacyChunk(connection, chunk) {
connection.saveStream.write(chunk);
connection.saveRawStream.write(chunk);
}
function handleLegacyDataStreamEnd(connection, done) {
connection.saveStream.end();
connection.saveRawStream.end();
// ABC is the queue id to be advertised to the client
// There is no current significance to this.
done(null, 'ABC');
}
function handleDataStream(stream, session, callback) {
var id = makeId();
session.saveStream = createSaveStream(id, {

@@ -148,16 +101,15 @@ from: session.envelope.mailFrom,

remoteAddress: session.remoteAddress
});
})
session.saveRawStream = createRawStream(id);
session.saveRawStream = createRawStream(id)
stream.pipe(session.saveStream);
stream.pipe(session.saveRawStream);
stream.pipe(session.saveStream)
stream.pipe(session.saveRawStream)
stream.on('end', function(){
session.saveRawStream.end();
callback(null, 'Message queued as ' + id);
});
stream.on('end', function () {
session.saveRawStream.end()
callback(null, 'Message queued as ' + id)
})
}
/**

@@ -167,16 +119,14 @@ * Delete everything in the temp folder

function clearTempFolder(){
function clearTempFolder () {
fs.readdir(tempDir, function (err, files) {
if (err) throw err
fs.readdir(tempDir, function(err, files){
if (err) throw err;
files.forEach(function(file){
fs.unlink( path.join(tempDir, file), function(err) {
if (err) throw err;
});
});
});
files.forEach(function (file) {
fs.unlink(path.join(tempDir, file), function (err) {
if (err) throw err
})
})
})
}
/**

@@ -186,12 +136,10 @@ * Delete a single email's attachments

function deleteAttachments(attachments) {
attachments.forEach(function(attachment){
fs.unlink( path.join(tempDir, attachment.contentId), function (err) {
if (err) logger.error(err);
});
});
function deleteAttachments (attachments) {
attachments.forEach(function (attachment) {
fs.unlink(path.join(tempDir, attachment.contentId), function (err) {
if (err) logger.error(err)
})
})
}
/**

@@ -201,17 +149,16 @@ * Create temp folder

function createTempFolder() {
function createTempFolder () {
if (fs.existsSync(tempDir)) {
clearTempFolder();
return;
clearTempFolder()
return
}
if (!fs.existsSync(path.dirname(tempDir))) {
fs.mkdirSync(path.dirname(tempDir));
logger.log('Temporary directory created at %s', path.dirname(tempDir));
fs.mkdirSync(path.dirname(tempDir))
logger.log('Temporary directory created at %s', path.dirname(tempDir))
}
if (!fs.existsSync(tempDir)) {
fs.mkdirSync(tempDir);
logger.log('Temporary directory created at %s', tempDir);
fs.mkdirSync(tempDir)
logger.log('Temporary directory created at %s', tempDir)
}

@@ -221,67 +168,51 @@ }

/**
* Authorize callback for smtp server
* Create and configure the mailserver
*/
function authorizeUser(auth, session, callback) {
var username = auth.username;
var password = auth.password;
mailServer.create = function (port, host, user, password, hideExtensions) {
const hideExtensionOptions = getHideExtensionOptions(hideExtensions)
const smtpServerConfig = Object.assign({
onAuth: smtpHelpers.createOnAuthCallback(user, password),
onData: handleDataStream,
logger: false,
disabledCommands: (user && password) ? ['STARTTLS'] : ['AUTH']
}, hideExtensionOptions)
// conn, username, password, callback
if (legacy) {
username = arguments[1];
password = arguments[2];
callback = arguments[3];
}
const smtp = new SMTPServer(smtpServerConfig)
if (this.options.incomingUser && this.options.incomingPassword) {
if (username !== this.options.incomingUser ||
password !== this.options.incomingPassword) {
return callback(new Error('Invalid username or password'));
}
}
callback(null, { user: this.options.incomingUser });
}
smtp.on('error', mailServer.onSmtpError)
// Setup temp folder for attachments
createTempFolder()
/**
* Create and configure the mailserver
*/
mailServer.port = port || defaultPort
mailServer.host = host || defaultHost
mailServer.create = function(port, host, user, password) {
// testability requires this to be exposed.
// otherwise we cannot test whether error handling works
mailServer.smtp = smtp
}
// Start the server & Disable DNS checking
if (legacy) {
smtp = simplesmtp.createServer({
disableDNSValidation: true,
requireAuthentication: !!(user && password),
incomingUser: user,
incomingPassword: password
});
const HIDEABLE_EXTENSIONS = [
'STARTTLS',
'PIPELINING',
'8BITMIME',
'SMTPUTF8'
]
smtp.on('startData', handleLegacyDataStream);
smtp.on('data', handleLegacyChunk);
smtp.on('dataReady', handleLegacyDataStreamEnd);
if (user && password) {
smtp.on('authorizeUser', authorizeUser);
function getHideExtensionOptions (extensions) {
if (!extensions) {
return {}
}
return extensions.reduce(function (options, extension) {
const ext = extension.toUpperCase()
if (HIDEABLE_EXTENSIONS.indexOf(ext) > -1) {
options[`hide${ext}`] = true
} else {
throw new Error(`Invalid hideable extension: ${ext}`)
}
} else {
smtp = new SMTPServer({
incomingUser: user,
incomingPassword: password,
onAuth: authorizeUser,
onData: handleDataStream,
logger: false,
disabledCommands: !!(user && password)?['STARTTLS']:['AUTH']
});
}
return options
}, {})
}
// Setup temp folder for attachments
createTempFolder();
mailServer.port = port || defaultPort;
mailServer.host = host || defaultHost;
};
/**

@@ -291,22 +222,28 @@ * Start the mailServer

mailServer.listen = function(callback) {
mailServer.listen = function (callback) {
if (typeof callback !== 'function') callback = null
if (typeof callback !== 'function') callback = null;
// Listen on the specified port
smtp.listen(mailServer.port, mailServer.host, function(err) {
mailServer.smtp.listen(mailServer.port, mailServer.host, function (err) {
if (err) {
if (callback) {
callback(err);
callback(err)
} else {
throw err;
throw err
}
}
if (callback) callback();
if (callback) callback()
logger.info('MailDev SMTP Server running at %s:%s', mailServer.host, mailServer.port);
});
};
logger.info('MailDev SMTP Server running at %s:%s', mailServer.host, mailServer.port)
})
}
mailServer.onSmtpError = function (e) {
logger.info(
`Could not start mail server on ${e.address}:${e.port}\n` +
`Port already in use or insufficient rights to bind port`
)
process.emit('SIGTERM')
}

@@ -317,9 +254,8 @@ /**

mailServer.end = function(callback) {
var method = legacy ? 'end' : 'close';
smtp[method](callback);
outgoing.close();
};
mailServer.close = function (callback) {
mailServer.emit('close')
mailServer.smtp.close(callback)
outgoing.close()
}
/**

@@ -331,8 +267,7 @@ * Extend Event Emitter methods

mailServer.on = eventEmitter.on.bind(eventEmitter);
mailServer.emit = eventEmitter.emit.bind(eventEmitter);
mailServer.removeListener = eventEmitter.removeListener.bind(eventEmitter);
mailServer.removeAllListeners = eventEmitter.removeAllListeners.bind(eventEmitter);
mailServer.on = eventEmitter.on.bind(eventEmitter)
mailServer.emit = eventEmitter.emit.bind(eventEmitter)
mailServer.removeListener = eventEmitter.removeListener.bind(eventEmitter)
mailServer.removeAllListeners = eventEmitter.removeAllListeners.bind(eventEmitter)
/**

@@ -342,14 +277,13 @@ * Get an email by id

mailServer.getEmail = function(id, done){
mailServer.getEmail = function (id, done) {
var email = store.filter(function (element) {
return element.id === id
})[0]
var email = store.filter(function(element){
return element.id === id;
})[0];
if (email) {
done(null, email);
done(null, email)
} else {
done(new Error('Email was not found'));
done(new Error('Email was not found'))
}
};
}

@@ -360,11 +294,10 @@ /**

mailServer.getRawEmail = function(id, done){
mailServer.getRawEmail = function (id, done) {
mailServer.getEmail(id, function (err, email) {
if (err) return done(err)
mailServer.getEmail(id, function(err, email){
if (err) return done(err);
done(null, fs.createReadStream(path.join(tempDir, id + '.eml')))
})
}
done(null, fs.createReadStream( path.join(tempDir, id + '.eml') ) );
});
};
/**

@@ -374,39 +307,36 @@ * Returns the html of a given email

mailServer.getEmailHTML = function(id, baseUrl, done) {
mailServer.getEmailHTML = function (id, baseUrl, done) {
if (!done && typeof baseUrl === 'function') {
done = baseUrl;
baseUrl = null;
done = baseUrl
baseUrl = null
}
if (baseUrl)
baseUrl = '//' + baseUrl;
if (baseUrl) { baseUrl = '//' + baseUrl }
mailServer.getEmail(id, function(err, email) {
if (err) return done(err);
mailServer.getEmail(id, function (err, email) {
if (err) return done(err)
var html = email.html;
var html = email.html
if (!email.attachments)
return done(null, html);
if (!email.attachments) { return done(null, html) }
var embeddedAttachments = email.attachments.filter(function(attachment) {
return attachment.contentId;
});
var embeddedAttachments = email.attachments.filter(function (attachment) {
return attachment.contentId
})
var getFileUrl = function(id, baseUrl, filename) {
return (baseUrl || '') + '/email/' + id + '/attachment/' + filename;
};
var getFileUrl = function (id, baseUrl, filename) {
return (baseUrl || '') + '/email/' + id + '/attachment/' + filename
}
if (embeddedAttachments.length) {
embeddedAttachments.forEach(function(attachment) {
var regex = new RegExp('src=("|\')cid:' + attachment.contentId + '("|\')', 'g');
var replacement = 'src="' + getFileUrl(id, baseUrl, attachment.generatedFileName) + '"';
html = html.replace(regex, replacement);
});
embeddedAttachments.forEach(function (attachment) {
var regex = new RegExp('src=("|\')cid:' + attachment.contentId + '("|\')', 'g')
var replacement = 'src="' + getFileUrl(id, baseUrl, attachment.generatedFileName) + '"'
html = html.replace(regex, replacement)
})
}
done(null, html);
});
};
done(null, html)
})
}

@@ -417,7 +347,6 @@ /**

mailServer.getAllEmail = function(done){
done(null, store);
};
mailServer.getAllEmail = function (done) {
done(null, store)
}
/**

@@ -427,38 +356,37 @@ * Delete an email by id

mailServer.deleteEmail = function(id, done){
var email = null;
var emailIndex = null;
mailServer.deleteEmail = function (id, done) {
var email = null
var emailIndex = null
store.forEach(function(element, index){
if (element.id === id){
email = element;
emailIndex = index;
store.forEach(function (element, index) {
if (element.id === id) {
email = element
emailIndex = index
}
});
})
if (emailIndex === null){
return done(new Error('Email not found'));
if (emailIndex === null) {
return done(new Error('Email not found'))
}
//delete raw email
fs.unlink( path.join(tempDir, id + '.eml'), function (err) {
// delete raw email
fs.unlink(path.join(tempDir, id + '.eml'), function (err) {
if (err) {
logger.error(err);
logger.error(err)
} else {
eventEmitter.emit('delete', {id:id, index:emailIndex});
eventEmitter.emit('delete', {id: id, index: emailIndex})
}
});
})
if (email.attachments){
deleteAttachments(email.attachments);
if (email.attachments) {
deleteAttachments(email.attachments)
}
logger.warn('Deleting email - %s', email.subject);
logger.warn('Deleting email - %s', email.subject)
store.splice(emailIndex, 1);
store.splice(emailIndex, 1)
done(null, true);
};
done(null, true)
}
/**

@@ -468,14 +396,12 @@ * Delete all emails in the store

mailServer.deleteAllEmail = function(done){
mailServer.deleteAllEmail = function (done) {
logger.warn('Deleting all email')
logger.warn('Deleting all email');
clearTempFolder()
store.length = 0
eventEmitter.emit('delete', {id: 'all'})
clearTempFolder();
store.length = 0;
eventEmitter.emit('delete', {id:'all'});
done(null, true)
}
done(null, true);
};
/**

@@ -485,41 +411,37 @@ * Returns the content type and a readable stream of the file

mailServer.getEmailAttachment = function(id, filename, done){
mailServer.getEmailAttachment = function (id, filename, done) {
mailServer.getEmail(id, function (err, email) {
if (err) return done(err)
mailServer.getEmail(id, function(err, email){
if (err) return done(err);
if (!email.attachments || !email.attachments.length) {
return done(new Error('Email has no attachments'));
return done(new Error('Email has no attachments'))
}
var match = email.attachments.filter(function(attachment){
return attachment.generatedFileName === filename;
})[0];
var match = email.attachments.filter(function (attachment) {
return attachment.generatedFileName === filename
})[0]
if (!match) {
return done(new Error('Attachment not found'));
return done(new Error('Attachment not found'))
}
done(null, match.contentType, fs.createReadStream( path.join(tempDir, match.contentId) ) );
done(null, match.contentType, fs.createReadStream(path.join(tempDir, match.contentId)))
})
}
});
};
/**
* Setup outgoing
*/
mailServer.setupOutgoing = function(host, port, user, pass, secure) {
outgoing.setup(host, port, user, pass, secure);
};
mailServer.setupOutgoing = function (host, port, user, pass, secure) {
outgoing.setup(host, port, user, pass, secure)
}
mailServer.isOutgoingEnabled = function() {
return outgoing.isEnabled();
};
mailServer.isOutgoingEnabled = function () {
return outgoing.isEnabled()
}
mailServer.getOutgoingHost = function() {
return outgoing.getOutgoingHost();
};
mailServer.getOutgoingHost = function () {
return outgoing.getOutgoingHost()
}
/**

@@ -529,5 +451,5 @@ * Set Auto Relay Mode, automatic send email to recipient

mailServer.setAutoRelayMode = function(enabled, rules) {
outgoing.setAutoRelayMode(enabled, rules);
};
mailServer.setAutoRelayMode = function (enabled, rules) {
outgoing.setAutoRelayMode(enabled, rules)
}

@@ -538,11 +460,9 @@ /**

mailServer.relayMail = function(idOrMailObject, isAutoRelay, done) {
mailServer.relayMail = function (idOrMailObject, isAutoRelay, done) {
if (!outgoing.isEnabled()) { return done(new Error('Outgoing mail not configured')) }
if (!outgoing.isEnabled())
return done(new Error('Outgoing mail not configured'));
// isAutoRelay is an option argument
if (typeof isAutoRelay === 'function') {
done = isAutoRelay;
isAutoRelay = false;
done = isAutoRelay
isAutoRelay = false
}

@@ -552,20 +472,20 @@

if (typeof idOrMailObject === 'string') {
mailServer.getEmail(idOrMailObject, function(err, email) {
if (err) return done(err);
mailServer.relayMail(email, done);
});
return;
mailServer.getEmail(idOrMailObject, function (err, email) {
if (err) return done(err)
mailServer.relayMail(email, done)
})
return
}
var mail = idOrMailObject;
var mail = idOrMailObject
mailServer.getRawEmail(mail.id, function(err, rawEmailStream) {
mailServer.getRawEmail(mail.id, function (err, rawEmailStream) {
if (err) {
logger.error('Mail Stream Error: ', err);
return done(err);
logger.error('Mail Stream Error: ', err)
return done(err)
}
outgoing.relayMail(mail, rawEmailStream, isAutoRelay, done);
});
};
outgoing.relayMail(mail, rawEmailStream, isAutoRelay, done)
})
}

@@ -575,10 +495,10 @@ /**

*/
mailServer.getEmailEml = function(id, done) {
mailServer.getEmail(id, function(err, email){
if (err) return done(err);
mailServer.getEmailEml = function (id, done) {
mailServer.getEmail(id, function (err, email) {
if (err) return done(err)
var filename = email.id + '.eml';
var filename = email.id + '.eml'
done(null, 'message/rfc822', filename, fs.createReadStream(path.join(tempDir, id + '.eml')));
});
};
done(null, 'message/rfc822', filename, fs.createReadStream(path.join(tempDir, id + '.eml')))
})
}

@@ -0,1 +1,2 @@

'use strict'

@@ -6,17 +7,17 @@ /**

var SMTPConnection = require('smtp-connection');
var wildstring = require('wildstring');
var async = require('async');
var fs = require('fs');
var logger = require('./logger');
const SMTPConnection = require('smtp-connection')
const wildstring = require('wildstring')
const async = require('async')
const fs = require('fs')
const logger = require('./logger')
wildstring.caseSensitive = false;
wildstring.caseSensitive = false
var config = {
autoRelay: false
};
}
// The SMTP connection client
var client;
var emailQueue;
let client
let emailQueue

@@ -27,25 +28,24 @@ /**

var outgoing = module.exports = {};
const outgoing = module.exports = {}
outgoing.setup = function(host, port, user, pass, secure) {
outgoing.setup = function (host, port, user, pass, secure) {
// defaults
port = port || (secure ? 465 : 25)
host = host || 'localhost'
secure = secure || false
//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
config.host = host;
config.port = port;
config.user = user;
config.pass = pass;
config.secure = secure;
this._createClient()
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);
};
emailQueue = async.queue(relayWorker, 1)
}
outgoing._createClient = function() {
outgoing._createClient = function () {
try {

@@ -56,8 +56,8 @@ client = new SMTPConnection({

secure: config.secure,
auth: (config.pass && config.user) ?
{ user: config.user, pass: config.pass } :
false,
auth: (config.pass && config.user)
? { user: config.user, pass: config.pass }
: false,
tls: { rejectUnauthorized: false },
debug: true
});
})

@@ -71,41 +71,38 @@ logger.info(

config.secure ? 'yes' : 'no'
);
} catch (err){
logger.error('Error during configuration of SMTP Server for outgoing email', err);
)
} catch (err) {
logger.error('Error during configuration of SMTP Server for outgoing email', err)
}
};
}
outgoing.close = function() {
if (this.isEnabled())
client.close();
};
outgoing.close = function () {
if (this.isEnabled()) { client.close() }
}
outgoing.isEnabled = function() {
return !!client;
};
outgoing.isEnabled = function () {
return !!client
}
outgoing.getOutgoingHost = function() {
return config.host;
};
outgoing.getOutgoingHost = function () {
return config.host
}
outgoing.isAutoRelayEnabled = function() {
return config.autoRelay;
};
outgoing.isAutoRelayEnabled = function () {
return config.autoRelay
}
outgoing.setAutoRelayMode = function(enabled, rules) {
if (!client){
config.autoRelay = false;
logger.info('Outgoing mail not configured - Auto relay mode ignored');
return;
outgoing.setAutoRelayMode = function (enabled, rules) {
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'));
rules = JSON.parse(fs.readFileSync(rules, 'utf8'))
} catch (err) {
logger.error('Error reading config file at ' + rules);
throw err;
logger.error('Error reading config file at ' + rules)
throw err
}

@@ -115,7 +112,7 @@ }

if (Array.isArray(rules)) {
config.autoRelayRules = rules;
config.autoRelayRules = rules
}
}
config.autoRelay = enabled;
config.autoRelay = enabled

@@ -126,8 +123,7 @@ if (config.autoRelay) {

JSON.stringify(config.autoRelayRules)
);
)
}
};
}
outgoing.relayMail = function(emailObject, emailStream, isAutoRelay, callback) {
outgoing.relayMail = function (emailObject, emailStream, isAutoRelay, callback) {
emailQueue.push({

@@ -138,27 +134,24 @@ emailObject: emailObject,

callback: callback
});
};
})
}
function relayMail (emailObject, emailStream, isAutoRelay, done) {
if (!client) { return done(new Error('Outgoing mail not configured')) }
function relayMail(emailObject, emailStream, isAutoRelay, done) {
if (!client)
return done(new Error('Outgoing mail not configured'));
// Fallback to the object if the address key isn't defined
var getAddress = function(addressObj) {
return typeof addressObj.address !== 'undefined' ? addressObj.address : addressObj;
};
const getAddress = function (addressObj) {
return typeof addressObj.address !== 'undefined' ? addressObj.address : addressObj
}
var mailSendCallback = function(err) {
const mailSendCallback = function (err) {
if (err) {
logger.error('Outgoing client login error: ', err);
return done(err);
logger.error('Outgoing client login error: ', err)
return done(err)
}
var recipients = emailObject.envelope.to.map(getAddress);
var sender = getAddress(emailObject.envelope.from);
let recipients = emailObject.envelope.to.map(getAddress)
const sender = getAddress(emailObject.envelope.from)
if (isAutoRelay && config.autoRelayRules) {
recipients = getAutoRelayableRecipients(recipients);
recipients = getAutoRelayableRecipients(recipients)
}

@@ -168,3 +161,3 @@

if (recipients.length === 0) {
return done('Email had no recipients');
return done('Email had no recipients')
}

@@ -176,64 +169,61 @@

}, emailStream, function (err) {
client.quit()
outgoing._createClient()
client.quit();
outgoing._createClient();
if (err) {
logger.error('Mail Delivery Error: ', err);
return done(err);
logger.error('Mail Delivery Error: ', err)
return done(err)
}
logger.log('Mail Delivered: ', emailObject.subject);
logger.log('Mail Delivered: ', emailObject.subject)
return done();
});
};
return done()
})
}
var mailConnectCallback = function(err) {
var mailConnectCallback = function (err) {
if (err) {
logger.error('Outgoing connection error: ', err);
return done(err);
logger.error('Outgoing connection error: ', err)
return done(err)
}
if (client.options.auth) {
client.login(client.options.auth, mailSendCallback);
client.login(client.options.auth, mailSendCallback)
} else {
mailSendCallback(err);
mailSendCallback(err)
}
};
}
client.connect(mailConnectCallback);
client.connect(mailConnectCallback)
}
function relayWorker(task, callback) {
function relayWorker (task, callback) {
const relayCallback = function (err, result) {
task.callback && task.callback(err, result)
callback(err, result)
}
var relayCallback = function(err, result){
task.callback && task.callback(err, result);
callback(err, result);
};
relayMail(task.emailObject, task.emailStream, task.isAutoRelay, relayCallback);
relayMail(task.emailObject, task.emailStream, task.isAutoRelay, relayCallback)
}
function getAutoRelayableRecipients(recipients) {
return recipients.filter(function(email) {
return validateAutoRelayRules(email);
});
function getAutoRelayableRecipients (recipients) {
return recipients.filter(function (email) {
return validateAutoRelayRules(email)
})
}
function validateAutoRelayRules(email) {
function validateAutoRelayRules (email) {
if (!config.autoRelayRules) {
return true;
return true
}
return config.autoRelayRules.reduce(function(result, rule) {
var toMatch = rule.allow || rule.deny || '';
var isMatch = wildstring.match(toMatch, email);
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 ? true : false) :
result;
}, true);
return isMatch
? (!!rule.allow)
: result
}, true)
}

@@ -0,1 +1,2 @@

'use strict'

@@ -5,144 +6,123 @@ /**

*/
var express = require('express');
var pkg = require('../package.json');
const express = require('express')
const pkg = require('../package.json')
var emailRegexp = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
const emailRegexp = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
module.exports = function(app, mailserver, basePathname) {
var router = express.Router();
module.exports = function (app, mailserver, basePathname) {
var router = express.Router()
// Get all emails
router.get('/email', function(req, res){
router.get('/email', function (req, res) {
mailserver.getAllEmail(function (err, emailList) {
if (err) return res.status(404).json([])
mailserver.getAllEmail(function(err, emailList){
if (err) return res.status(404).json([]);
res.json(emailList)
})
})
res.json(emailList);
});
});
// Get single email
router.get('/email/:id', function(req, res){
router.get('/email/:id', function (req, res) {
mailserver.getEmail(req.params.id, function (err, email) {
if (err) return res.status(404).json({ error: err.message })
mailserver.getEmail( req.params.id, function(err, email){
if (err) return res.status(404).json({ error: err.message });
// Mark the email as 'read'
email.read = true;
email.read = true
res.json(email);
});
res.json(email)
})
})
});
// Delete all emails
router.delete('/email/all', function(req, res){
router.delete('/email/all', function (req, res) {
mailserver.deleteAllEmail(function (err) {
if (err) return res.status(500).json({ error: err.message })
mailserver.deleteAllEmail(function(err){
if (err) return res.status(500).json({ error: err.message });
res.json(true)
})
})
res.json(true);
});
});
// Delete email by id
router.delete('/email/:id', function(req, res){
router.delete('/email/:id', function (req, res) {
mailserver.deleteEmail(req.params.id, function (err) {
if (err) return res.status(500).json({ error: err.message })
mailserver.deleteEmail(req.params.id, function(err){
if (err) return res.status(500).json({ error: err.message });
res.json(true)
})
})
res.json(true);
});
});
// Get Email HTML
router.get('/email/:id/html', function(req, res){
router.get('/email/:id/html', function (req, res) {
// Use the headers over hostname to include any port
var baseUrl = req.headers.host + (req.baseUrl || '');
var baseUrl = req.headers.host + (req.baseUrl || '')
mailserver.getEmailHTML(req.params.id, baseUrl, function(err, html){
if (err) return res.status(404).json({ error: err.message });
mailserver.getEmailHTML(req.params.id, baseUrl, function (err, html) {
if (err) return res.status(404).json({ error: err.message })
res.send(html);
});
res.send(html)
})
})
});
// Serve Attachments
router.get('/email/:id/attachment/:filename', function(req, res){
router.get('/email/:id/attachment/:filename', function (req, res) {
mailserver.getEmailAttachment(req.params.id, req.params.filename, function (err, contentType, readStream) {
if (err) return res.status(404).json('File not found')
mailserver.getEmailAttachment(req.params.id, req.params.filename, function(err, contentType, readStream){
if (err) return res.status(404).json('File not found');
res.contentType(contentType)
readStream.pipe(res)
})
})
res.contentType(contentType);
readStream.pipe(res);
});
});
// Serve email.eml
router.get('/email/:id/download', function(req, res){
router.get('/email/:id/download', function (req, res) {
mailserver.getEmailEml(req.params.id, function (err, contentType, filename, readStream) {
if (err) return res.status(404).json('File not found')
mailserver.getEmailEml(req.params.id, function(err, contentType, filename, readStream){
if (err) return res.status(404).json('File not found');
res.setHeader('Content-disposition', 'attachment; filename=' + filename)
res.contentType(contentType)
readStream.pipe(res)
})
})
res.setHeader('Content-disposition', 'attachment; filename=' + filename);
res.contentType(contentType);
readStream.pipe(res);
});
});
// Get email source from .eml file
router.get('/email/:id/source', function(req, res){
router.get('/email/:id/source', function (req, res) {
mailserver.getRawEmail(req.params.id, function (err, readStream) {
if (err) return res.status(404).json('File not found')
readStream.pipe(res)
})
})
mailserver.getRawEmail( req.params.id, function(err, readStream){
if (err) return res.status(404).json('File not found');
readStream.pipe(res);
});
});
// Get any config settings for display
router.get('/config', function(req, res){
router.get('/config', function (req, res) {
res.json({
version: pkg.version,
version: pkg.version,
smtpPort: mailserver.port,
isOutgoingEnabled: mailserver.isOutgoingEnabled(),
outgoingHost: mailserver.getOutgoingHost()
});
})
})
});
// Relay the email
router.post('/email/:id/relay/:relayTo?', function(req, res){
mailserver.getEmail(req.params.id, function(err, email){
if (err) return res.status(404).json({ error: err.message });
router.post('/email/:id/relay/:relayTo?', function (req, res) {
mailserver.getEmail(req.params.id, function (err, email) {
if (err) return res.status(404).json({ error: err.message })
if (req.params.relayTo) {
if (emailRegexp.test(req.params.relayTo)) {
email.to = [{address: req.params.relayTo}];
email.envelope.to = [ { address: req.params.relayTo, args: false }]
email.to = [{address: req.params.relayTo}]
email.envelope.to = [{ address: req.params.relayTo, args: false }]
} else {
return res.status(400).json({ error: 'Incorrect email address provided :' + req.params.relayTo })
}
else {
return res.status(400).json({ error: 'Incorrect email address provided :' + req.params.relayTo });
}
}
mailserver.relayMail(email, function(err) {
if (err) return res.status(500).json({ error: err.message });
mailserver.relayMail(email, function (err) {
if (err) return res.status(500).json({ error: err.message })
res.json(true);
});
res.json(true)
})
})
})
});
});
app.use(basePathname, router);
};
app.use(basePathname, router)
}

@@ -0,1 +1,2 @@

'use strict'

@@ -6,43 +7,65 @@ /**

var express = require('express');
var app = express();
var server = require('http').createServer(app);
var io = require('socket.io')();
var routes = require('./routes');
var auth = require('./auth');
var logger = require('./logger');
var path = require('path');
const express = require('express')
const http = require('http')
const io = require('socket.io')()
const routes = require('./routes')
const auth = require('./auth')
const logger = require('./logger')
const path = require('path')
const web = module.exports = {}
/**
* WebSockets
* Keep record of all connections to close them on shutdown
*/
const connections = {}
function emitNewMail(socket) {
return function(email) {
socket.emit('newMail', email);
};
function handleConnection (socket) {
const key = `${socket.remoteAddress}:${socket.remotePort}`
connections[key] = socket
socket.on('close', function () {
delete connections[key]
})
}
function emitDeleteMail(socket) {
return function(email) {
socket.emit('deleteMail', email);
};
function closeConnections () {
for (let key in connections) {
connections[key].destroy()
}
}
function webSocketConnection(mailserver) {
/**
* WebSockets
*/
return function onConnection(socket) {
function emitNewMail (socket) {
return function (email) {
socket.emit('newMail', email)
}
}
// When a new email arrives, the 'new' event will be emitted
mailserver.on('new', emitNewMail(socket));
mailserver.on('delete', emitDeleteMail(socket));
function emitDeleteMail (socket) {
return function (email) {
socket.emit('deleteMail', email)
}
}
socket.on('disconnect', function(){
mailserver.removeListener('new', emitNewMail(socket));
mailserver.removeListener('delete', emitDeleteMail(socket));
});
function webSocketConnection (mailserver) {
return function onConnection (socket) {
const newHandlers = emitNewMail(socket)
const deleteHandler = emitDeleteMail(socket)
mailserver.on('new', newHandlers)
mailserver.on('delete', deleteHandler)
};
function removeListeners () {
mailserver.removeListener('new', newHandlers)
mailserver.removeListener('delete', deleteHandler)
}
socket.on('disconnect', removeListeners)
}
}
web.server = null
/**

@@ -52,32 +75,44 @@ * Start the web server

module.exports.start = function(port, host, mailserver, user, password, basePathname) {
web.start = function (port, host, mailserver, user, password, basePathname) {
const app = express()
web.server = http.createServer(app)
if (user && password) {
app.use(auth(user, password));
app.use(auth(user, password))
}
if (basePathname) {
io.path(basePathname + '/socket.io');
io.path(basePathname + '/socket.io')
} else {
basePathname = '/';
basePathname = '/'
}
app.use(basePathname, express.static(path.join(__dirname, '../app')));
app.use(basePathname, express.static(path.join(__dirname, '../app')))
routes(app, mailserver, basePathname);
routes(app, mailserver, basePathname)
io.attach(server);
io.on('connection', webSocketConnection(mailserver));
io.attach(web.server)
io.on('connection', webSocketConnection(mailserver))
port = port || 1080;
host = host || '0.0.0.0';
port = port || 1080
host = host || '0.0.0.0'
server.listen(port, host);
web.server.listen(port, host)
logger.info('MailDev app running at %s:%s', host, port);
web.server.on('connection', handleConnection)
};
web.server.on('error', function (err) {
logger.info('Could not start web server on ' + err.address + ':' + err.port + '\nPort already in use or insufficient rights to bind port')
process.emit('SIGTERM')
})
module.exports.close = function(callback) {
server.close(callback);
};
logger.info('MailDev webapp running at http://%s:%s', host, port)
}
web.close = function (callback) {
if (!web.server) {
return callback()
}
closeConnections()
io.close(callback)
}
{
"name": "maildev",
"description": "SMTP Server and Web Interface for reading and testing emails during development",
"version": "0.14.0",
"version": "1.0.0-rc1",
"keywords": [

@@ -9,3 +9,7 @@ "email",

"mail",
"mailcatcher"
"maildev",
"mailcatcher",
"testing",
"development",
"smtp"
],

@@ -25,3 +29,7 @@ "author": "Dan Farrelly",

"scripts": {
"test": "./node_modules/.bin/istanbul cover _mocha"
"test": "standard && istanbul cover _mocha",
"lint": "standard",
"dev": "node ./scripts/dev.js & npm run css-watch",
"css": "node-sass --output-style compressed -o app/styles assets/styles/style.scss",
"css-watch": "node-sass -wr --output-style compressed -o app/styles assets/styles/style.scss"
},

@@ -37,27 +45,28 @@ "main": "./index.js",

"express": "4.13.4",
"mailparser": "0.5.3",
"mailparser": "0.6.2",
"open": "0.0.5",
"simplesmtp": "0.3.35",
"smtp-connection": "2.3.1",
"smtp-server": "1.4.0",
"socket.io": "1.4.5",
"smtp-server": "1.16.1",
"socket.io": "1.7.3",
"wildstring": "1.0.8"
},
"devDependencies": {
"grunt": "0.4.5",
"grunt-concurrent": "2.2.1",
"grunt-contrib-jshint": "1.0.0",
"grunt-contrib-watch": "1.0.0",
"grunt-nodemon": "0.4.1",
"grunt-sass": "1.1.0",
"expect": "1.20.2",
"got": "^6.7.1",
"http-proxy-middleware": "0.12.0",
"istanbul": "0.4.4",
"matchdep": "1.0.1",
"mocha": "2.2.5",
"istanbul": "^0.4.5",
"mocha": "^3.3.0",
"node-sass": "^4.5.2",
"nodemailer": "2.3.0",
"request": "2.69.0"
"nodemon": "^1.11.0",
"standard": "^10.0.2"
},
"engines": {
"node": ">=0.10.0"
"node": ">=4.0.0"
},
"standard": {
"ignore": [
"app/components/"
]
}
}

@@ -6,6 +6,7 @@ # MailDev

[![NPM Version](https://img.shields.io/npm/v/maildev.svg)](https://www.npmjs.com/package/maildev)
[![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com)
**MailDev** is a simple way to test your project's generated emails during development with an easy to use web interface that runs on your machine built on top of [Node.js](http://www.nodejs.org).
![MailDev Screenshot](https://dl.dropboxusercontent.com/u/50627698/maildev/screenshot-2015-03-29.png)
![MailDev Screenshot](https://www.dropbox.com/s/xo7nk8zmtno53om/maildev-04-12-13.png?dl=1)

@@ -30,21 +31,26 @@ ## Install & Run

-h, --help output usage information
-V, --version output the version number
-s, --smtp <port> SMTP port to catch emails [1025]
-w, --web <port> Port to run the Web GUI [1080]
--ip <ip address> IP Address to bind services to [0.0.0.0]
--outgoing-host <host> SMTP host for outgoing emails
--outgoing-port <port> SMTP port for outgoing emails
--outgoing-user <user> SMTP user for outgoing emails
--outgoing-pass <pass> SMTP password for outgoing emails
--outgoing-secure Use SMTP SSL for outgoing emails
--auto-relay Use auto relay mode
--auto-relay-rules <file> Filter rules for auto relay mode
--incoming-user <user> SMTP user for incoming emails
--incoming-pass <pass> SMTP password for incoming emails
--web-ip <ip address> IP Address to bind HTTP service to, defaults to --ip
--web-user <user> HTTP basic auth username
--web-pass <pass> HTTP basic auth password
-o, --open Open the Web GUI after startup
-h, --help output usage information
-V, --version output the version number
-s, --smtp <port> SMTP port to catch emails [1025]
-w, --web <port> Port to run the Web GUI [1080]
--ip <ip address> IP Address to bind SMTP service to
--outgoing-host <host> SMTP host for outgoing emails
--outgoing-port <port> SMTP port for outgoing emails
--outgoing-user <user> SMTP user for outgoing emails
--outgoing-pass <password> SMTP password for outgoing emails
--outgoing-secure Use SMTP SSL for outgoing emails
--auto-relay Use auto-relay mode
--auto-relay-rules <file> Filter rules for auto relay mode
--incoming-user <user> SMTP user for incoming emails
--incoming-pass <pass> SMTP password for incoming emails
--web-ip <ip address> IP Address to bind HTTP service to, defaults to --ip
--web-user <user> HTTP user for GUI
--web-pass <password> HTTP password for GUI
--base-pathname <path> base path for URLs
--disable-web Disable the use of the web interface. Useful for unit testing
--hide-extensions <extensions> Comma separated list of SMTP extensions to NOT advertise
(STARTTLS, SMTPUTF8, PIPELINING, 8BITMIME)
-o, --open Open the Web GUI after startup
-v, --verbose
--silent

@@ -57,11 +63,11 @@ ## API

```javascript
var MailDev = require('maildev');
const MailDev = require('maildev')
var maildev = new MailDev();
const maildev = new MailDev()
maildev.listen();
maildev.listen()
maildev.on('new', function(email){
maildev.on('new', function (email) {
// We got a new email!
});
})
```

@@ -124,7 +130,7 @@

```javascript
var transport = nodemailer.createTransport({
const transport = nodemailer.createTransport({
port: 1025,
ignoreTLS: true,
// other settings...
});
})
```

@@ -135,6 +141,6 @@

```javascript
var transport = nodemailer.createTransport("SMTP", {
const transport = nodemailer.createTransport('SMTP', {
port: 1025,
// other settings...
});
})
```

@@ -175,14 +181,16 @@

# grunt-cli is needed by grunt; ignore this if already installed
npm install -g grunt-cli
npm install
grunt dev
npm run dev
The "dev" task will run MailDev using nodemon and restart automatically when
changes are detected. On `*.scss` file save, the css will also be recompiled.
Using `test/send.js`, a few test emails will be sent every time the application
restarts.
The `grunt dev` task will run the project using nodemon and restart automatically when changes are detected. SASS files will be compiled automatically on save also. To trigger some emails for testing run `node test/send.js` in a separate shell. Please run jshint to your lint code before submitting a pull request; run `grunt jshint`.
The project uses the [JavaScript Standard coding style](https://standardjs.com).
To lint your code before submitting your PR, run `npm run lint`.
To run the test suite:
$ npm run test
$ npm test

@@ -193,3 +201,11 @@ ## [Changelog](https://github.com/djfarrelly/MailDev/releases)

**MailDev** is built on using great open source projects including [Express](http://expressjs.com), [AngularJS](http://angularjs.org/), [Font Awesome](http://fontawesome.io/) and two great projects from [Andris Reinman](https://github.com/andris9): [Simple SMTP](https://github.com/andris9/simplesmtp) and [Mailparser](https://github.com/andris9/mailparser). Many thanks to Andris as his projects are the backbone of this app and to [MailCatcher](http://mailcatcher.me/) for the inspiration.
**MailDev** is built on using great open source projects including
[Express](http://expressjs.com),
[AngularJS](http://angularjs.org/),
[Font Awesome](http://fontawesome.io/) and two great projects from
[Andris Reinman](https://github.com/andris9):
[smtp-server](https://github.com/nodemailer/smtp-server)
and [mailparser](https://github.com/nodemailer/mailparser).
Many thanks to Andris as his projects are the backbone of this app and to
[MailCatcher](http://mailcatcher.me/) for the inspiration.

@@ -196,0 +212,0 @@ Additionally, thanks to all the awesome [contributors](https://github.com/djfarrelly/MailDev/graphs/contributors)

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc