From e30e29bf5d2537e20307629092659c43f65eaba6 Mon Sep 17 00:00:00 2001 From: Maurice Williams Date: Tue, 22 Jul 2014 21:22:13 -0400 Subject: [PATCH] Implementing HTML emails closes #3082 - no more in-line HTML strings - adding files for "welcome", "reset password", and "invite user" emails - added mail.generateContent() to create HTML and plain-text email content - refactored methods that trigger emails to send both HTML and plain-text emails --- core/server/api/authentication.js | 67 ++++----- core/server/api/mail.js | 63 +++++++-- core/server/api/users.js | 56 +++++--- core/server/email-templates/invite-user.html | 56 ++++++++ .../email-templates/raw/invite-user.html | 127 +++++++++++++++++ .../email-templates/raw/reset-password.html | 125 +++++++++++++++++ core/server/email-templates/raw/test.html | 128 +++++++++++++++++ core/server/email-templates/raw/welcome.html | 130 ++++++++++++++++++ .../email-templates/reset-password.html | 54 ++++++++ core/server/email-templates/test.html | 57 ++++++++ core/server/email-templates/welcome.html | 59 ++++++++ package.json | 3 +- 12 files changed, 858 insertions(+), 67 deletions(-) create mode 100644 core/server/email-templates/invite-user.html create mode 100644 core/server/email-templates/raw/invite-user.html create mode 100644 core/server/email-templates/raw/reset-password.html create mode 100644 core/server/email-templates/raw/test.html create mode 100644 core/server/email-templates/raw/welcome.html create mode 100644 core/server/email-templates/reset-password.html create mode 100644 core/server/email-templates/test.html create mode 100644 core/server/email-templates/welcome.html diff --git a/core/server/api/authentication.js b/core/server/api/authentication.js index d9e6193c75..a19477185f 100644 --- a/core/server/api/authentication.js +++ b/core/server/api/authentication.js @@ -46,24 +46,26 @@ authentication = { return dataProvider.User.generateResetToken(email, expires, dbHash); }).then(function (resetToken) { var baseUrl = config.forceAdminSSL ? (config.urlSSL || config.url) : config.url, - siteLink = '' + baseUrl + '', resetUrl = baseUrl.replace(/\/$/, '') + '/ghost/reset/' + resetToken + '/', - resetLink = '' + resetUrl + '', - payload = { + emailData = { + resetUrl: resetUrl + }; + + mail.generateContent({data: emailData, template: 'reset-password'}).then(function (emailContent) { + var payload = { mail: [{ message: { to: email, subject: 'Reset Password', - html: '

Hello!

' + - '

A request has been made to reset the password on the site ' + siteLink + '.

' + - '

Please follow the link below to reset your password:

' + resetLink + '

' + - '

Ghost

' + html: emailContent.html, + text: emailContent.text }, options: {} }] }; + return mail.send(payload, {context: {internal: true}}); + }); - return mail.send(payload, {context: {internal: true}}); }).then(function () { return when.resolve({passwordreset: [{message: 'Check your email for further instructions.'}]}); }).otherwise(function (error) { @@ -212,33 +214,32 @@ authentication = { setupUser = user.toJSON(); return settings.edit({settings: userSettings}, {context: {user: setupUser.id}}); }).then(function () { - var message = { - to: setupUser.email, - subject: 'Your New Ghost Blog', - html: '

Hello!

' + - '

Good news! You\'ve successfully created a brand new Ghost blog over on ' + config.url + '

' + - '

You can log in to your admin account with the following details:

' + - '

Email Address: ' + setupUser.email + '
' + - 'Password: The password you chose when you signed up

' + - '

Keep this email somewhere safe for future reference, and have fun!

' + - '

xoxo

' + - '

Team Ghost
' + - 'https://ghost.org

' - }, - payload = { - mail: [{ - message: message, - options: {} - }] - }; + var data = { + ownerEmail: setupUser.email + }; - return mail.send(payload, {context: {internal: true}}).otherwise(function (error) { - errors.logError( - error.message, - "Unable to send welcome email, your blog will continue to function.", - "Please see http://docs.ghost.org/mail/ for instructions on configuring email." - ); + mail.generateContent({data: data, template: 'welcome'}).then(function (emailContent) { + var message = { + to: setupUser.email, + subject: 'Your New Ghost Blog', + html: emailContent.html, + text: emailContent.text + }, + payload = { + mail: [{ + message: message, + options: {} + }] + }; + return mail.send(payload, {context: {internal: true}}).otherwise(function (error) { + errors.logError( + error.message, + "Unable to send welcome email, your blog will continue to function.", + "Please see http://docs.ghost.org/mail/ for instructions on configuring email." + ); + }); }); + }).then(function () { return when.resolve({ users: [setupUser]}); }); diff --git a/core/server/api/mail.js b/core/server/api/mail.js index 40b4d02413..a4f0aead1d 100644 --- a/core/server/api/mail.js +++ b/core/server/api/mail.js @@ -1,9 +1,14 @@ // # Mail API // API for sending Mail var when = require('when'), + _ = require('lodash'), config = require('../config'), canThis = require('../permissions').canThis, errors = require('../errors'), + path = require('path'), + fs = require('fs'), + templatesDir = path.resolve(__dirname, '..', 'email-templates'), + htmlToText = require('html-to-text'), mail; /** @@ -54,24 +59,60 @@ mail = { * @returns {Promise} */ sendTest: function (object, options) { - var html = '

Hello there!

' + - '

Excellent!' + - ' You\'ve successfully setup your email config for your Ghost blog over on ' + config.url + '

' + - '

If you hadn\'t, you wouldn\'t be reading this email, but you are, so it looks like all is well :)

' + - '

xoxo

' + - '

Team Ghost
' + - 'https://ghost.org

', - payload = {mail: [{ + return mail.generateContent({template: 'test'}).then(function (emailContent) { + var payload = {mail: [{ message: { to: object.to, subject: 'Test Ghost Email', - html: html + html: emailContent.html, + text: emailContent.text } }]}; - return mail.send(payload, options); + return mail.send(payload, options); + }); + }, + + /** + * + * @param { + * data: JSON object representing the data that will go into the email + * template: which email template to load (files are stored in /core/server/email-templates/) + * } + * @returns {*} + */ + generateContent: function (options) { + + var defaultData = { + siteUrl: config.forceAdminSSL ? (config.urlSSL || config.url) : config.url + }, + emailData = _.defaults(defaultData, options.data); + + _.templateSettings.interpolate = /{{([\s\S]+?)}}/g; + + //read the proper email body template + return when.promise(function (resolve, reject) { + fs.readFile(templatesDir + '/' + options.template + '.html', {encoding: 'utf8'}, function (err, fileContent) { + if (err) { + reject(err); + } + + //insert user-specific data into the email + var htmlContent = _.template(fileContent, emailData), + textContent; + + //generate a plain-text version of the same email + textContent = htmlToText.fromString(htmlContent); + + resolve({ html: htmlContent, + text: textContent + }); + + }); + }); + } }; -module.exports = mail; \ No newline at end of file +module.exports = mail; diff --git a/core/server/api/users.js b/core/server/api/users.js index dfbc400a77..a377e1f893 100644 --- a/core/server/api/users.js +++ b/core/server/api/users.js @@ -182,28 +182,40 @@ users = { dbHash = response.settings[0].value; return dataProvider.User.generateResetToken(user.email, expires, dbHash); }).then(function (resetToken) { - var baseUrl = config.forceAdminSSL ? (config.urlSSL || config.url) : config.url, - siteLink = '' + baseUrl + '', - resetUrl = baseUrl.replace(/\/$/, '') + '/ghost/signup/' + resetToken + '/', - resetLink = '' + resetUrl + '', - payload = { - mail: [{ - message: { - to: user.email, - subject: 'Invitation', - html: '

Hello!

' + - '

You have been invited to ' + siteLink + '.

' + - '

Please follow the link to sign up and publish your ideas:

' + resetLink + '

' + - '

Ghost

' - }, - options: {} - }] - }; - return mail.send(payload, {context: {internal: true}}).then(function () { - // If status was invited-pending and sending the invitation succeeded, set status to invited. - if (user.status === 'invited-pending') { - return dataProvider.User.edit({status: 'invited'}, {id: user.id}); - } + when.join(users.read({'id': user.created_by}), settings.read({'key': 'title'})).then(function (values) { + var invitedBy = values[0].users[0], + blogTitle = values[1].settings[0].value, + baseUrl = config.forceAdminSSL ? (config.urlSSL || config.url) : config.url, + resetUrl = baseUrl.replace(/\/$/, '') + '/ghost/signup/' + resetToken + '/', + emailData = { + blogName: blogTitle, + invitedByName: invitedBy.name, + invitedByEmail: invitedBy.email, + resetLink: resetUrl + }; + + mail.generateContent({data: emailData, template: 'invite-user'}).then(function (emailContent) { + + var payload = { + mail: [ + { + message: { + to: user.email, + subject: emailData.invitedByName + ' has invited you to join ' + emailData.blogName, + html: emailContent.html, + text: emailContent.text + }, + options: {} + } + ] + }; + return mail.send(payload, {context: {internal: true}}).then(function () { + // If status was invited-pending and sending the invitation succeeded, set status to invited. + if (user.status === 'invited-pending') { + return dataProvider.User.edit({status: 'invited'}, {id: user.id}); + } + }); + }); }); }).then(function () { return when.resolve({users: [user]}); diff --git a/core/server/email-templates/invite-user.html b/core/server/email-templates/invite-user.html new file mode 100644 index 0000000000..08a84e0ff2 --- /dev/null +++ b/core/server/email-templates/invite-user.html @@ -0,0 +1,56 @@ + + + + + + + + + + + + + +
+ + + + + +
+ +
+ + + + +
+ + +

Welcome

+

{{blogName}} is using Ghost to publish things on the internet! {{invitedByName}} has invited you to join. Please click on the link below to activate your account.

+

{{resetLink}}

+

No idea what Ghost is? It's a simple, beautiful platform for running an online blog or publication. Writers, businesses and individuals from all over the world use Ghost to publish their stories and ideas. Find out more.

+

If you have trouble activating your {{blogName}} account, you can reach out to {{invitedByName}} on {{invitedByEmail}} for assistance.

+

Have fun, and good luck!

+ + +
+
+
+ + + + + +
+ +
+ +
+ + + + diff --git a/core/server/email-templates/raw/invite-user.html b/core/server/email-templates/raw/invite-user.html new file mode 100644 index 0000000000..ba340f87c5 --- /dev/null +++ b/core/server/email-templates/raw/invite-user.html @@ -0,0 +1,127 @@ + + + + + + + + + + + + + +
+ + + + + +
+ +
+ + + + +
+ + +

Welcome

+

{{blogName}} is using Ghost to publish things on the internet! {{invitedByName}} has invited you to join. Please click on the link below to activate your account.

+

{{resetLink}}

+

No idea what Ghost is? It's a simple, beautiful platform for running an online blog or publication. Writers, businesses and individuals from all over the world use Ghost to publish their stories and ideas. Find out more.

+

If you have trouble activating your {{blogName}} account, you can reach out to {{invitedByName}} on {{invitedByEmail}} for assistance.

+

Have fun, and good luck!

+ + +
+
+
+ + + + + +
+ +
+ +
+ + + diff --git a/core/server/email-templates/raw/reset-password.html b/core/server/email-templates/raw/reset-password.html new file mode 100644 index 0000000000..0ff85211e6 --- /dev/null +++ b/core/server/email-templates/raw/reset-password.html @@ -0,0 +1,125 @@ + + + + + + + + + + + + + +
+ + + + + +
+ +
+ + + + +
+ + +

Hello!

+

A request has been made to reset the password on the site {{ siteUrl }} .

+

Please follow the link below to reset your password:

{{ resetUrl }}

+

Ghost

+ + +
+
+
+ + + + + +
+ +
+ +
+ + + diff --git a/core/server/email-templates/raw/test.html b/core/server/email-templates/raw/test.html new file mode 100644 index 0000000000..5b77cc16cf --- /dev/null +++ b/core/server/email-templates/raw/test.html @@ -0,0 +1,128 @@ + + + + + + + + + + + + + +
+ + + + + +
+ +
+ + + + +
+ + +

Hello there!

+

Excellent! + You've successfully setup your email config for your Ghost blog over on {{ siteUrl }}

+

If you hadn't, you wouldn't be reading this email, but you are, so it looks like all is well :)

+

xoxo

+

Team Ghost
+ https://ghost.org

+ + +
+
+
+ + + + + +
+ +
+ +
+ + + diff --git a/core/server/email-templates/raw/welcome.html b/core/server/email-templates/raw/welcome.html new file mode 100644 index 0000000000..d2c1acf901 --- /dev/null +++ b/core/server/email-templates/raw/welcome.html @@ -0,0 +1,130 @@ + + + + + + + + + + + + + +
+ + + + + +
+ +
+ + + + +
+ + +

Hello!

+

Good news! You've successfully created a brand new Ghost blog over on {{ siteUrl }}

+

You can log in to your admin account with the following details:

+

Email Address: {{ownerEmail}}
+ Password: The password you chose when you signed up

+

Keep this email somewhere safe for future reference, and have fun!

+

xoxo

+

Team Ghost
+ https://ghost.org

+ + +
+
+
+ + + + + +
+ +
+ +
+ + + diff --git a/core/server/email-templates/reset-password.html b/core/server/email-templates/reset-password.html new file mode 100644 index 0000000000..69542b20aa --- /dev/null +++ b/core/server/email-templates/reset-password.html @@ -0,0 +1,54 @@ + + + + + + + + + + + + + +
+ + + + + +
+ +
+ + + + +
+ + +

Hello!

+

A request has been made to reset the password on the site {{ siteUrl }} .

+

Please follow the link below to reset your password:

{{ resetUrl }}

+

Ghost

+ + +
+
+
+ + + + + +
+ +
+ +
+ + + + diff --git a/core/server/email-templates/test.html b/core/server/email-templates/test.html new file mode 100644 index 0000000000..f418a1a4a4 --- /dev/null +++ b/core/server/email-templates/test.html @@ -0,0 +1,57 @@ + + + + + + + + + + + + + +
+ + + + + +
+ +
+ + + + +
+ + +

Hello there!

+

Excellent! + You've successfully setup your email config for your Ghost blog over on {{ siteUrl }}

+

If you hadn't, you wouldn't be reading this email, but you are, so it looks like all is well :)

+

xoxo

+

Team Ghost
+ https://ghost.org

+ + +
+
+
+ + + + + +
+ +
+ +
+ + + + diff --git a/core/server/email-templates/welcome.html b/core/server/email-templates/welcome.html new file mode 100644 index 0000000000..10b7e7152c --- /dev/null +++ b/core/server/email-templates/welcome.html @@ -0,0 +1,59 @@ + + + + + + + + + + + + + +
+ + + + + +
+ +
+ + + + +
+ + +

Hello!

+

Good news! You've successfully created a brand new Ghost blog over on {{ siteUrl }}

+

You can log in to your admin account with the following details:

+

Email Address: {{ownerEmail}}
+ Password: The password you chose when you signed up

+

Keep this email somewhere safe for future reference, and have fun!

+

xoxo

+

Team Ghost
+ https://ghost.org

+ + +
+
+
+ + + + + +
+ +
+ +
+ + + + diff --git a/package.json b/package.json index b8db05c706..9ba018c575 100644 --- a/package.json +++ b/package.json @@ -37,14 +37,15 @@ "busboy": "0.2.3", "colors": "0.6.2", "compression": "^1.0.2", - "cookie-parser": "1.0.1", "connect": "3.0.0-rc.1", "connect-slashes": "1.2.0", + "cookie-parser": "1.0.1", "downsize": "0.0.5", "express": "4.1.1", "express-hbs": "0.7.10", "express-session": "1.0.4", "fs-extra": "0.8.1", + "html-to-text": "^0.1.0", "knex": "0.6.21", "lodash": "2.4.1", "moment": "2.4.0",