Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#Mailin [![Build Status](https://travis-ci.org/Flolagale/mailin.svg?branch=master)](https://travis-ci.org/Flolagale/mailin)
#Mailin [![Build Status](https://travis-ci.org/Flolagale/mailin.svg?branch=master)](https://travis-ci.org/MailHops/mailin.svg?branch=mailhops)

__Artisanal inbound emails for every web app__
<img align="right" src="postman.jpg"/>
Expand Down
10 changes: 9 additions & 1 deletion cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ program.version(pkg.version)
.option('-l, --log-file [file path]', "The log file path. Default to '/var/log/mailin.log'.")
.option('--disable-dkim', 'Disable dkim checking. The dkim field in the webhook payload will be set to false.')
.option('--disable-spf', 'Disable spf checking. The spf field in the webhook payload will be set to false.')
.option('--disable-webhook', 'Disable Webhook.')
.option('--disable-spam-score', 'Disable spam score computation. The spamScore field in the webhook payload will be set to 0.0.')
.option('--mailhops-api-key', 'Get the mail route from MailHops API. https://mailhops.com')
.option('--verbose', 'Set the logging level to verbose.')
.option('--debug', 'Printout debug info such as the smtp commands.')
.option('--profile', 'Enable basic memory usage profiling.')
Expand All @@ -55,6 +57,8 @@ mailin.start({
disableDkim: program.disableDkim,
disableSpf: program.disableSpf,
disableSpamScore: program.disableSpamScore,
disableWebhook: program.disableWebhook,
mailhopsApiKey: program.mailhopsApiKey,
verbose: program.verbose,
debug: program.debug,
profile: program.profile,
Expand All @@ -63,11 +67,15 @@ mailin.start({
}, function (err) {
if (err) process.exit(1);

logger.info('Webhook url: ' + mailin.configuration.webhook);
if(mailin.configuration.disableWebhook)
logger.info('Webhook is disabled');
else
logger.info('Webhook url: ' + mailin.configuration.webhook);

if (mailin.configuration.logFile) logger.info('Log file: ' + mailin.configuration.logFile);

if (mailin.configuration.disableDkim) logger.info('Dkim checking is disabled');
if (mailin.configuration.disableSpf) logger.info('Spf checking is disabled');
if (mailin.configuration.disableSpamScore) logger.info('Spam score computation is disabled');
if (mailin.configuration.mailhopsApiKey) logger.info('Using MailHops');
});
18 changes: 18 additions & 0 deletions lib/mailUtilities.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ var logger = require('./logger');
var path = require('path');
var Spamc = require('spamc');
var spamc = new Spamc();
var MailHops = require("mailhops");

/* Verify Python availability. */
var isPythonAvailable = shell.which('python');
Expand Down Expand Up @@ -79,5 +80,22 @@ module.exports = {
if (err) return callback(new Error('Unable to compute spam score.'));
callback(null, result.spamScore);
});
},

/* @param rawEmail is the full raw mime email as a string. */
getMailHops: function (rawEmail, mailhopsApiKey, callback) {
var mailhops = new MailHops({
api_key: mailhopsApiKey,
app_name: "Node Mailin"
});
var headerText = rawEmail.substring(0,rawEmail.indexOf('\n\n'));
var ips = mailhops.getIPsFromHeader(headerText);
if(!ips)
return callback(new Error('Unable to get MailHops.'));
mailhops.lookup(ips,function(err, result, body){
if (err) logger.error(err);
if (err) return callback(new Error('Unable to get MailHops.'));
callback(null, body);
});
}
};
26 changes: 22 additions & 4 deletions lib/mailin.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ var Mailin = function () {
disableDkim: false,
disableSpf: false,
disableSpamScore: false,
mailhopsApiKey: false,
verbose: false,
debug: false,
logLevel: 'info',
Expand Down Expand Up @@ -209,16 +210,18 @@ Mailin.prototype.start = function (options, callback) {
validateDkim(connection, rawEmail),
validateSpf(connection),
computeSpamScore(connection, rawEmail),
getMailHops(connection, rawEmail),
parseEmail(connection)
]);
})
.spread(function (rawEmail, isDkimValid, isSpfValid, spamScore, parsedEmail) {
.spread(function (rawEmail, isDkimValid, isSpfValid, spamScore, mailHops, parsedEmail) {
return Promise.all([
connection,
rawEmail,
isDkimValid,
isSpfValid,
spamScore,
mailHops,
parsedEmail,
detectLanguage(connection, parsedEmail.text)
]);
Expand Down Expand Up @@ -283,6 +286,19 @@ Mailin.prototype.start = function (options, callback) {
});
}

function getMailHops(connection, rawEmail) {
if (configuration.mailhopsApiKey===false) {
return Promise.resolve(false);
}

return mailUtilities.getMailHopsAsync(rawEmail, configuration.mailhopsApiKey)
.catch(function (err) {
logger.error(connection.id + ' Unable to get MailHops.');
logger.error(err);
return false;
});
}

function parseEmail(connection) {
return new Promise(function (resolve) {
logger.verbose(connection.id + ' Parsing email.');
Expand Down Expand Up @@ -335,12 +351,13 @@ Mailin.prototype.start = function (options, callback) {
return language;
}

function finalizeMessage(connection, rawEmail, isDkimValid, isSpfValid, spamScore, parsedEmail, language) {
function finalizeMessage(connection, rawEmail, isDkimValid, isSpfValid, spamScore, mailHops, parsedEmail, language) {

/* Finalize the parsed email object. */
parsedEmail.dkim = isDkimValid ? 'pass' : 'failed';
parsedEmail.spf = isSpfValid ? 'pass' : 'failed';
parsedEmail.spamScore = spamScore;
parsedEmail.mailHops = mailHops.response || null;
parsedEmail.language = language;

/* Make fields exist, even if empty. That will make
Expand All @@ -362,6 +379,9 @@ Mailin.prototype.start = function (options, callback) {

function postWebhook(connection, finalizedMessage) {
return new Promise(function (resolve) {

logger.debug(finalizedMessage);

if (configuration.disableWebhook) return resolve();

logger.info(connection.id + ' Sending request to webhook ' + configuration.webhook);
Expand All @@ -378,8 +398,6 @@ Mailin.prototype.start = function (options, callback) {
});
logger.profile('Convert attachments to strings');

logger.debug(finalizedMessage);

var req = request.post(configuration.webhook);
req.field('mailinMsg', JSON.stringify(finalizedMessage));

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"html-to-text": "^1.3.1",
"languagedetect": "^1.1.1",
"lodash": "^3.9.3",
"mailhops": "^2.0.0",
"mailparser": "^0.5.1",
"node-uuid": "^1.4.3",
"semver": "^5.0.1",
Expand Down
3 changes: 3 additions & 0 deletions test/fixtures/test-html-only.eml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ X-Mailer: Nodemailer 1.0
From: "Me" <[email protected]>
To: [email protected]
Content-Type: text/html
Received: from mta2.e.mozilla.org (mta2.e.mozilla.org [68.232.195.239])
(using TLSv1 with cipher ECDHE-RSA-AES256-SHA (256/256 bits))
; Fri, 2 Dec 2016 09:41:31 -0800 (PST)

<h1>Hello World</h1>
<p>This is a line that needs to be at least a little longer than 80 characters so that we can check the character wrapping functionality.</p>
Expand Down
7 changes: 5 additions & 2 deletions test/fixtures/test.eml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@ To: "First Receiver" <[email protected]>, [email protected]
Content-Type: multipart/mixed;
boundary="----mailcomposer-?=_1-1402581589619"
MIME-Version: 1.0

------mailcomposer-?=_1-1402581589619
Received: from mta2.e.mozilla.org (mta2.e.mozilla.org [68.232.195.239])
(using TLSv1 with cipher ECDHE-RSA-AES256-SHA (256/256 bits))
; Fri, 2 Dec 2016 09:41:31 -0800 (PST)

------mailcomposer-?=_1-1402581589619
Content-Type: multipart/alternative;
boundary="----mailcomposer-?=_2-1402581589620"

Expand Down
15 changes: 15 additions & 0 deletions test/mailUtilitiesSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,18 @@ describe('The mail signature verfier', function () {
});
});
});

describe('The mailhops verfier', function () {
it('should be able to verify the route',
function (done) {
var email = fs.readFileSync('./test/fixtures/test.eml').toString();
mailUtilities.getMailHops(email, "", function (err, result) {
if (err) console.log(err);
should.not.exist(err);

result.response.route.length.should.eql(2);
result.response.route[0].ip.should.eql('68.232.195.239');
done();
});
});
});
12 changes: 8 additions & 4 deletions test/mailinSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ before(function (done) {
// This checks the webhook; that's why the server must be already up and listening
mailin.start({
// verbose: true,
mailhopsApiKey: false,
smtpOptions: {
secure: false
}
Expand Down Expand Up @@ -75,13 +76,13 @@ describe('Mailin', function () {

mailin.on('message', function (connection, data) {
console.log("Event 'message' triggered.");
// console.log(data);
try {

data.attachments[0].content.toString().should.eql('my dummy attachment contents');

/* Delete the headers that include a timestamp. */
delete data.headers.received;
delete data.receivedDate;

data.should.eql({
html: '<b>Hello world!</b>',
Expand Down Expand Up @@ -127,14 +128,15 @@ describe('Mailin', function () {
}],
spf: 'failed',
spamScore: expectedSpamScore,
mailHops: null,
language: 'pidgin',
cc: [],
connection: connData
});

doing--;
} catch (e) {
done(e);
done(e);
}
});

Expand All @@ -160,6 +162,7 @@ describe('Mailin', function () {

/* Delete the headers that include a timestamp. */
delete mailinMsg.headers.received;
delete mailinMsg.receivedDate;

/* And the connection id, which is random. */
delete mailinMsg.data;
Expand Down Expand Up @@ -208,6 +211,7 @@ describe('Mailin', function () {
spf: 'failed',
spamScore: expectedSpamScore,
language: 'pidgin',
mailHops: null,
cc: [],
connection: connData
});
Expand All @@ -216,7 +220,7 @@ describe('Mailin', function () {

doing--;
} catch (e) {
done(e);
done(e);
}
});

Expand Down Expand Up @@ -259,7 +263,7 @@ describe('Mailin', function () {
this.timeout(10000);

mailin.on('message', function (connection, data) {
// console.log(data);

try {
data.text.should.eql('HELLO WORLD\nThis is a line that needs to be at least a little longer than 80 characters so\nthat we can check the character wrapping functionality.\n\nThis is a test of a link [https://github.com/Flolagale/mailin] .');
done();
Expand Down