mirror of
https://github.com/nodemailer/wildduck.git
synced 2025-01-27 10:18:25 +08:00
Allow using external resources for default messages
This commit is contained in:
parent
ba1fc7f3ad
commit
b5fdbcd3e1
12 changed files with 280 additions and 46 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -4,4 +4,4 @@ npm-debug.log
|
|||
.npmrc
|
||||
config/production.*
|
||||
config/development.*
|
||||
emails/*.json
|
||||
emails/example.json
|
||||
|
|
21
api.js
21
api.js
|
@ -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);
|
||||
|
|
|
@ -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>"
|
||||
}
|
|
@ -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
BIN
emails/example.duck.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
130
emails/example.html
Normal file
130
emails/example.html
Normal 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">
|
||||
— 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>
|
12
emails/example.json.disabled
Normal file
12
emails/example.json.disabled
Normal 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
9
emails/example.txt
Normal 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
|
98
lib/tools.js
98
lib/tools.js
|
@ -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();
|
||||
});
|
||||
};
|
||||
|
|
|
@ -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
|
||||
},
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue