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:
commit
07eaaae8bf
5 changed files with 172 additions and 119 deletions
|
@ -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}});
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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",
|
||||
|
|
Loading…
Add table
Reference in a new issue