mirror of
https://github.com/nodemailer/wildduck.git
synced 2025-01-14 20:07:54 +08:00
Allow specifying defualt emails for created users
This commit is contained in:
parent
bef736c1b8
commit
12585229a3
7 changed files with 205 additions and 4 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -4,3 +4,4 @@ npm-debug.log
|
||||||
.npmrc
|
.npmrc
|
||||||
config/production.*
|
config/production.*
|
||||||
config/development.*
|
config/development.*
|
||||||
|
emails/*.json
|
||||||
|
|
4
api.js
4
api.js
|
@ -100,9 +100,9 @@ module.exports = done => {
|
||||||
database: db.database,
|
database: db.database,
|
||||||
redis: db.redis
|
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 });
|
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);
|
usersRoutes(db, server, userHandler);
|
||||||
addressesRoutes(db, server);
|
addressesRoutes(db, server);
|
||||||
|
|
17
emails/01.json.example
Normal file
17
emails/01.json.example
Normal 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
23
emails/README.md
Normal 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
|
92
lib/tools.js
92
lib/tools.js
|
@ -3,6 +3,11 @@
|
||||||
const punycode = require('punycode');
|
const punycode = require('punycode');
|
||||||
const libmime = require('libmime');
|
const libmime = require('libmime');
|
||||||
const consts = require('./consts');
|
const consts = require('./consts');
|
||||||
|
const fs = require('fs');
|
||||||
|
const he = require('he');
|
||||||
|
const pathlib = require('path');
|
||||||
|
|
||||||
|
let templates = false;
|
||||||
|
|
||||||
function checkRangeQuery(uids, ne) {
|
function checkRangeQuery(uids, ne) {
|
||||||
// check if uids is a straight continous array and if such then return a range query,
|
// 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 = {
|
module.exports = {
|
||||||
normalizeAddress,
|
normalizeAddress,
|
||||||
redisConfig,
|
redisConfig,
|
||||||
checkRangeQuery,
|
checkRangeQuery,
|
||||||
decodeAddresses,
|
decodeAddresses,
|
||||||
getMailboxCounter
|
getMailboxCounter,
|
||||||
|
getEmailTemplates
|
||||||
};
|
};
|
||||||
|
|
|
@ -13,12 +13,14 @@ const os = require('os');
|
||||||
const crypto = require('crypto');
|
const crypto = require('crypto');
|
||||||
const mailboxTranslations = require('./translations');
|
const mailboxTranslations = require('./translations');
|
||||||
const base32 = require('base32.js');
|
const base32 = require('base32.js');
|
||||||
|
const MailComposer = require('nodemailer/lib/mail-composer');
|
||||||
|
|
||||||
class UserHandler {
|
class UserHandler {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
this.database = options.database;
|
this.database = options.database;
|
||||||
this.users = options.users || options.database;
|
this.users = options.users || options.database;
|
||||||
this.redis = options.redis;
|
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(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) {
|
reset(username, callback) {
|
||||||
let password = generatePassword.generate({
|
let password = generatePassword.generate({
|
||||||
length: 12,
|
length: 12,
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
"addressparser": "^1.0.1",
|
"addressparser": "^1.0.1",
|
||||||
"bcryptjs": "^2.4.3",
|
"bcryptjs": "^2.4.3",
|
||||||
"generate-password": "^1.3.0",
|
"generate-password": "^1.3.0",
|
||||||
|
"he": "^1.1.1",
|
||||||
"html-to-text": "^3.3.0",
|
"html-to-text": "^3.3.0",
|
||||||
"iconv-lite": "^0.4.18",
|
"iconv-lite": "^0.4.18",
|
||||||
"joi": "^10.6.0",
|
"joi": "^10.6.0",
|
||||||
|
|
Loading…
Reference in a new issue