Allow specifying defualt emails for created users

This commit is contained in:
Andris Reinman 2017-07-31 14:16:50 +03:00
parent bef736c1b8
commit 12585229a3
7 changed files with 205 additions and 4 deletions

1
.gitignore vendored
View file

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

4
api.js
View file

@ -100,9 +100,9 @@ module.exports = done => {
database: db.database,
redis: db.redis
});
userHandler = new UserHandler({ database: db.database, users: db.users, redis: db.redis });
mailboxHandler = new MailboxHandler({ database: db.database, users: db.users, redis: db.redis, notifier });
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 });
usersRoutes(db, server, userHandler);
addressesRoutes(db, server);

17
emails/01.json.example Normal file
View file

@ -0,0 +1,17 @@
{
"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>"
}

23
emails/README.md Normal file
View file

@ -0,0 +1,23 @@
# 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.
All string values can take the following template tags (case sensitive):
- **[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
- **[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
You can also specify some extra options with the mail data object
- **flag** is a boolean. If true, then the message is flagged
- **seen** is a boolean. If true, then the message is marked as seen
- **mailbox** is a string with one of the following values (case insensitive):
- **'INBOX'** (the default) to store the message to INBOX
- **'Sent'** to store the message to the Sent Mail folder
- **'Trash'** to store the message to the Trash folder
- **'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

View file

@ -3,6 +3,11 @@
const punycode = require('punycode');
const libmime = require('libmime');
const consts = require('./consts');
const fs = require('fs');
const he = require('he');
const pathlib = require('path');
let templates = false;
function checkRangeQuery(uids, ne) {
// check if uids is a straight continous array and if such then return a range query,
@ -156,10 +161,95 @@ function getMailboxCounter(db, mailbox, type, done) {
});
}
function renderEmailTemplate(tags, template) {
let result = JSON.parse(JSON.stringify(template));
let walk = (node, nodeKey) => {
if (!node) {
return;
}
Object.keys(node || {}).forEach(key => {
if (!node[key]) {
return;
}
if (Array.isArray(node[key])) {
return node[key].forEach(child => walk(child, nodeKey));
}
if (typeof node[key] === 'object') {
return walk(node[key], key);
}
if (typeof node[key] === 'string') {
let isHTML = /html/i.test(key);
node[key] = node[key].replace(/\[([^\]]+)\]/g, (match, tag) => {
if (tag in tags) {
return isHTML ? he.encode(tags[tag]) : tags[tag];
}
return match;
});
return;
}
});
};
walk(result, false);
return result;
}
function getEmailTemplates(tags, callback) {
if (templates) {
return callback(null, templates.map(template => renderEmailTemplate(tags, template)));
}
let templateFolder = pathlib.join(__dirname, '..', 'emails');
fs.readdir(templateFolder, (err, files) => {
if (err) {
return callback(err);
}
files = files.sort((a, b) => a.localeCompare(b));
let pos = 0;
let newTemplates = [];
let checkFiles = () => {
if (pos >= files.length) {
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) => {
if (err) {
// ignore?
return checkFiles();
}
let parsed;
try {
parsed = JSON.parse(email);
} catch (E) {
//ignore?
}
if (parsed) {
newTemplates.push(parsed);
}
return checkFiles();
});
};
checkFiles();
});
}
module.exports = {
normalizeAddress,
redisConfig,
checkRangeQuery,
decodeAddresses,
getMailboxCounter
getMailboxCounter,
getEmailTemplates
};

View file

@ -13,12 +13,14 @@ const os = require('os');
const crypto = require('crypto');
const mailboxTranslations = require('./translations');
const base32 = require('base32.js');
const MailComposer = require('nodemailer/lib/mail-composer');
class UserHandler {
constructor(options) {
this.database = options.database;
this.users = options.users || options.database;
this.redis = options.redis;
this.messageHandler = options.messageHandler;
}
/**
@ -463,7 +465,20 @@ class UserHandler {
return callback(new Error('Database Error, failed to create user'));
}
return callback(null, id);
if (!this.messageHandler) {
return callback(null, id);
}
this.pushDefaultMessages(
id,
{
NAME: data.name || address,
FNAME: (data.name || '').trim().replace(/\s+/g, ' ').split(' ').shift() || address,
DOMAIN: address.substr(address.indexOf('@') + 1),
EMAIL: address
},
() => callback(null, id)
);
});
});
});
@ -471,6 +486,60 @@ class UserHandler {
});
}
pushDefaultMessages(user, tags, callback) {
tools.getEmailTemplates(tags, (err, messages) => {
if (err || !messages || !messages.length) {
return callback();
}
let pos = 0;
let insertMessages = () => {
if (pos >= messages.length) {
return callback();
}
let data = messages[pos++];
let compiler = new MailComposer(data);
compiler.compile().build((err, message) => {
if (err) {
return insertMessages();
}
let mailboxQueryKey = 'path';
let mailboxQueryValue = 'INBOX';
if (['sent', 'trash', 'junk', 'drafts', 'archive'].includes((data.mailbox || '').toString().toLowerCase())) {
mailboxQueryKey = 'specialUse';
mailboxQueryValue = '\\' + data.mailbox.toLowerCase().replace(/^./g, c => c.toUpperCase());
}
let flags = [];
if (data.seen) {
flags.push('\\Seen');
}
if (data.flag) {
flags.push('\\Flagged');
}
this.messageHandler.add(
{
user,
[mailboxQueryKey]: mailboxQueryValue,
meta: {
source: 'AUTO',
time: Date.now()
},
flags,
raw: message
},
insertMessages
);
});
};
insertMessages();
});
}
reset(username, callback) {
let password = generatePassword.generate({
length: 12,

View file

@ -26,6 +26,7 @@
"addressparser": "^1.0.1",
"bcryptjs": "^2.4.3",
"generate-password": "^1.3.0",
"he": "^1.1.1",
"html-to-text": "^3.3.0",
"iconv-lite": "^0.4.18",
"joi": "^10.6.0",