Allow using external resources for default messages

This commit is contained in:
Andris Reinman 2017-08-04 14:07:17 +03:00
parent ba1fc7f3ad
commit b5fdbcd3e1
12 changed files with 280 additions and 46 deletions

2
.gitignore vendored
View file

@ -4,4 +4,4 @@ npm-debug.log
.npmrc
config/production.*
config/development.*
emails/*.json
emails/example.json

21
api.js
View file

@ -100,9 +100,24 @@ module.exports = done => {
database: db.database,
redis: db.redis
});
messageHandler = new MessageHandler({ database: db.database, gridfs: db.gridfs, redis: db.redis });
userHandler = new UserHandler({ database: db.database, users: db.users, redis: db.redis, messageHandler });
mailboxHandler = new MailboxHandler({ database: db.database, users: db.users, redis: db.redis, notifier });
messageHandler = new MessageHandler({
database: db.database,
users: db.users,
gridfs: db.gridfs,
redis: db.redis
});
userHandler = new UserHandler({
database: db.database,
users: db.users,
redis: db.redis,
messageHandler
});
mailboxHandler = new MailboxHandler({
database: db.database,
users: db.users,
redis: db.redis,
notifier
});
usersRoutes(db, server, userHandler);
addressesRoutes(db, server);

View file

@ -1,17 +0,0 @@
{
"mailbox": "INBOX",
"seen": true,
"flag": true,
"from": {
"name": "Support",
"address": "info@example.com"
},
"to": {
"name": "[NAME]",
"address": "[EMAIL]"
},
"subject": "[FNAME], welcome to our awesome service!",
"text": "[FNAME], your new email account [EMAIL] is now ready to be used!",
"html": "<p><strong>[FNAME]</strong>, your new email account [EMAIL] is now ready to be used!</p>"
}

View file

@ -1,14 +1,21 @@
# Default messages
Add here messages that should be inserted to new users INBOX. Messages are formatted according to [Nodemailer message structure](https://nodemailer.com/message/) and sorted by filename. Only files with .json extension are used.
Add here default email messages that should be inserted to new users account. To test it out, rename example.json.disabled to example.json, restart Wild Duck and create a new account. Your INBOX should include the message composed from the example.
All string values can take the following template tags (case sensitive):
## Creating default messages
Messages are formatted according to the [Nodemailer message structure](https://nodemailer.com/message/) and stored to this folder as json files.
All string values in the JSON structure can use the following template tags (case sensitive) that are replaced while compiling:
- **[USERNAME]** will be replaced by the username of the user
- **[DOMAIN]** will be replaced by the service domain
- **[EMAIL]** will be replaced by the email address of the user
- **[DOMAIN]** will be replaced by the domain part of the email address
- **[NAME]** will be replaced by the registered name of the user
- **[FNAME]** will be replaced by the first part of the registered name of the user
- **[FNAME]** will be replaced by the first name of the registered user
- **[LNAME]** will be replaced by the last name of the registered user
> NB! All values are replaced as is, except in the `html` field. For `html` the replaced values are html encoded.
You can also specify some extra options with the mail data object
@ -21,3 +28,10 @@ You can also specify some extra options with the mail data object
- **'Junk'** to store the message to the Spam folder
- **'Drafts'** to store the message to the Drafts folder
- **'Archive'** to store the message to the Archive folder
You can include some resources as external files by using the same name prefix as the main json file. Name prefix can be anything, it is used to sort the messages (if you want to insert multiple messages at once) and also to group resources related to that message.
- **name.json** is the main message file, this includes the general message structure
- **name.html** or **name.htm** is the HTML content of the message. If this file exists then it sets or overrides the `html` property in message json structure
- **name.text** or **name.txt** is the plaintext content of the message. If this file exists then it sets or overrides the `text` property in message json structure
- **name.filename.ext** is included in the message as an attachment

BIN
emails/example.duck.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

130
emails/example.html Normal file
View file

@ -0,0 +1,130 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
<head>
<meta name="viewport" content="width=device-width" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Actionable emails e.g. reset password</title>
<style type="text/css">
img {
max-width: 100%;
}
body {
-webkit-font-smoothing: antialiased;
-webkit-text-size-adjust: none;
width: 100% !important;
height: 100%;
line-height: 1.6em;
}
body {
background-color: #f6f6f6;
}
@media only screen and (max-width: 640px) {
body {
padding: 0 !important;
}
h1 {
font-weight: 800 !important;
margin: 20px 0 5px !important;
}
h2 {
font-weight: 800 !important;
margin: 20px 0 5px !important;
}
h3 {
font-weight: 800 !important;
margin: 20px 0 5px !important;
}
h4 {
font-weight: 800 !important;
margin: 20px 0 5px !important;
}
h1 {
font-size: 22px !important;
}
h2 {
font-size: 18px !important;
}
h3 {
font-size: 16px !important;
}
.container {
padding: 0 !important;
width: 100% !important;
}
.content {
padding: 0 !important;
}
.content-wrap {
padding: 10px !important;
}
.invoice {
width: 100% !important;
}
}
</style>
</head>
<body itemscope itemtype="http://schema.org/EmailMessage" style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; width: 100% !important; height: 100%; line-height: 1.6em; background-color: #f6f6f6; margin: 0;"
bgcolor="#f6f6f6">
<table class="body-wrap" style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; width: 100%; background-color: #f6f6f6; margin: 0;" bgcolor="#f6f6f6">
<tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
<td style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;" valign="top"></td>
<td class="container" width="600" style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; display: block !important; max-width: 600px !important; clear: both !important; margin: 0 auto;"
valign="top">
<div class="content" style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; max-width: 600px; display: block; margin: 0 auto; padding: 20px;">
<table class="main" width="100%" cellpadding="0" cellspacing="0" itemprop="action" itemscope itemtype="http://schema.org/ConfirmAction" style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; border-radius: 3px; background-color: #fff; margin: 0; border: 1px solid #e9e9e9;"
bgcolor="#fff">
<tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
<td class="content-wrap" style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 20px;" valign="top">
<meta itemprop="name" content="Confirm Email" style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;" />
<table width="100%" cellpadding="0" cellspacing="0" style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
<tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
<td class="content-block" style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;" valign="top">
[FNAME], welcome to Wild Duck email service!
</td>
</tr>
<tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
<td class="content-block" style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;" valign="top">
If you are seeing this message then it means you have reached the inbox of your new email address [EMAIL]. Be aware though that the service is in a constant change, so this address might disappear during the next database schema update. Don't start using it as your main email address!
</td>
</tr>
<tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
<td class="content-block" style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;" valign="top">
This message was generated and inserted to this mailbox from the /emails folder using the default template.
</td>
</tr>
<tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
<td class="content-block" style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;" valign="top">
&mdash; the Wild Duck team
</td>
</tr>
</table>
</td>
</tr>
</table>
<div class="footer" style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; width: 100%; clear: both; color: #999; margin: 0; padding: 20px;">
<table width="100%" style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
<tr style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
<td class="aligncenter content-block" style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; vertical-align: top; color: #999; text-align: center; margin: 0; padding: 0 0 20px;"
align="center" valign="top">Check out <a href="https://github.com/nodemailer/wildduck" style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; color: #999; text-decoration: underline; margin: 0;">Wild Duck</a> on
Github.</td>
</tr>
</table>
</div>
</div>
</td>
<td style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;" valign="top"></td>
</tr>
</table>
</body>
</html>

View file

@ -0,0 +1,12 @@
{
"flag": true,
"from": {
"name": "Wild Duck Support",
"address": "info@[DOMAIN]"
},
"to": {
"name": "[NAME]",
"address": "[EMAIL]"
},
"subject": "[FNAME], welcome to our awesome service!"
}

9
emails/example.txt Normal file
View file

@ -0,0 +1,9 @@
[FNAME], welcome to Wild Duck email service!
If you are seeing this message then it means you have reached the inbox of your new email address [EMAIL]. Be aware though that the service is in a constant change, so this address might disappear during the next database schema update. Don't start using it as your main email address!
This message was generated and inserted to this mailbox from the /emails folder using the default template.
Your friends from the Wild Duck team
Check out Wild Duck on Github: https://github.com/nodemailer/wildduck

View file

@ -170,7 +170,7 @@ function renderEmailTemplate(tags, template) {
}
Object.keys(node || {}).forEach(key => {
if (!node[key]) {
if (!node[key] || ['content'].includes(key)) {
return;
}
@ -213,30 +213,102 @@ function getEmailTemplates(tags, callback) {
files = files.sort((a, b) => a.localeCompare(b));
let pos = 0;
let newTemplates = [];
let filesMap = new Map();
let checkFiles = () => {
if (pos >= files.length) {
let newTemplates = Array.from(filesMap)
.map(entry => {
entry = entry[1];
if (!entry.message) {
return false;
}
if (entry.html) {
entry.message.html = entry.html;
}
if (entry.text) {
entry.message.text = entry.text;
}
if (entry.attachments) {
entry.message.attachments = [].concat(entry.message.attachments || []).concat(entry.attachments);
}
if (entry.text) {
entry.message.text = entry.text;
}
return entry.message;
})
.filter(entry => entry);
templates = newTemplates;
return callback(null, templates.map(template => renderEmailTemplate(tags, template)));
}
let file = files[pos++];
if (!/\.json$/i.test(file)) {
return checkFiles();
}
fs.readFile(pathlib.join(templateFolder, file), 'utf-8', (err, email) => {
let fParts = pathlib.parse(file);
fs.readFile(pathlib.join(templateFolder, file), (err, value) => {
if (err) {
// ignore?
return checkFiles();
}
let parsed;
try {
parsed = JSON.parse(email);
} catch (E) {
//ignore?
let ext = fParts.ext.toLowerCase();
let name = fParts.name.toLowerCase();
if (name.indexOf('.') >= 0) {
name = name.substr(0, name.indexOf('.'));
}
if (parsed) {
newTemplates.push(parsed);
let type = false;
switch (ext) {
case '.json': {
try {
value = JSON.parse(value.toString('utf-8'));
type = 'message';
} catch (E) {
//ignore?
}
break;
}
case '.html':
case '.htm':
value = value.toString('utf-8');
type = 'html';
break;
case '.text':
case '.txt':
value = value.toString('utf-8');
type = 'text';
break;
default: {
if (name.length < fParts.name.length) {
type = 'attachment';
value = {
filename: fParts.base.substr(name.length + 1),
content: value.toString('base64'),
encoding: 'base64'
};
}
}
}
if (type) {
if (!filesMap.has(name)) {
filesMap.set(name, {});
}
if (type === 'attachment') {
if (!filesMap.get(name).attachments) {
filesMap.get(name).attachments = [value];
} else {
filesMap.get(name).attachments.push(value);
}
} else {
filesMap.get(name)[type] = value;
}
}
return checkFiles();
});
};

View file

@ -14,6 +14,7 @@ const crypto = require('crypto');
const mailboxTranslations = require('./translations');
const base32 = require('base32.js');
const MailComposer = require('nodemailer/lib/mail-composer');
const humanname = require('humanname');
class UserHandler {
constructor(options) {
@ -480,11 +481,13 @@ class UserHandler {
return callback(null, id);
}
let parsedName = humanname.parse(userData.name);
this.pushDefaultMessages(
userData,
{
NAME: userData.name || address,
FNAME: (userData.name || '').trim().replace(/\s+/g, ' ').split(' ').shift() || address,
FNAME: parsedName.firstName,
LNAME: parsedName.lastName,
DOMAIN: address.substr(address.indexOf('@') + 1),
EMAIL: address
},

View file

@ -28,6 +28,8 @@
"generate-password": "^1.3.0",
"he": "^1.1.1",
"html-to-text": "^3.3.0",
"humanname": "^0.2.2",
"humanparser": "^1.4.0",
"iconv-lite": "^0.4.18",
"joi": "^10.6.0",
"js-yaml": "^3.9.1",
@ -58,7 +60,7 @@
"url": "git://github.com/wildduck-email/wildduck.git"
},
"optionalDependencies": {
"@ronomon/crypto-async": "^2.0.1",
"@ronomon/crypto-async": "^2.2.0",
"modern-syslog": "^1.1.4"
}
}

View file

@ -4,7 +4,6 @@
const chai = require('chai');
const frisby = require('icedfrisby');
const Joi = require('joi');
const expect = chai.expect;
chai.config.includeStack = true;
@ -26,11 +25,6 @@ frisby
{ json: true }
)
.expectStatus(200)
.expectJSONTypes({
success: Joi.boolean(),
id: Joi.string(),
error: Joi.string()
})
.afterJSON(response => {
expect(response).to.exist;
expect(response.success).to.be.true;