0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-04-01 02:41:39 -05:00

Merge pull request #3915 from javorszky/iss1538b

Replaces sendmail with direct
This commit is contained in:
Hannah Wolfe 2014-09-11 20:47:18 +01:00
commit 07eaaae8bf
5 changed files with 172 additions and 119 deletions

View file

@ -104,12 +104,12 @@ function builtFilesExist() {
// This is also a "one central repository" of adding startup notifications in case
// in the future apps will want to hook into here
function initNotifications() {
if (mailer.state && mailer.state.usingSendmail) {
if (mailer.state && mailer.state.usingDirect) {
api.notifications.add({ notifications: [{
type: 'info',
message: [
"Ghost is attempting to use your server's <b>sendmail</b> to send e-mail.",
"It is recommended that you explicitly configure an e-mail service,",
"Ghost is attempting to use a direct method to send e-mail.",
"It is recommended that you explicitly configure an e-mail service.",
"See <a href=\"http://support.ghost.org/mail\" target=\"_blank\">http://support.ghost.org/mail</a> for instructions"
].join(' ')
}] }, {context: {internal: true}});

View file

@ -1,5 +1,4 @@
var cp = require('child_process'),
_ = require('lodash'),
var _ = require('lodash'),
Promise = require('bluebird'),
nodemailer = require('nodemailer'),
config = require('./config');
@ -19,36 +18,10 @@ GhostMailer.prototype.init = function () {
return Promise.resolve();
}
// Attempt to detect and fallback to `sendmail`
return this.detectSendmail().then(function (binpath) {
self.transport = nodemailer.createTransport('sendmail', {
path: binpath
});
self.state.usingSendmail = true;
}).catch(function () {
self.state.emailDisabled = true;
self.transport = null;
});
};
self.transport = nodemailer.createTransport('direct');
self.state.usingDirect = true;
GhostMailer.prototype.isWindows = function () {
return process.platform === 'win32';
};
GhostMailer.prototype.detectSendmail = function () {
if (this.isWindows()) {
return Promise.reject();
}
return new Promise(function (resolve, reject) {
cp.exec('which sendmail', function (err, stdout) {
if (err && !/bin\/sendmail/.test(stdout)) {
return reject();
}
resolve(stdout.toString().replace(/(\n|\r|\r\n)$/, ''));
});
});
return Promise.resolve();
};
GhostMailer.prototype.createTransport = function () {
@ -95,7 +68,35 @@ GhostMailer.prototype.send = function (message) {
to: to,
generateTextFromHTML: true
});
return sendMail(message);
return new Promise(function (resolve, reject) {
sendMail(message, function (error, response) {
if (error) {
return reject(new Error(error));
}
if (self.transport.transportType !== 'DIRECT') {
return resolve(response);
}
response.statusHandler.once("failed", function (data) {
var reason = 'Email Error: Failed sending email';
if (data.error.errno === "ENOTFOUND") {
reason += ': there is no mail server at this address: ' + data.domain;
}
reason += '.';
return reject(new Error(reason));
});
response.statusHandler.once("requeue", function (data) {
return reject(new Error("Email Error: message was not sent, requeued. Probably will not be sent. :( \nMore info: " + data.error.message));
});
response.statusHandler.once("sent", function () {
return resolve("Message was accepted by the mail server. Make sure to check inbox and spam folders. :)");
});
});
});
};
module.exports = new GhostMailer();

View file

@ -2,10 +2,22 @@
/*jshint expr:true*/
var testUtils = require('../../utils'),
should = require('should'),
config = require('../../../server/config'),
mailer = require('../../../server/mail'),
// Stuff we are testing
MailAPI = require('../../../server/api/mail'),
mailData = {
mailDataNoDomain = {
mail: [{
message: {
to: 'joe@doesntexistexample091283zalgo.com',
subject: 'testemail',
html: '<p>This</p>'
},
options: {}
}]
},
mailDataNoServer = {
mail: [{
message: {
to: 'joe@example.com',
@ -14,9 +26,48 @@ var testUtils = require('../../utils'),
},
options: {}
}]
},
mailDataIncomplete = {
mail: [{
message: {
subject: 'testemail',
html: '<p>This</p>'
},
options: {}
}]
};
describe('Mail API', function () {
describe('Mail API Nothing configured', function () {
before(testUtils.teardown);
afterEach(testUtils.teardown);
beforeEach(testUtils.setup('perms:mail', 'perms:init'));
should.exist(MailAPI);
it('return no email configured', function (done) {
MailAPI.send(mailDataNoServer, testUtils.context.internal).then(function (response) {
/*jshint unused:false */
done();
}).catch(function (error) {
error.message.should.eql('Email Error: No e-mail transport configured.');
error.type.should.eql('EmailError');
done();
}).catch(done);
});
it('return no email configured even when sending incomplete data', function (done) {
MailAPI.send(mailDataIncomplete, testUtils.context.internal).then(function (response) {
/*jshint unused:false */
done();
}).catch(function (error) {
error.message.should.eql('Email Error: No e-mail transport configured.');
error.type.should.eql('EmailError');
done();
}).catch(done);
});
});
describe('Mail API Direct', function () {
// Keep the DB clean
before(testUtils.teardown);
afterEach(testUtils.teardown);
@ -24,13 +75,88 @@ describe('Mail API', function () {
should.exist(MailAPI);
it('return correct failure message (internal)', function (done) {
MailAPI.send(mailData, testUtils.context.internal).then(function (response) {
it('return correct failure message for domain doesnt exist', function (done) {
config.load().then(config.set({mail: {}})).then(mailer.init()).then(function () {
mailer.transport.transportType.should.eql('DIRECT');
return MailAPI.send(mailDataNoDomain, testUtils.context.internal);
}).then(function (response) {
/*jshint unused:false */
done();
}).catch(function (error) {
error.message.should.startWith('Email Error: Failed sending email');
error.type.should.eql('EmailError');
done();
}).catch(done);
});
});
it('return correct failure message for no mail server at this address', function (done) {
mailer.transport.transportType.should.eql('DIRECT');
MailAPI.send(mailDataNoServer, testUtils.context.internal).then(function (response) {
/*jshint unused:false */
done();
}).catch(function (error) {
error.message.should.eql('Email Error: Failed sending email.');
error.type.should.eql('EmailError');
done();
}).catch(done);
});
it('return correct failure message for incomplete data', function (done) {
mailer.transport.transportType.should.eql('DIRECT');
MailAPI.send(mailDataIncomplete, testUtils.context.internal).then(function (response) {
/*jshint unused:false */
done();
}).catch(function (error) {
error.message.should.eql('Email Error: Incomplete message data.');
error.type.should.eql('EmailError');
done();
}).catch(done);
});
});
describe('Mail API Stub', function () {
// Keep the DB clean
before(testUtils.teardown);
afterEach(testUtils.teardown);
beforeEach(testUtils.setup('perms:mail', 'perms:init'));
should.exist(MailAPI);
it('stub returns a success', function (done) {
config.load().then(config.set({mail: {transport: 'stub'}})).then(mailer.init()).then(function () {
mailer.transport.transportType.should.eql('STUB');
return MailAPI.send(mailDataNoServer, testUtils.context.internal);
}).then(function (response) {
should.exist(response.mail);
should.exist(response.mail[0].message);
should.exist(response.mail[0].status);
response.mail[0].status.should.eql({message: 'Message Queued'});
response.mail[0].message.subject.should.eql('testemail');
/*jshint unused:false */
done();
}).catch(function (error) {
should.not.exist(error);
done();
}).catch(done);
});
it('stub returns a boo boo', function (done) {
config.load().then(config.set({mail: {transport: 'stub', error: 'Stub made a boo boo :('}})).then(mailer.init()).then(function () {
mailer.transport.transportType.should.eql('STUB');
return MailAPI.send(mailDataNoServer, testUtils.context.internal);
}).then(function (response) {
/*jshint unused:false */
done();
}).catch(function (error) {
error.message.should.startWith('Email Error: Failed sending email: there is no mail server at this address');
error.type.should.eql('EmailError');
done();
}).catch(done);
});
});

View file

@ -10,10 +10,8 @@ var should = require('should'),
mailer = rewire('../../server/mail'),
defaultConfig = require('../../../config'),
SMTP,
SENDMAIL,
fakeConfig,
fakeSettings,
fakeSendmail,
sandbox = sinon.sandbox.create();
// Mock SMTP config
@ -28,13 +26,6 @@ SMTP = {
}
};
// Mock Sendmail config
SENDMAIL = {
transport: 'sendmail',
options: {
path: '/nowhere/sendmail'
}
};
describe('Mail', function () {
var overrideConfig = function (newConfig) {
@ -51,17 +42,9 @@ describe('Mail', function () {
url: 'http://test.tryghost.org',
email: 'ghost-test@localhost'
};
fakeSendmail = '/fake/bin/sendmail';
overrideConfig(fakeConfig);
sandbox.stub(mailer, 'isWindows', function () {
return false;
});
sandbox.stub(mailer, 'detectSendmail', function () {
return Promise.resolve(fakeSendmail);
});
});
afterEach(function () {
@ -85,73 +68,16 @@ describe('Mail', function () {
}).catch(done);
});
it('should setup sendmail transport on initialization', function (done) {
overrideConfig({mail: SENDMAIL});
mailer.init().then(function () {
mailer.should.have.property('transport');
mailer.transport.transportType.should.eql('SENDMAIL');
mailer.transport.sendMail.should.be.a.function;
done();
}).catch(done);
});
it('should fallback to sendmail if no config set', function (done) {
overrideConfig({mail: null});
mailer.init().then(function () {
mailer.should.have.property('transport');
mailer.transport.transportType.should.eql('SENDMAIL');
mailer.transport.options.path.should.eql(fakeSendmail);
done();
}).catch(done);
});
it('should fallback to sendmail if config is empty', function (done) {
it('should fallback to direct if config is empty', function (done) {
overrideConfig({mail: {}});
mailer.init().then(function () {
mailer.should.have.property('transport');
mailer.transport.transportType.should.eql('SENDMAIL');
mailer.transport.options.path.should.eql(fakeSendmail);
mailer.transport.transportType.should.eql('DIRECT');
done();
}).catch(done);
});
it('should disable transport if config is empty & sendmail not found', function (done) {
overrideConfig({mail: {}});
mailer.detectSendmail.restore();
sandbox.stub(mailer, 'detectSendmail', Promise.reject);
mailer.init().then(function () {
should.not.exist(mailer.transport);
done();
}).catch(done);
});
it('should disable transport if config is empty & platform is win32', function (done) {
overrideConfig({mail: {}});
mailer.detectSendmail.restore();
mailer.isWindows.restore();
sandbox.stub(mailer, 'isWindows', function () {
return true;
});
mailer.init().then(function () {
should.not.exist(mailer.transport);
done();
}).catch(done);
});
it('should fail to send messages when no transport is set', function (done) {
mailer.detectSendmail.restore();
sandbox.stub(mailer, 'detectSendmail', Promise.reject);
mailer.init().then(function () {
mailer.send().then(function () {
should.fail();
done();
}).catch(function (err) {
err.should.be.an.instanceOf(Error);
done();
}).catch(done);
});
});
it('should fail to send messages when given insufficient data', function (done) {
Promise.settle([
mailer.send(),

View file

@ -52,7 +52,7 @@
"moment": "2.4.0",
"morgan": "1.0.0",
"node-uuid": "1.4.1",
"nodemailer": "0.5.13",
"nodemailer": "0.7.1",
"oauth2orize": "1.0.1",
"passport": "0.2.0",
"passport-http-bearer": "1.0.1",