mirror of
https://github.com/nodemailer/wildduck.git
synced 2025-01-01 13:13:53 +08:00
addeed /addresses/resolve
This commit is contained in:
parent
7e18ad65a3
commit
bfdd0f03a6
7 changed files with 450 additions and 173 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -1 +1 @@
|
|||
define({
"name": "wildduck",
"version": "1.0.0",
"description": "WildDuck API docs. Under construction, see old docs here: https://github.com/nodemailer/wildduck/blob/master/docs/api.md",
"title": "WildDuck API",
"url": "http://localhost:8080",
"sampleUrl": false,
"defaultVersion": "0.0.0",
"apidoc": "0.3.0",
"generator": {
"name": "apidoc",
"time": "2018-01-03T11:22:08.500Z",
"url": "http://apidocjs.com",
"version": "0.17.6"
}
});
|
||||
define({
"name": "wildduck",
"version": "1.0.0",
"description": "WildDuck API docs. Under construction, see old docs here: https://github.com/nodemailer/wildduck/blob/master/docs/api.md",
"title": "WildDuck API",
"url": "http://localhost:8080",
"sampleUrl": false,
"defaultVersion": "0.0.0",
"apidoc": "0.3.0",
"generator": {
"name": "apidoc",
"time": "2018-01-04T10:03:09.783Z",
"url": "http://apidocjs.com",
"version": "0.17.6"
}
});
|
||||
|
|
|
@ -1 +1 @@
|
|||
{
"name": "wildduck",
"version": "1.0.0",
"description": "WildDuck API docs. Under construction, see old docs here: https://github.com/nodemailer/wildduck/blob/master/docs/api.md",
"title": "WildDuck API",
"url": "http://localhost:8080",
"sampleUrl": false,
"defaultVersion": "0.0.0",
"apidoc": "0.3.0",
"generator": {
"name": "apidoc",
"time": "2018-01-03T11:22:08.500Z",
"url": "http://apidocjs.com",
"version": "0.17.6"
}
}
|
||||
{
"name": "wildduck",
"version": "1.0.0",
"description": "WildDuck API docs. Under construction, see old docs here: https://github.com/nodemailer/wildduck/blob/master/docs/api.md",
"title": "WildDuck API",
"url": "http://localhost:8080",
"sampleUrl": false,
"defaultVersion": "0.0.0",
"apidoc": "0.3.0",
"generator": {
"name": "apidoc",
"time": "2018-01-04T10:03:09.783Z",
"url": "http://apidocjs.com",
"version": "0.17.6"
}
}
|
||||
|
|
|
@ -113,12 +113,12 @@ module.exports = (db, server) => {
|
|||
|
||||
let filter = query
|
||||
? {
|
||||
address: {
|
||||
// cannot use dotless version as this would break domain search
|
||||
$regex: query.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&'),
|
||||
$options: ''
|
||||
}
|
||||
}
|
||||
address: {
|
||||
// cannot use dotless version as this would break domain search
|
||||
$regex: query.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&'),
|
||||
$options: ''
|
||||
}
|
||||
}
|
||||
: {};
|
||||
|
||||
db.users.collection('addresses').count(filter, (err, total) => {
|
||||
|
@ -1001,6 +1001,11 @@ module.exports = (db, server) => {
|
|||
* @apiParam {String[]} targets An array of forwarding targets. The value could either be an email address or a relay url to next MX server ("smtp://mx2.zone.eu:25")
|
||||
* @apiParam {Number} [forwards] Daily allowed forwarding count for this address
|
||||
* @apiParam {Boolean} [allowWildcard=false] If <code>true</code> then address value can be in the form of <code>*@example.com</code>, otherwise using * is not allowed
|
||||
* @apiParam {Object} [autoreply] Autoreply information
|
||||
* @apiParam {Boolean} [autoreply.enabled] If true, then autoreply is enabled for this address
|
||||
* @apiParam {String} [autoreply.subject] Autoreply subject line
|
||||
* @apiParam {String} [autoreply.text] Autoreply plaintext content
|
||||
* @apiParam {String} [autoreply.html] Autoreply HTML content
|
||||
*
|
||||
* @apiSuccess {Boolean} success Indicates successful response
|
||||
* @apiSuccess {String} id ID of the Address
|
||||
|
@ -1053,7 +1058,24 @@ module.exports = (db, server) => {
|
|||
forwards: Joi.number()
|
||||
.min(0)
|
||||
.default(0),
|
||||
allowWildcard: Joi.boolean().truthy(['Y', 'true', 'yes', 1])
|
||||
allowWildcard: Joi.boolean().truthy(['Y', 'true', 'yes', 1]),
|
||||
autoreply: Joi.object().keys({
|
||||
enabled: Joi.boolean()
|
||||
.truthy(['Y', 'true', 'yes', 1])
|
||||
.default(true),
|
||||
subject: Joi.string()
|
||||
.empty('')
|
||||
.trim()
|
||||
.max(128),
|
||||
text: Joi.string()
|
||||
.empty('')
|
||||
.trim()
|
||||
.max(128 * 1024),
|
||||
html: Joi.string()
|
||||
.empty('')
|
||||
.trim()
|
||||
.max(128 * 1024)
|
||||
})
|
||||
});
|
||||
|
||||
const result = Joi.validate(req.params, schema, {
|
||||
|
@ -1075,6 +1097,32 @@ module.exports = (db, server) => {
|
|||
let targets = result.value.targets;
|
||||
let forwards = result.value.forwards;
|
||||
|
||||
if (result.value.autoreply) {
|
||||
if (!result.value.autoreply.subject && 'subject' in req.params.autoreply) {
|
||||
result.value.autoreply.subject = '';
|
||||
}
|
||||
|
||||
if (!result.value.autoreply.text && 'text' in req.params.autoreply) {
|
||||
result.value.autoreply.text = '';
|
||||
if (!result.value.autoreply.html) {
|
||||
// make sure we also update html part
|
||||
result.value.autoreply.html = '';
|
||||
}
|
||||
}
|
||||
|
||||
if (!result.value.autoreply.html && 'html' in req.autoreply.params) {
|
||||
result.value.autoreply.html = '';
|
||||
if (!result.value.autoreply.text) {
|
||||
// make sure we also update plaintext part
|
||||
result.value.autoreply.text = '';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
result.value.autoreply = {
|
||||
enabled: false
|
||||
};
|
||||
}
|
||||
|
||||
// needed to resolve users for addresses
|
||||
let addrlist = [];
|
||||
let cachedAddrviews = new WeakMap();
|
||||
|
@ -1202,6 +1250,7 @@ module.exports = (db, server) => {
|
|||
addrview: address.substr(0, address.indexOf('@')).replace(/\./g, '') + address.substr(address.indexOf('@')),
|
||||
targets,
|
||||
forwards,
|
||||
autoreply: result.value.autoreply,
|
||||
created: new Date()
|
||||
},
|
||||
(err, r) => {
|
||||
|
@ -1240,6 +1289,11 @@ module.exports = (db, server) => {
|
|||
* @apiParam {String} address ID of the Address
|
||||
* @apiParam {String[]} [targets] An array of forwarding targets. The value could either be an email address or a relay url to next MX server ("smtp://mx2.zone.eu:25"). If set then overwrites previous targets array
|
||||
* @apiParam {Number} [forwards] Daily allowed forwarding count for this address
|
||||
* @apiParam {Object} [autoreply] Autoreply information
|
||||
* @apiParam {Boolean} [autoreply.enabled] If true, then autoreply is enabled for this address
|
||||
* @apiParam {String} [autoreply.subject] Autoreply subject line
|
||||
* @apiParam {String} [autoreply.text] Autoreply plaintext content
|
||||
* @apiParam {String} [autoreply.html] Autoreply HTML content
|
||||
*
|
||||
* @apiSuccess {Boolean} success Indicates successful response
|
||||
*
|
||||
|
@ -1283,7 +1337,22 @@ module.exports = (db, server) => {
|
|||
})
|
||||
)
|
||||
.min(1),
|
||||
forwards: Joi.number().min(0)
|
||||
forwards: Joi.number().min(0),
|
||||
autoreply: Joi.object().keys({
|
||||
enabled: Joi.boolean().truthy(['Y', 'true', 'yes', 1]),
|
||||
subject: Joi.string()
|
||||
.empty('')
|
||||
.trim()
|
||||
.max(128),
|
||||
text: Joi.string()
|
||||
.empty('')
|
||||
.trim()
|
||||
.max(128 * 1024),
|
||||
html: Joi.string()
|
||||
.empty('')
|
||||
.trim()
|
||||
.max(128 * 1024)
|
||||
})
|
||||
});
|
||||
|
||||
const result = Joi.validate(req.params, schema, {
|
||||
|
@ -1306,6 +1375,32 @@ module.exports = (db, server) => {
|
|||
updates.forwards = result.value.forwards;
|
||||
}
|
||||
|
||||
if (result.value.autoreply) {
|
||||
if (!result.value.autoreply.subject && 'subject' in req.params.autoreply) {
|
||||
result.value.autoreply.subject = '';
|
||||
}
|
||||
|
||||
if (!result.value.autoreply.text && 'text' in req.params.autoreply) {
|
||||
result.value.autoreply.text = '';
|
||||
if (!result.value.autoreply.html) {
|
||||
// make sure we also update html part
|
||||
result.value.autoreply.html = '';
|
||||
}
|
||||
}
|
||||
|
||||
if (!result.value.autoreply.html && 'html' in req.autoreply.params) {
|
||||
result.value.autoreply.html = '';
|
||||
if (!result.value.autoreply.text) {
|
||||
// make sure we also update plaintext part
|
||||
result.value.autoreply.text = '';
|
||||
}
|
||||
}
|
||||
|
||||
Object.keys(result.value.autoreply).forEach(key => {
|
||||
updates['autoreply.' + key] = result.value.autoreply[key];
|
||||
});
|
||||
}
|
||||
|
||||
db.users.collection('addresses').findOne(
|
||||
{
|
||||
_id: address
|
||||
|
@ -1562,6 +1657,11 @@ module.exports = (db, server) => {
|
|||
* @apiSuccess {Number} limits.forwards.allowed How many messages per 24 hour can be forwarded
|
||||
* @apiSuccess {Number} limits.forwards.used How many messages are forwarded during current 24 hour period
|
||||
* @apiSuccess {Number} limits.forwards.ttl Time until the end of current 24 hour period
|
||||
* @apiSuccess {Object} autoreply Autoreply information
|
||||
* @apiSuccess {Boolean} autoreply.enabled If true, then autoreply is enabled for this address
|
||||
* @apiSuccess {String} autoreply.subject Autoreply subject line
|
||||
* @apiSuccess {String} autoreply.text Autoreply plaintext content
|
||||
* @apiSuccess {String} autoreply.html Autoreply HTML content
|
||||
* @apiSuccess {String} created Datestring of the time the address was created
|
||||
*
|
||||
* @apiError error Description of the error
|
||||
|
@ -1668,6 +1768,7 @@ module.exports = (db, server) => {
|
|||
ttl: forwardsTtl >= 0 ? forwardsTtl : false
|
||||
}
|
||||
},
|
||||
autoreply: addressData.autoreply || { enabled: false },
|
||||
created: addressData.created
|
||||
});
|
||||
|
||||
|
@ -1676,4 +1777,181 @@ module.exports = (db, server) => {
|
|||
}
|
||||
);
|
||||
});
|
||||
|
||||
/**
|
||||
* @api {get} /addresses/resolve/:address Get Address info
|
||||
* @apiName GetAddressInfo
|
||||
* @apiGroup Addresses
|
||||
* @apiHeader {String} X-Access-Token Optional access token if authentication is enabled
|
||||
* @apiHeaderExample {json} Header-Example:
|
||||
* {
|
||||
* "X-Access-Token": "59fc66a03e54454869460e45"
|
||||
* }
|
||||
*
|
||||
* @apiParam {String} address ID of the Address
|
||||
* @apiParam {String} address ID of the Address or e-mail address string
|
||||
*
|
||||
* @apiSuccess {Boolean} success Indicates successful response
|
||||
* @apiSuccess {String} id ID of the Address
|
||||
* @apiSuccess {String} address E-mail address string
|
||||
* @apiSuccess {String} user ID of the user if the address belongs to an User
|
||||
* @apiSuccess {String[]} targets List of forwarding targets if this is a Forwarded address
|
||||
* @apiSuccess {Object} limits Account limits and usage for Forwarded address
|
||||
* @apiSuccess {Object} limits.forwards Forwarding quota
|
||||
* @apiSuccess {Number} limits.forwards.allowed How many messages per 24 hour can be forwarded
|
||||
* @apiSuccess {Number} limits.forwards.used How many messages are forwarded during current 24 hour period
|
||||
* @apiSuccess {Number} limits.forwards.ttl Time until the end of current 24 hour period
|
||||
* @apiSuccess {Object} autoreply Autoreply information
|
||||
* @apiSuccess {Boolean} autoreply.enabled If true, then autoreply is enabled for this address
|
||||
* @apiSuccess {String} autoreply.subject Autoreply subject line
|
||||
* @apiSuccess {String} autoreply.text Autoreply plaintext content
|
||||
* @apiSuccess {String} autoreply.html Autoreply HTML content
|
||||
* @apiSuccess {String} created Datestring of the time the address was created
|
||||
*
|
||||
* @apiError error Description of the error
|
||||
*
|
||||
* @apiExample {curl} Example usage:
|
||||
* curl -i http://localhost:8080/addresses/resolve/k%C3%A4ru%40j%C3%B5geva.ee
|
||||
*
|
||||
* @apiSuccessExample {json} User-Address:
|
||||
* HTTP/1.1 200 OK
|
||||
* {
|
||||
* "success": true,
|
||||
* "id": "59ef21aef255ed1d9d790e81",
|
||||
* "address": "user@example.com",
|
||||
* "user": "59ef21aef255ed1d9d771bb"
|
||||
* "created": "2017-10-24T11:19:10.911Z"
|
||||
* }
|
||||
*
|
||||
* @apiSuccessExample {json} Forwarded-Address:
|
||||
* HTTP/1.1 200 OK
|
||||
* {
|
||||
* "success": true,
|
||||
* "id": "59ef21aef255ed1d9d790e81",
|
||||
* "address": "user@example.com",
|
||||
* "targets": [
|
||||
* "my.other.address@example.com"
|
||||
* ],
|
||||
* "limits": {
|
||||
* "forwards": {
|
||||
* "allowed": 2000,
|
||||
* "used": 0,
|
||||
* "ttl": false
|
||||
* }
|
||||
* },
|
||||
* "autoreply": {
|
||||
* "enabled": false
|
||||
* },
|
||||
* "created": "2017-10-24T11:19:10.911Z"
|
||||
* }
|
||||
*
|
||||
* @apiErrorExample {json} Error-Response:
|
||||
* HTTP/1.1 200 OK
|
||||
* {
|
||||
* "error": "This address does not exist"
|
||||
* }
|
||||
*/
|
||||
server.get('/addresses/resolve/:address', (req, res, next) => {
|
||||
res.charSet('utf-8');
|
||||
|
||||
const schema = Joi.object().keys({
|
||||
address: [
|
||||
Joi.string()
|
||||
.hex()
|
||||
.lowercase()
|
||||
.length(24)
|
||||
.required(),
|
||||
Joi.string().email()
|
||||
]
|
||||
});
|
||||
|
||||
const result = Joi.validate(req.params, schema, {
|
||||
abortEarly: false,
|
||||
convert: true
|
||||
});
|
||||
|
||||
if (result.error) {
|
||||
res.json({
|
||||
error: result.error.message,
|
||||
code: 'InputValidationError'
|
||||
});
|
||||
return next();
|
||||
}
|
||||
|
||||
let query = {};
|
||||
if (result.value.address.indexOf('@') >= 0) {
|
||||
let address = tools.normalizeAddress(result.value.address);
|
||||
query = {
|
||||
addrview: address.substr(0, address.indexOf('@')).replace(/\./g, '') + address.substr(address.indexOf('@'))
|
||||
};
|
||||
} else {
|
||||
let address = new ObjectID(result.value.address);
|
||||
query = {
|
||||
_id: address
|
||||
};
|
||||
}
|
||||
|
||||
db.users.collection('addresses').findOne(query, (err, addressData) => {
|
||||
if (err) {
|
||||
res.json({
|
||||
error: 'MongoDB Error: ' + err.message,
|
||||
code: 'InternalDatabaseError'
|
||||
});
|
||||
return next();
|
||||
}
|
||||
if (!addressData) {
|
||||
res.status(404);
|
||||
res.json({
|
||||
error: 'Invalid or unknown address',
|
||||
code: 'AddressNotFound'
|
||||
});
|
||||
return next();
|
||||
}
|
||||
|
||||
if (addressData.user) {
|
||||
res.json({
|
||||
success: true,
|
||||
id: addressData._id,
|
||||
address: addressData.address,
|
||||
user: addressData.user,
|
||||
created: addressData.created
|
||||
});
|
||||
return next();
|
||||
}
|
||||
|
||||
db.redis
|
||||
.multi()
|
||||
// sending counters are stored in Redis
|
||||
.get('wdf:' + addressData._id.toString())
|
||||
.ttl('wdf:' + addressData._id.toString())
|
||||
.exec((err, result) => {
|
||||
if (err) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
let forwards = Number(addressData.forwards) || config.maxForwards || consts.MAX_FORWARDS;
|
||||
|
||||
let forwardsSent = Number(result && result[0] && result[0][1]) || 0;
|
||||
let forwardsTtl = Number(result && result[1] && result[1][1]) || 0;
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
id: addressData._id,
|
||||
address: addressData.address,
|
||||
targets: addressData.targets && addressData.targets.map(t => t.value),
|
||||
limits: {
|
||||
forwards: {
|
||||
allowed: forwards,
|
||||
used: forwardsSent,
|
||||
ttl: forwardsTtl >= 0 ? forwardsTtl : false
|
||||
}
|
||||
},
|
||||
autoreply: addressData.autoreply || { enabled: false },
|
||||
created: addressData.created
|
||||
});
|
||||
|
||||
return next();
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
|
260
lib/autoreply.js
260
lib/autoreply.js
|
@ -5,171 +5,147 @@ const MessageSplitter = require('./message-splitter');
|
|||
const consts = require('./consts');
|
||||
const errors = require('./errors');
|
||||
|
||||
module.exports = (options, callback) => {
|
||||
module.exports = (options, autoreplyData, callback) => {
|
||||
if (!options.sender || /mailer-daemon|no-?reply/gi.test(options.sender)) {
|
||||
return callback(null, false);
|
||||
}
|
||||
|
||||
let curtime = new Date();
|
||||
options.db.database.collection('autoreplies').findOne(
|
||||
{
|
||||
user: options.userData._id,
|
||||
start: {
|
||||
$lte: curtime
|
||||
},
|
||||
end: {
|
||||
$gte: curtime
|
||||
}
|
||||
},
|
||||
(err, autoreply) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
// step 1. check if recipient is valid (non special address)
|
||||
// step 2. check if recipient not in cache list
|
||||
// step 3. parse headers, check if not automatic message
|
||||
// step 4. prepare message with special headers (in-reply-to, references, Auto-Submitted)
|
||||
|
||||
if (!autoreply || !autoreply.status) {
|
||||
return callback(null, false);
|
||||
}
|
||||
let messageHeaders = false;
|
||||
let messageSplitter = new MessageSplitter();
|
||||
|
||||
// step 1. check if recipient is valid (non special address)
|
||||
// step 2. check if recipient not in cache list
|
||||
// step 3. parse headers, check if not automatic message
|
||||
// step 4. prepare message with special headers (in-reply-to, references, Auto-Submitted)
|
||||
messageSplitter.once('headers', headers => {
|
||||
messageHeaders = headers;
|
||||
|
||||
let messageHeaders = false;
|
||||
let messageSplitter = new MessageSplitter();
|
||||
let autoSubmitted = headers.getFirst('Auto-Submitted');
|
||||
if (autoSubmitted && autoSubmitted.toLowerCase() !== 'no') {
|
||||
// skip automatic messages
|
||||
return callback(null, false);
|
||||
}
|
||||
let precedence = headers.getFirst('Precedence');
|
||||
if (precedence && ['list', 'junk', 'bulk'].includes(precedence.toLowerCase())) {
|
||||
return callback(null, false);
|
||||
}
|
||||
let listUnsubscribe = headers.getFirst('List-Unsubscribe');
|
||||
if (listUnsubscribe) {
|
||||
return callback(null, false);
|
||||
}
|
||||
let suppressAutoresponse = headers.getFirst('X-Auto-Response-Suppress');
|
||||
if (suppressAutoresponse && /OOF|AutoReply/i.test(suppressAutoresponse)) {
|
||||
return callback(null, false);
|
||||
}
|
||||
|
||||
messageSplitter.once('headers', headers => {
|
||||
messageHeaders = headers;
|
||||
|
||||
let autoSubmitted = headers.getFirst('Auto-Submitted');
|
||||
if (autoSubmitted && autoSubmitted.toLowerCase() !== 'no') {
|
||||
// skip automatic messages
|
||||
return callback(null, false);
|
||||
}
|
||||
let precedence = headers.getFirst('Precedence');
|
||||
if (precedence && ['list', 'junk', 'bulk'].includes(precedence.toLowerCase())) {
|
||||
return callback(null, false);
|
||||
}
|
||||
let listUnsubscribe = headers.getFirst('List-Unsubscribe');
|
||||
if (listUnsubscribe) {
|
||||
return callback(null, false);
|
||||
}
|
||||
let suppressAutoresponse = headers.getFirst('X-Auto-Response-Suppress');
|
||||
if (suppressAutoresponse && /OOF|AutoReply/i.test(suppressAutoresponse)) {
|
||||
options.db.redis
|
||||
.multi()
|
||||
// delete all old entries
|
||||
.zremrangebyscore('war:' + autoreplyData._id, '-inf', Date.now() - consts.MAX_AUTOREPLY_INTERVAL)
|
||||
// add new entry if not present
|
||||
.zadd('war:' + autoreplyData._id, 'NX', Date.now(), options.sender)
|
||||
// if no-one touches this key from now, then delete after max interval has passed
|
||||
.expire('war:' + autoreplyData._id, consts.MAX_AUTOREPLY_INTERVAL)
|
||||
.exec((err, result) => {
|
||||
if (err) {
|
||||
errors.notify(err, { userId: autoreplyData._id });
|
||||
return callback(null, false);
|
||||
}
|
||||
|
||||
options.db.redis
|
||||
.multi()
|
||||
// delete all old entries
|
||||
.zremrangebyscore('war:' + autoreply._id, '-inf', Date.now() - consts.MAX_AUTOREPLY_INTERVAL)
|
||||
// add new entry if not present
|
||||
.zadd('war:' + autoreply._id, 'NX', Date.now(), options.sender)
|
||||
// if no-one touches this key from now, then delete after max interval has passed
|
||||
.expire('war:' + autoreply._id, consts.MAX_AUTOREPLY_INTERVAL)
|
||||
.exec((err, result) => {
|
||||
if (err) {
|
||||
errors.notify(err, { userId: options.userData._id });
|
||||
return callback(null, false);
|
||||
}
|
||||
if (!result || !result[1] || !result[1][1]) {
|
||||
// already responded
|
||||
return callback(null, false);
|
||||
}
|
||||
|
||||
if (!result || !result[1] || !result[1][1]) {
|
||||
// already responded
|
||||
return callback(null, false);
|
||||
}
|
||||
// check limiting counters
|
||||
options.messageHandler.counters.ttlcounter('wda:' + autoreplyData._id, 1, consts.MAX_AUTOREPLIES, false, (err, result) => {
|
||||
if (err || !result.success) {
|
||||
return callback(null, false);
|
||||
}
|
||||
|
||||
// check limiting counters
|
||||
options.messageHandler.counters.ttlcounter('wda:' + options.userData._id, 1, consts.MAX_AUTOREPLIES, false, (err, result) => {
|
||||
if (err || !result.success) {
|
||||
return callback(null, false);
|
||||
let data = {
|
||||
envelope: {
|
||||
from: '',
|
||||
to: options.sender
|
||||
},
|
||||
from: {
|
||||
name: options.userData && options.userData.name,
|
||||
address: options.recipient
|
||||
},
|
||||
to: options.sender,
|
||||
subject: (autoreplyData.subject && 'Auto: ' + autoreplyData.subject) || {
|
||||
prepared: true,
|
||||
value: 'Auto: Re: ' + headers.getFirst('Subject')
|
||||
},
|
||||
headers: {
|
||||
'Auto-Submitted': 'auto-replied',
|
||||
'X-WD-Autoreply-For': (options.parentId || '').toString()
|
||||
},
|
||||
inReplyTo: headers.getFirst('Message-ID'),
|
||||
references: (headers.getFirst('Message-ID') + ' ' + headers.getFirst('References')).trim(),
|
||||
text: autoreplyData.text,
|
||||
html: autoreplyData.html
|
||||
};
|
||||
|
||||
let compiler = new MailComposer(data);
|
||||
let message = options.maildrop.push(
|
||||
{
|
||||
parentId: options.parentId,
|
||||
reason: 'autoreply',
|
||||
from: '',
|
||||
to: options.sender,
|
||||
interface: 'autoreplies'
|
||||
},
|
||||
(err, ...args) => {
|
||||
if (err || !args[0]) {
|
||||
if (err) {
|
||||
err.code = err.code || 'ERRCOMPOSE';
|
||||
}
|
||||
return callback(err, ...args);
|
||||
}
|
||||
|
||||
let data = {
|
||||
envelope: {
|
||||
from: '',
|
||||
to: options.sender
|
||||
},
|
||||
from: {
|
||||
name: options.userData.name,
|
||||
address: options.recipient
|
||||
},
|
||||
to: options.sender,
|
||||
subject: autoreply.subject
|
||||
? 'Auto: ' + autoreply.subject
|
||||
: {
|
||||
prepared: true,
|
||||
value: 'Auto: Re: ' + headers.getFirst('Subject')
|
||||
},
|
||||
headers: {
|
||||
'Auto-Submitted': 'auto-replied',
|
||||
'X-WD-Autoreply-For': (options.parentId || '').toString()
|
||||
},
|
||||
inReplyTo: headers.getFirst('Message-ID'),
|
||||
references: (headers.getFirst('Message-ID') + ' ' + headers.getFirst('References')).trim(),
|
||||
text: autoreply.text,
|
||||
html: autoreply.html
|
||||
};
|
||||
|
||||
let compiler = new MailComposer(data);
|
||||
let message = options.maildrop.push(
|
||||
options.db.database.collection('messagelog').insertOne(
|
||||
{
|
||||
id: args[0].id,
|
||||
messageId: args[0].messageId,
|
||||
parentId: options.parentId,
|
||||
reason: 'autoreply',
|
||||
action: 'AUTOREPLY',
|
||||
from: '',
|
||||
to: options.sender,
|
||||
interface: 'autoreplies'
|
||||
created: new Date()
|
||||
},
|
||||
(err, ...args) => {
|
||||
if (err || !args[0]) {
|
||||
if (err) {
|
||||
err.code = err.code || 'ERRCOMPOSE';
|
||||
}
|
||||
return callback(err, ...args);
|
||||
}
|
||||
options.db.database.collection('messagelog').insertOne(
|
||||
{
|
||||
id: args[0].id,
|
||||
messageId: args[0].messageId,
|
||||
parentId: options.parentId,
|
||||
action: 'AUTOREPLY',
|
||||
from: '',
|
||||
to: options.sender,
|
||||
created: new Date()
|
||||
},
|
||||
() => callback(err, args && args[0].id)
|
||||
);
|
||||
}
|
||||
() => callback(err, args && args[0].id)
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
if (message) {
|
||||
compiler
|
||||
.compile()
|
||||
.createReadStream()
|
||||
.pipe(message);
|
||||
}
|
||||
});
|
||||
});
|
||||
if (message) {
|
||||
compiler
|
||||
.compile()
|
||||
.createReadStream()
|
||||
.pipe(message);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
messageSplitter.on('error', () => false);
|
||||
messageSplitter.on('data', () => false);
|
||||
messageSplitter.on('end', () => false);
|
||||
messageSplitter.on('error', () => false);
|
||||
messageSplitter.on('data', () => false);
|
||||
messageSplitter.on('end', () => false);
|
||||
|
||||
setImmediate(() => {
|
||||
let pos = 0;
|
||||
let writeNextChunk = () => {
|
||||
if (messageHeaders || pos >= options.chunks.length) {
|
||||
return messageSplitter.end();
|
||||
}
|
||||
let chunk = options.chunks[pos++];
|
||||
if (!messageSplitter.write(chunk)) {
|
||||
return messageSplitter.once('drain', writeNextChunk);
|
||||
} else {
|
||||
setImmediate(writeNextChunk);
|
||||
}
|
||||
};
|
||||
setImmediate(() => {
|
||||
let pos = 0;
|
||||
let writeNextChunk = () => {
|
||||
if (messageHeaders || pos >= options.chunks.length) {
|
||||
return messageSplitter.end();
|
||||
}
|
||||
let chunk = options.chunks[pos++];
|
||||
if (!messageSplitter.write(chunk)) {
|
||||
return messageSplitter.once('drain', writeNextChunk);
|
||||
} else {
|
||||
setImmediate(writeNextChunk);
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
setImmediate(writeNextChunk);
|
||||
});
|
||||
};
|
||||
|
|
|
@ -380,9 +380,9 @@ class FilterHandler {
|
|||
|
||||
targets: forwardTargets.size
|
||||
? Array.from(forwardTargets).map(row => ({
|
||||
type: row[1].type,
|
||||
value: row[1].value
|
||||
}))
|
||||
type: row[1].type,
|
||||
value: row[1].value
|
||||
}))
|
||||
: false,
|
||||
|
||||
chunks,
|
||||
|
@ -405,20 +405,43 @@ class FilterHandler {
|
|||
return setImmediate(done);
|
||||
}
|
||||
|
||||
autoreply(
|
||||
let curtime = new Date();
|
||||
options.db.database.collection('autoreplies').findOne(
|
||||
{
|
||||
db: this.db,
|
||||
maildrop: this.maildrop,
|
||||
|
||||
parentId: prepared.id,
|
||||
userData,
|
||||
sender,
|
||||
recipient,
|
||||
chunks,
|
||||
chunklen,
|
||||
messageHandler: this.messageHandler
|
||||
user: userData._id,
|
||||
start: {
|
||||
$lte: curtime
|
||||
},
|
||||
end: {
|
||||
$gte: curtime
|
||||
}
|
||||
},
|
||||
done
|
||||
(err, autoreplyData) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
if (!autoreplyData || !autoreplyData.status) {
|
||||
return callback(null, false);
|
||||
}
|
||||
|
||||
autoreply(
|
||||
{
|
||||
db: this.db,
|
||||
maildrop: this.maildrop,
|
||||
|
||||
parentId: prepared.id,
|
||||
userData,
|
||||
sender,
|
||||
recipient,
|
||||
chunks,
|
||||
chunklen,
|
||||
messageHandler: this.messageHandler
|
||||
},
|
||||
autoreplyData,
|
||||
done
|
||||
);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -549,10 +572,10 @@ class FilterHandler {
|
|||
},
|
||||
!isEncrypted
|
||||
? {
|
||||
// reuse parsed values
|
||||
mimeTree: messageOpts.prepared.mimeTree,
|
||||
maildata: messageOpts.maildata
|
||||
}
|
||||
// reuse parsed values
|
||||
mimeTree: messageOpts.prepared.mimeTree,
|
||||
maildata: messageOpts.maildata
|
||||
}
|
||||
: false
|
||||
);
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue