mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-01-01 23:37:43 +03:00
improvement: mail structure (#7033)
no issue - in preparation for subscribers V2 - do not implement code in index.js - create mail utils
This commit is contained in:
parent
1b3e1df641
commit
e91e9eadac
@ -3,7 +3,8 @@ var _ = require('lodash'),
|
||||
pipeline = require('../utils/pipeline'),
|
||||
dataProvider = require('../models'),
|
||||
settings = require('./settings'),
|
||||
mail = require('./mail'),
|
||||
mail = require('./../mail'),
|
||||
apiMail = require('./mail'),
|
||||
globalUtils = require('../utils'),
|
||||
utils = require('./utils'),
|
||||
errors = require('../errors'),
|
||||
@ -171,7 +172,7 @@ authentication = {
|
||||
'/ghost/reset/' +
|
||||
globalUtils.encodeBase64URLsafe(data.resetToken) + '/';
|
||||
|
||||
return mail.generateContent({
|
||||
return mail.utils.generateContent({
|
||||
data: {
|
||||
resetUrl: resetUrl
|
||||
},
|
||||
@ -189,7 +190,7 @@ authentication = {
|
||||
}]
|
||||
};
|
||||
|
||||
return mail.send(payload, {context: {internal: true}});
|
||||
return apiMail.send(payload, {context: {internal: true}});
|
||||
});
|
||||
}
|
||||
|
||||
@ -413,7 +414,7 @@ authentication = {
|
||||
ownerEmail: setupUser.email
|
||||
};
|
||||
|
||||
return mail.generateContent({data: data, template: 'welcome'})
|
||||
return mail.utils.generateContent({data: data, template: 'welcome'})
|
||||
.then(function then(content) {
|
||||
var message = {
|
||||
to: setupUser.email,
|
||||
@ -428,7 +429,7 @@ authentication = {
|
||||
}]
|
||||
};
|
||||
|
||||
mail.send(payload, {context: {internal: true}}).catch(function (error) {
|
||||
apiMail.send(payload, {context: {internal: true}}).catch(function (error) {
|
||||
errors.logError(
|
||||
error.message,
|
||||
i18n.t(
|
||||
|
@ -1,35 +1,26 @@
|
||||
// # Mail API
|
||||
// API for sending Mail
|
||||
var _ = require('lodash').runInContext(),
|
||||
Promise = require('bluebird'),
|
||||
|
||||
var Promise = require('bluebird'),
|
||||
pipeline = require('../utils/pipeline'),
|
||||
config = require('../config'),
|
||||
errors = require('../errors'),
|
||||
GhostMail = require('../mail'),
|
||||
mail = require('../mail'),
|
||||
Models = require('../models'),
|
||||
utils = require('./utils'),
|
||||
notifications = require('./notifications'),
|
||||
path = require('path'),
|
||||
fs = require('fs'),
|
||||
templatesDir = path.resolve(__dirname, '..', 'mail', 'templates'),
|
||||
htmlToText = require('html-to-text'),
|
||||
readFile = Promise.promisify(fs.readFile),
|
||||
docName = 'mail',
|
||||
i18n = require('../i18n'),
|
||||
mode = process.env.NODE_ENV,
|
||||
testing = mode !== 'production' && mode !== 'development',
|
||||
mailer,
|
||||
mail;
|
||||
|
||||
_.templateSettings.interpolate = /{{([\s\S]+?)}}/g;
|
||||
apiMail;
|
||||
|
||||
/**
|
||||
* Send mail helper
|
||||
*/
|
||||
|
||||
function sendMail(object) {
|
||||
if (!(mailer instanceof GhostMail) || testing) {
|
||||
mailer = new GhostMail();
|
||||
if (!(mailer instanceof mail.GhostMailer) || testing) {
|
||||
mailer = new mail.GhostMailer();
|
||||
}
|
||||
|
||||
return mailer.send(object.mail[0].message).catch(function (err) {
|
||||
@ -58,7 +49,7 @@ function sendMail(object) {
|
||||
* @typedef Mail
|
||||
* @param mail
|
||||
*/
|
||||
mail = {
|
||||
apiMail = {
|
||||
/**
|
||||
* ### Send
|
||||
* Send an email
|
||||
@ -95,9 +86,9 @@ mail = {
|
||||
}
|
||||
|
||||
tasks = [
|
||||
utils.handlePermissions(docName, 'send'),
|
||||
send,
|
||||
formatResponse
|
||||
utils.handlePermissions(docName, 'send'),
|
||||
send,
|
||||
formatResponse
|
||||
];
|
||||
|
||||
return pipeline(tasks, options || {});
|
||||
@ -127,7 +118,7 @@ mail = {
|
||||
*/
|
||||
|
||||
function generateContent(result) {
|
||||
return mail.generateContent({template: 'test'}).then(function (content) {
|
||||
return mail.utils.generateContent({template: 'test'}).then(function (content) {
|
||||
var payload = {
|
||||
mail: [{
|
||||
message: {
|
||||
@ -158,45 +149,7 @@ mail = {
|
||||
];
|
||||
|
||||
return pipeline(tasks);
|
||||
},
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Object} options {
|
||||
* data: JSON object representing the data that will go into the email
|
||||
* template: which email template to load (files are stored in /core/server/mail/templates/)
|
||||
* }
|
||||
* @returns {*}
|
||||
*/
|
||||
generateContent: function (options) {
|
||||
var defaults,
|
||||
data;
|
||||
|
||||
defaults = {
|
||||
siteUrl: config.forceAdminSSL ? (config.urlSSL || config.url) : config.url
|
||||
};
|
||||
|
||||
data = _.defaults(defaults, options.data);
|
||||
|
||||
// read the proper email body template
|
||||
return readFile(path.join(templatesDir, options.template + '.html'), 'utf8').then(function (content) {
|
||||
var compiled,
|
||||
htmlContent,
|
||||
textContent;
|
||||
|
||||
// insert user-specific data into the email
|
||||
compiled = _.template(content);
|
||||
htmlContent = compiled(data);
|
||||
|
||||
// generate a plain-text version of the same email
|
||||
textContent = htmlToText.fromString(htmlContent);
|
||||
|
||||
return {
|
||||
html: htmlContent,
|
||||
text: textContent
|
||||
};
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = mail;
|
||||
module.exports = apiMail;
|
||||
|
@ -9,7 +9,8 @@ var Promise = require('bluebird'),
|
||||
utils = require('./utils'),
|
||||
globalUtils = require('../utils'),
|
||||
config = require('../config'),
|
||||
mail = require('./mail'),
|
||||
mail = require('./../mail'),
|
||||
apiMail = require('./mail'),
|
||||
pipeline = require('../utils/pipeline'),
|
||||
i18n = require('../i18n'),
|
||||
|
||||
@ -44,7 +45,7 @@ sendInviteEmail = function sendInviteEmail(user) {
|
||||
|
||||
emailData.resetLink = baseUrl.replace(/\/$/, '') + '/ghost/signup/' + globalUtils.encodeBase64URLsafe(resetToken) + '/';
|
||||
|
||||
return mail.generateContent({data: emailData, template: 'invite-user'});
|
||||
return mail.utils.generateContent({data: emailData, template: 'invite-user'});
|
||||
}).then(function (emailContent) {
|
||||
var payload = {
|
||||
mail: [{
|
||||
@ -58,7 +59,7 @@ sendInviteEmail = function sendInviteEmail(user) {
|
||||
}]
|
||||
};
|
||||
|
||||
return mail.send(payload, {context: {internal: true}});
|
||||
return apiMail.send(payload, {context: {internal: true}});
|
||||
});
|
||||
};
|
||||
/**
|
||||
|
105
core/server/mail/GhostMailer.js
Normal file
105
core/server/mail/GhostMailer.js
Normal file
@ -0,0 +1,105 @@
|
||||
// # Mail
|
||||
// Handles sending email for Ghost
|
||||
var _ = require('lodash'),
|
||||
Promise = require('bluebird'),
|
||||
nodemailer = require('nodemailer'),
|
||||
validator = require('validator'),
|
||||
config = require('../config'),
|
||||
i18n = require('../i18n');
|
||||
|
||||
function GhostMailer() {
|
||||
var transport = config.mail && config.mail.transport || 'direct',
|
||||
options = config.mail && _.clone(config.mail.options) || {};
|
||||
|
||||
this.state = {};
|
||||
|
||||
this.transport = nodemailer.createTransport(transport, options);
|
||||
|
||||
this.state.usingDirect = transport === 'direct';
|
||||
}
|
||||
|
||||
GhostMailer.prototype.from = function () {
|
||||
var from = config.mail && (config.mail.from || config.mail.fromaddress);
|
||||
|
||||
// If we don't have a from address at all
|
||||
if (!from) {
|
||||
// Default to ghost@[blog.url]
|
||||
from = 'ghost@' + this.getDomain();
|
||||
}
|
||||
|
||||
// If we do have a from address, and it's just an email
|
||||
if (validator.isEmail(from)) {
|
||||
if (!config.theme.title) {
|
||||
config.theme.title = i18n.t('common.mail.title', {domain: this.getDomain()});
|
||||
}
|
||||
from = '"' + config.theme.title + '" <' + from + '>';
|
||||
}
|
||||
|
||||
return from;
|
||||
};
|
||||
|
||||
// Moved it to its own module
|
||||
GhostMailer.prototype.getDomain = function () {
|
||||
var domain = config.url.match(new RegExp('^https?://([^/:?#]+)(?:[/:?#]|$)', 'i'));
|
||||
return domain && domain[1];
|
||||
};
|
||||
|
||||
// Sends an email message enforcing `to` (blog owner) and `from` fields
|
||||
// This assumes that api.settings.read('email') was already done on the API level
|
||||
GhostMailer.prototype.send = function (message) {
|
||||
var self = this,
|
||||
to;
|
||||
|
||||
// important to clone message as we modify it
|
||||
message = _.clone(message) || {};
|
||||
to = message.to || false;
|
||||
|
||||
if (!(message && message.subject && message.html && message.to)) {
|
||||
return Promise.reject(new Error(i18n.t('errors.mail.incompleteMessageData.error')));
|
||||
}
|
||||
|
||||
message = _.extend(message, {
|
||||
from: self.from(),
|
||||
to: to,
|
||||
generateTextFromHTML: true,
|
||||
encoding: 'base64'
|
||||
});
|
||||
|
||||
return new Promise(function (resolve, reject) {
|
||||
self.transport.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 = i18n.t('errors.mail.failedSendingEmail.error');
|
||||
|
||||
if (data.error && data.error.errno === 'ENOTFOUND') {
|
||||
reason += i18n.t('errors.mail.noMailServerAtAddress.error', {domain: data.domain});
|
||||
}
|
||||
reason += '.';
|
||||
return reject(new Error(reason));
|
||||
});
|
||||
|
||||
response.statusHandler.once('requeue', function (data) {
|
||||
var errorMessage = i18n.t('errors.mail.messageNotSent.error');
|
||||
|
||||
if (data.error && data.error.message) {
|
||||
errorMessage += i18n.t('errors.general.moreInfo', {info: data.error.message});
|
||||
}
|
||||
|
||||
return reject(new Error(errorMessage));
|
||||
});
|
||||
|
||||
response.statusHandler.once('sent', function () {
|
||||
return resolve(i18n.t('notices.mail.messageSent'));
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = GhostMailer;
|
@ -1,105 +1,2 @@
|
||||
// # Mail
|
||||
// Handles sending email for Ghost
|
||||
var _ = require('lodash'),
|
||||
Promise = require('bluebird'),
|
||||
nodemailer = require('nodemailer'),
|
||||
validator = require('validator'),
|
||||
config = require('../config'),
|
||||
i18n = require('../i18n');
|
||||
|
||||
function GhostMailer() {
|
||||
var transport = config.mail && config.mail.transport || 'direct',
|
||||
options = config.mail && _.clone(config.mail.options) || {};
|
||||
|
||||
this.state = {};
|
||||
|
||||
this.transport = nodemailer.createTransport(transport, options);
|
||||
|
||||
this.state.usingDirect = transport === 'direct';
|
||||
}
|
||||
|
||||
GhostMailer.prototype.from = function () {
|
||||
var from = config.mail && (config.mail.from || config.mail.fromaddress);
|
||||
|
||||
// If we don't have a from address at all
|
||||
if (!from) {
|
||||
// Default to ghost@[blog.url]
|
||||
from = 'ghost@' + this.getDomain();
|
||||
}
|
||||
|
||||
// If we do have a from address, and it's just an email
|
||||
if (validator.isEmail(from)) {
|
||||
if (!config.theme.title) {
|
||||
config.theme.title = i18n.t('common.mail.title', {domain: this.getDomain()});
|
||||
}
|
||||
from = '"' + config.theme.title + '" <' + from + '>';
|
||||
}
|
||||
|
||||
return from;
|
||||
};
|
||||
|
||||
// Moved it to its own module
|
||||
GhostMailer.prototype.getDomain = function () {
|
||||
var domain = config.url.match(new RegExp('^https?://([^/:?#]+)(?:[/:?#]|$)', 'i'));
|
||||
return domain && domain[1];
|
||||
};
|
||||
|
||||
// Sends an email message enforcing `to` (blog owner) and `from` fields
|
||||
// This assumes that api.settings.read('email') was already done on the API level
|
||||
GhostMailer.prototype.send = function (message) {
|
||||
var self = this,
|
||||
to;
|
||||
|
||||
// important to clone message as we modify it
|
||||
message = _.clone(message) || {};
|
||||
to = message.to || false;
|
||||
|
||||
if (!(message && message.subject && message.html && message.to)) {
|
||||
return Promise.reject(new Error(i18n.t('errors.mail.incompleteMessageData.error')));
|
||||
}
|
||||
|
||||
message = _.extend(message, {
|
||||
from: self.from(),
|
||||
to: to,
|
||||
generateTextFromHTML: true,
|
||||
encoding: 'base64'
|
||||
});
|
||||
|
||||
return new Promise(function (resolve, reject) {
|
||||
self.transport.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 = i18n.t('errors.mail.failedSendingEmail.error');
|
||||
|
||||
if (data.error && data.error.errno === 'ENOTFOUND') {
|
||||
reason += i18n.t('errors.mail.noMailServerAtAddress.error', {domain: data.domain});
|
||||
}
|
||||
reason += '.';
|
||||
return reject(new Error(reason));
|
||||
});
|
||||
|
||||
response.statusHandler.once('requeue', function (data) {
|
||||
var errorMessage = i18n.t('errors.mail.messageNotSent.error');
|
||||
|
||||
if (data.error && data.error.message) {
|
||||
errorMessage += i18n.t('errors.general.moreInfo', {info: data.error.message});
|
||||
}
|
||||
|
||||
return reject(new Error(errorMessage));
|
||||
});
|
||||
|
||||
response.statusHandler.once('sent', function () {
|
||||
return resolve(i18n.t('notices.mail.messageSent'));
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = GhostMailer;
|
||||
exports.GhostMailer = require('./GhostMailer');
|
||||
exports.utils = require('./utils');
|
||||
|
40
core/server/mail/utils.js
Normal file
40
core/server/mail/utils.js
Normal file
@ -0,0 +1,40 @@
|
||||
var _ = require('lodash').runInContext(),
|
||||
fs = require('fs'),
|
||||
Promise = require('bluebird'),
|
||||
path = require('path'),
|
||||
htmlToText = require('html-to-text'),
|
||||
config = require('../config'),
|
||||
templatesDir = path.resolve(__dirname, '..', 'mail', 'templates');
|
||||
|
||||
_.templateSettings.interpolate = /{{([\s\S]+?)}}/g;
|
||||
|
||||
exports.generateContent = function generateContent(options) {
|
||||
var defaults,
|
||||
data;
|
||||
|
||||
defaults = {
|
||||
siteUrl: config.forceAdminSSL ? (config.urlSSL || config.url) : config.url
|
||||
};
|
||||
|
||||
data = _.defaults(defaults, options.data);
|
||||
|
||||
// read the proper email body template
|
||||
return Promise.promisify(fs.readFile)(path.join(templatesDir, options.template + '.html'), 'utf8')
|
||||
.then(function (content) {
|
||||
var compiled,
|
||||
htmlContent,
|
||||
textContent;
|
||||
|
||||
// insert user-specific data into the email
|
||||
compiled = _.template(content);
|
||||
htmlContent = compiled(data);
|
||||
|
||||
// generate a plain-text version of the same email
|
||||
textContent = htmlToText.fromString(htmlContent);
|
||||
|
||||
return {
|
||||
html: htmlContent,
|
||||
text: textContent
|
||||
};
|
||||
});
|
||||
};
|
@ -2,9 +2,9 @@ var should = require('should'),
|
||||
Promise = require('bluebird'),
|
||||
|
||||
// Stuff we are testing
|
||||
GhostMail = require('../../server/mail'),
|
||||
configUtils = require('../utils/configUtils'),
|
||||
i18n = require('../../server/i18n'),
|
||||
mail = require('../../../server/mail'),
|
||||
configUtils = require('../../utils/configUtils'),
|
||||
i18n = require('../../../server/i18n'),
|
||||
mailer,
|
||||
|
||||
// Mock SMTP config
|
||||
@ -37,7 +37,7 @@ var should = require('should'),
|
||||
|
||||
i18n.init();
|
||||
|
||||
describe('Mail', function () {
|
||||
describe('Mail: Ghostmailer', function () {
|
||||
afterEach(function () {
|
||||
mailer = null;
|
||||
|
||||
@ -45,7 +45,7 @@ describe('Mail', function () {
|
||||
});
|
||||
|
||||
it('should attach mail provider to ghost instance', function () {
|
||||
mailer = new GhostMail();
|
||||
mailer = new mail.GhostMailer();
|
||||
|
||||
should.exist(mailer);
|
||||
mailer.should.have.property('send').and.be.a.Function();
|
||||
@ -53,7 +53,7 @@ describe('Mail', function () {
|
||||
|
||||
it('should setup SMTP transport on initialization', function () {
|
||||
configUtils.set({mail: SMTP});
|
||||
mailer = new GhostMail();
|
||||
mailer = new mail.GhostMailer();
|
||||
|
||||
mailer.should.have.property('transport');
|
||||
mailer.transport.transportType.should.eql('SMTP');
|
||||
@ -63,7 +63,7 @@ describe('Mail', function () {
|
||||
it('should fallback to direct if config is empty', function () {
|
||||
configUtils.set({mail: {}});
|
||||
|
||||
mailer = new GhostMail();
|
||||
mailer = new mail.GhostMailer();
|
||||
|
||||
mailer.should.have.property('transport');
|
||||
mailer.transport.transportType.should.eql('DIRECT');
|
||||
@ -72,7 +72,7 @@ describe('Mail', function () {
|
||||
it('sends valid message successfully ', function (done) {
|
||||
configUtils.set({mail: {transport: 'stub'}});
|
||||
|
||||
mailer = new GhostMail();
|
||||
mailer = new mail.GhostMailer();
|
||||
|
||||
mailer.transport.transportType.should.eql('STUB');
|
||||
|
||||
@ -88,7 +88,7 @@ describe('Mail', function () {
|
||||
it('handles failure', function (done) {
|
||||
configUtils.set({mail: {transport: 'stub', options: {error: 'Stub made a boo boo :('}}});
|
||||
|
||||
mailer = new GhostMail();
|
||||
mailer = new mail.GhostMailer();
|
||||
|
||||
mailer.transport.transportType.should.eql('STUB');
|
||||
|
||||
@ -101,7 +101,7 @@ describe('Mail', function () {
|
||||
});
|
||||
|
||||
it('should fail to send messages when given insufficient data', function (done) {
|
||||
mailer = new GhostMail();
|
||||
mailer = new mail.GhostMailer();
|
||||
|
||||
Promise.all([
|
||||
mailer.send().reflect(),
|
||||
@ -122,7 +122,7 @@ describe('Mail', function () {
|
||||
beforeEach(function () {
|
||||
configUtils.set({mail: {}});
|
||||
|
||||
mailer = new GhostMail();
|
||||
mailer = new mail.GhostMailer();
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
@ -171,7 +171,7 @@ describe('Mail', function () {
|
||||
}
|
||||
});
|
||||
|
||||
mailer = new GhostMail();
|
||||
mailer = new mail.GhostMailer();
|
||||
|
||||
mailer.from().should.equal('"Blog Title" <static@example.com>');
|
||||
});
|
||||
@ -180,7 +180,7 @@ describe('Mail', function () {
|
||||
// Standard domain
|
||||
configUtils.set({url: 'http://default.com', mail: {from: null}, theme: {title: 'Test'}});
|
||||
|
||||
mailer = new GhostMail();
|
||||
mailer = new mail.GhostMailer();
|
||||
|
||||
mailer.from().should.equal('"Test" <ghost@default.com>');
|
||||
|
||||
@ -197,7 +197,7 @@ describe('Mail', function () {
|
||||
// Standard domain
|
||||
configUtils.set({mail: {from: '"bar" <from@default.com>', fromaddress: '"Qux" <fa@default.com>'}});
|
||||
|
||||
mailer = new GhostMail();
|
||||
mailer = new mail.GhostMailer();
|
||||
|
||||
mailer.from().should.equal('"bar" <from@default.com>');
|
||||
});
|
||||
@ -206,7 +206,7 @@ describe('Mail', function () {
|
||||
// from and fromaddress are both set
|
||||
configUtils.set({mail: {from: 'from@default.com', fromaddress: 'fa@default.com'}, theme: {title: 'Test'}});
|
||||
|
||||
mailer = new GhostMail();
|
||||
mailer = new mail.GhostMailer();
|
||||
|
||||
mailer.from().should.equal('"Test" <from@default.com>');
|
||||
|
||||
@ -223,7 +223,7 @@ describe('Mail', function () {
|
||||
// from and fromaddress are both set
|
||||
configUtils.set({mail: {from: '"R2D2" <from@default.com>', fromaddress: '"C3PO" <fa@default.com>'}, theme: {title: 'Test'}});
|
||||
|
||||
mailer = new GhostMail();
|
||||
mailer = new mail.GhostMailer();
|
||||
|
||||
mailer.from().should.equal('"R2D2" <from@default.com>');
|
||||
|
||||
@ -239,7 +239,7 @@ describe('Mail', function () {
|
||||
it('should use default title if not theme title is provided', function () {
|
||||
configUtils.set({url: 'http://default.com:2368/', mail: {from: null}, theme: {title: null}});
|
||||
|
||||
mailer = new GhostMail();
|
||||
mailer = new mail.GhostMailer();
|
||||
|
||||
mailer.from().should.equal('"Ghost at default.com" <ghost@default.com>');
|
||||
});
|
45
core/test/unit/mail/utils_spec.js
Normal file
45
core/test/unit/mail/utils_spec.js
Normal file
@ -0,0 +1,45 @@
|
||||
var sinon = require('sinon'),
|
||||
mail = require(__dirname + '../../../../server/mail'),
|
||||
sandbox = sinon.sandbox.create();
|
||||
|
||||
describe('Mail: Utils', function () {
|
||||
var scope = {ghostMailer: null};
|
||||
|
||||
before(function () {
|
||||
scope.ghostMailer = new mail.GhostMailer();
|
||||
|
||||
sandbox.stub(scope.ghostMailer.transport, 'sendMail', function (message, sendMailDone) {
|
||||
sendMailDone(null, {
|
||||
statusHandler: {
|
||||
once: function (eventName, eventDone) {
|
||||
if (eventName === 'sent') {
|
||||
eventDone();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
after(function () {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
it('generate welcome', function (done) {
|
||||
mail.utils.generateContent({
|
||||
template: 'welcome',
|
||||
data: {
|
||||
ownerEmail: 'kate@ghost.org'
|
||||
}
|
||||
}).then(function (result) {
|
||||
return scope.ghostMailer.send({
|
||||
to: 'kate@ghost.org',
|
||||
subject: 'lol',
|
||||
html: result.html,
|
||||
text: result.text
|
||||
});
|
||||
}).then(function () {
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue
Block a user