updated autoreplies

This commit is contained in:
Andris Reinman 2017-11-15 15:59:37 +02:00
parent 2af2719f95
commit 77d3c2bc92
5 changed files with 137 additions and 65 deletions

View file

@ -1771,26 +1771,33 @@ Wild Duck supports setting up autoreply messages that are sent to senders by LMT
#### PUT /users/{user}/autoreply #### PUT /users/{user}/autoreply
This call prepares the user to support 2FA tokens. If 2FA is already enabled then this call fails. This call sets up or updates autoreply message for the user.
**Parameters** **Parameters**
- **user** (required) is the ID of the user - **user** (required) is the ID of the user
- **status** is a boolean that indicates if autoreply messages should be sent (true) or not (false) - **status** is a boolean that indicates if autoreply messages should be sent (true) or not (false)
- **subject** is the subject line of autoreply message - **subject** is the subject line of autoreply message
- **message** is text body of the autoreply message - **text** is text body of the autoreply message
- **html** is html body of the autoreply message
- **start** is the start time of the autoreply
- **end** is the end time of the autoreply
**Response fields** **Response fields**
- **success** should be `true` - **success** should be `true`
Autoreply update calls can be done partially, eg. only updating status or subject.
**Example** **Example**
``` ```
curl -XPUT "http://localhost:8080/users/5971da1754cfdc7f0983b2ec/autoreply" -H 'content-type: application/json' -d '{ curl -XPUT "http://localhost:8080/users/5971da1754cfdc7f0983b2ec/autoreply" -H 'content-type: application/json' -d '{
"status": true, "status": true,
"subject": "Out of office", "subject": "Out of office",
"message": "I'm out of office this week" "text": "I'm out of office this week",
"start": "2017-11-15T00:00:00.000Z",
"end": "2017-11-19T00:00:00.000Z",
}' }'
``` ```

View file

@ -40,19 +40,14 @@ indexes:
unique: true unique: true
key: key:
unameview: 1 unameview: 1
- collection: users - collection: users
type: users # index applies to users database type: users # index applies to users database
index: index:
name: show_new name: show_new
key: key:
created: -1 created: -1
- collection: users
type: users # index applies to users database
index:
name: users_namespace
key:
ns: 1
unameview: 1
- collection: users - collection: users
type: users # index applies to users database type: users # index applies to users database
index: index:
@ -62,12 +57,14 @@ indexes:
sparse: true sparse: true
# Indexes for the addresses collection # Indexes for the addresses collection
# note: is it really needed? maybe sort and list using addrview?
- collection: addresses - collection: addresses
type: users # index applies to users database type: users # index applies to users database
index: index:
name: address name: address
key: key:
address: 1 address: 1
- collection: addresses - collection: addresses
type: users # index applies to users database type: users # index applies to users database
index: index:
@ -76,6 +73,7 @@ indexes:
key: key:
addrview: 1 addrview: 1
_id: 1 _id: 1
- collection: addresses - collection: addresses
type: users # index applies to users database type: users # index applies to users database
index: index:
@ -93,7 +91,6 @@ indexes:
user: 1 user: 1
# Indexes for the authentication log collection # Indexes for the authentication log collection
- collection: authlog - collection: authlog
type: users # index applies to users database type: users # index applies to users database
index: index:
@ -130,12 +127,14 @@ indexes:
- collection: autoreplies - collection: autoreplies
index: index:
name: user name: autoreply
key: key:
user: 1 user: 1
start: 1
end: 1
# Indexes for the mailboxes collection # Indexes for the mailboxes collection
# note: should mailboxes collection be sharded? could be by user
- collection: mailboxes - collection: mailboxes
index: index:
name: user_path name: user_path
@ -143,12 +142,14 @@ indexes:
key: key:
user: 1 user: 1
path: 1 path: 1
- collection: mailboxes - collection: mailboxes
index: index:
name: user_subscribed name: user_subscribed
key: key:
user: 1 user: 1
subscribed: 1 subscribed: 1
- collection: mailboxes - collection: mailboxes
index: index:
name: find_by_type name: find_by_type
@ -158,29 +159,6 @@ indexes:
# Indexes for the messages collection # Indexes for the messages collection
- collection: messages
index:
name: user_messages_by_thread
key:
user: 1
thread: 1
- collection: messages
index:
# use also as sharding key
name: mailbox_uid
key:
mailbox: 1
uid: 1
_id: 1
- collection: messages
index:
# use also as sharding key
name: mailbox_uid_reverse
key:
mailbox: 1
uid: -1
_id: -1
- collection: messages - collection: messages
index: index:
# several message related queries include the shard key values # several message related queries include the shard key values
@ -192,10 +170,28 @@ indexes:
- collection: messages - collection: messages
index: index:
name: newer_first name: user_messages_by_thread
key:
user: 1
thread: 1
- collection: messages
index:
# use also as sharding key
name: mailbox_uid
key:
mailbox: 1
uid: 1
_id: 1
- collection: messages
index:
name: mailbox_uid_reverse
key: key:
mailbox: 1 mailbox: 1
uid: -1 uid: -1
_id: -1
- collection: messages - collection: messages
index: index:
name: mailbox_modseq_uid name: mailbox_modseq_uid
@ -203,24 +199,21 @@ indexes:
mailbox: 1 mailbox: 1
modseq: 1 modseq: 1
uid: 1 uid: 1
- collection: messages - collection: messages
index: index:
name: mailbox_flags name: mailbox_flags
key: key:
mailbox: 1 mailbox: 1
flags: 1 flags: 1
- collection: messages
index:
name: by_modseq
key:
mailbox: 1
modseq: 1
- collection: messages - collection: messages
index: index:
name: by_idate name: by_idate
key: key:
mailbox: 1 mailbox: 1
idate: 1 idate: 1
- collection: messages - collection: messages
index: index:
name: by_hdate name: by_hdate
@ -228,12 +221,14 @@ indexes:
mailbox: 1 mailbox: 1
hdate: 1 hdate: 1
msgid: 1 msgid: 1
- collection: messages - collection: messages
index: index:
name: by_size name: by_size
key: key:
mailbox: 1 mailbox: 1
size: 1 size: 1
- collection: messages - collection: messages
index: index:
name: by_headers name: by_headers
@ -241,6 +236,7 @@ indexes:
mailbox: 1 mailbox: 1
headers.key: 1 headers.key: 1
headers.value: 1 headers.value: 1
- collection: messages - collection: messages
index: index:
# there can be only one $text index per collection # there can be only one $text index per collection
@ -252,6 +248,7 @@ indexes:
text: text text: text
partialFilterExpression: partialFilterExpression:
searchable: true searchable: true
- collection: messages - collection: messages
index: index:
# in most cases we only care about unseen, not seen messages # in most cases we only care about unseen, not seen messages
@ -259,6 +256,7 @@ indexes:
key: key:
mailbox: 1 mailbox: 1
unseen: 1 unseen: 1
- collection: messages - collection: messages
index: index:
# some mail agents list messages that do not have the \Deleted flag set # some mail agents list messages that do not have the \Deleted flag set
@ -266,24 +264,28 @@ indexes:
key: key:
mailbox: 1 mailbox: 1
undeleted: 1 undeleted: 1
- collection: messages - collection: messages
index: index:
name: mailbox_flagged_flag name: mailbox_flagged_flag
key: key:
mailbox: 1 mailbox: 1
flagged: 1 flagged: 1
- collection: messages - collection: messages
index: index:
name: mailbox_draft_flag name: mailbox_draft_flag
key: key:
mailbox: 1 mailbox: 1
draft: 1 draft: 1
- collection: messages - collection: messages
index: index:
name: has_attachment name: has_attachment
key: key:
mailbox: 1 mailbox: 1
ha: 1 ha: 1
- collection: messages - collection: messages
index: index:
# This filter finds all messages that are expired and must be deleted. # This filter finds all messages that are expired and must be deleted.
@ -305,6 +307,7 @@ indexes:
name: attachment_id_hashed name: attachment_id_hashed
key: key:
_id: hashed _id: hashed
- collection: attachments.files - collection: attachments.files
type: gridfs # index applies to gridfs database type: gridfs # index applies to gridfs database
index: index:
@ -312,6 +315,7 @@ indexes:
key: key:
metadata.c: 1 metadata.c: 1
metadata.m: 1 metadata.m: 1
- collection: attachments.chunks - collection: attachments.chunks
type: gridfs # index applies to gridfs database type: gridfs # index applies to gridfs database
index: index:
@ -319,6 +323,7 @@ indexes:
name: chunks_shard name: chunks_shard
key: key:
files_id: hashed files_id: hashed
- collection: attachments.chunks - collection: attachments.chunks
type: gridfs # index applies to gridfs database type: gridfs # index applies to gridfs database
index: index:
@ -337,6 +342,7 @@ indexes:
key: key:
mailbox: 1 mailbox: 1
modseq: 1 modseq: 1
- collection: journal - collection: journal
index: index:
# this index is used to send updates to a logged in webmail user # this index is used to send updates to a logged in webmail user
@ -344,6 +350,7 @@ indexes:
key: key:
user: 1 user: 1
_id: 1 _id: 1
- collection: journal - collection: journal
index: index:
# this index is used to find the latest journal entry # this index is used to find the latest journal entry
@ -351,6 +358,7 @@ indexes:
key: key:
user: 1 user: 1
_id: -1 _id: -1
- collection: journal - collection: journal
# delete journal entries after 3 hours # delete journal entries after 3 hours
index: index:
@ -366,12 +374,14 @@ indexes:
name: thread_shard name: thread_shard
key: key:
user: hashed user: hashed
- collection: threads - collection: threads
index: index:
name: thread name: thread
key: key:
user: 1 user: 1
ids: 1 ids: 1
- collection: threads - collection: threads
index: index:
name: thread_autoexpire name: thread_autoexpire
@ -392,7 +402,7 @@ indexes:
name: messagelog_parent name: messagelog_parent
key: key:
parentId: 1 parentId: 1
sparse: true sparse: true # only messages inserted by mail store have parentId set
- collection: messagelog - collection: messagelog
index: index:

View file

@ -8,10 +8,28 @@ module.exports = (db, server) => {
res.charSet('utf-8'); res.charSet('utf-8');
const schema = Joi.object().keys({ const schema = Joi.object().keys({
user: Joi.string().hex().lowercase().length(24).required(), user: Joi.string()
status: Joi.boolean().truthy(['Y', 'true', 'yes', 1]).default(false), .hex()
subject: Joi.string().empty('').trim().max(128), .lowercase()
message: Joi.string().empty('').trim().max(10 * 1024) .length(24)
.required(),
status: Joi.boolean()
.truthy(['Y', 'true', 'yes', 1])
.default(false),
subject: Joi.string()
.empty('')
.trim()
.max(128),
text: Joi.string()
.empty('')
.trim()
.max(128 * 1024),
html: Joi.string()
.empty('')
.trim()
.max(128 * 1024),
start: Joi.date(),
end: Joi.date().min(Joi.ref('start'))
}); });
const result = Joi.validate(req.params, schema, { const result = Joi.validate(req.params, schema, {
@ -30,8 +48,20 @@ module.exports = (db, server) => {
result.value.subject = ''; result.value.subject = '';
} }
if (!result.value.message && 'message' in req.params) { if (!result.value.text && 'text' in req.params) {
result.value.message = ''; result.value.text = '';
if (!result.value.html) {
// make sure we also update html part
result.value.html = '';
}
}
if (!result.value.html && 'html' in req.params) {
result.value.html = '';
if (!result.value.text) {
// make sure we also update plaintext part
result.value.text = '';
}
} }
let user = (result.value.user = new ObjectID(result.value.user)); let user = (result.value.user = new ObjectID(result.value.user));
@ -71,7 +101,11 @@ module.exports = (db, server) => {
res.charSet('utf-8'); res.charSet('utf-8');
const schema = Joi.object().keys({ const schema = Joi.object().keys({
user: Joi.string().hex().lowercase().length(24).required() user: Joi.string()
.hex()
.lowercase()
.length(24)
.required()
}); });
const result = Joi.validate(req.params, schema, { const result = Joi.validate(req.params, schema, {
@ -101,7 +135,10 @@ module.exports = (db, server) => {
success: true, success: true,
status: !!entry.status, status: !!entry.status,
subject: entry.subject || '', subject: entry.subject || '',
message: entry.message || '' text: entry.text || '',
html: entry.html || '',
start: entry.start,
end: entry.end
}); });
return next(); return next();
@ -112,7 +149,11 @@ module.exports = (db, server) => {
res.charSet('utf-8'); res.charSet('utf-8');
const schema = Joi.object().keys({ const schema = Joi.object().keys({
user: Joi.string().hex().lowercase().length(24).required() user: Joi.string()
.hex()
.lowercase()
.length(24)
.required()
}); });
const result = Joi.validate(req.params, schema, { const result = Joi.validate(req.params, schema, {

View file

@ -12,7 +12,16 @@ module.exports = (options, callback) => {
return callback(null, false); return callback(null, false);
} }
db.database.collection('autoreplies').findOne({ user: options.userData._id }, (err, autoreply) => { let curtime = new Date();
db.database.collection('autoreplies').findOne({
user: options.userData._id,
start: {
$gte: curtime
},
end: {
$lte: curtime
}
}, (err, autoreply) => {
if (err) { if (err) {
return callback(err); return callback(err);
} }
@ -53,9 +62,11 @@ module.exports = (options, callback) => {
db.redis db.redis
.multi() .multi()
// delete all old entries // delete all old entries
.zremrangebyscore('war:' + options.userData._id, '-inf', Date.now() - consts.MAX_AUTOREPLY_INTERVAL) .zremrangebyscore('war:' + autoreply._id, '-inf', Date.now() - consts.MAX_AUTOREPLY_INTERVAL)
// add enw entry if not present // add new entry if not present
.zadd('war:' + options.userData._id, 'NX', Date.now(), options.sender) .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) => { .exec((err, result) => {
if (err) { if (err) {
errors.notify(err, { userId: options.userData._id }); errors.notify(err, { userId: options.userData._id });
@ -90,11 +101,13 @@ module.exports = (options, callback) => {
value: 'Auto: Re: ' + headers.getFirst('Subject') value: 'Auto: Re: ' + headers.getFirst('Subject')
}, },
headers: { headers: {
'Auto-Submitted': 'auto-replied' 'Auto-Submitted': 'auto-replied',
'X-WD-Autoreply-For': (options.parentId || '').toString()
}, },
inReplyTo: headers.getFirst('Message-ID'), inReplyTo: headers.getFirst('Message-ID'),
references: (headers.getFirst('Message-ID') + ' ' + headers.getFirst('References')).trim(), references: (headers.getFirst('Message-ID') + ' ' + headers.getFirst('References')).trim(),
text: autoreply.message text: autoreply.text,
html: autoreply.html
}; };
let compiler = new MailComposer(data); let compiler = new MailComposer(data);

View file

@ -1,14 +1,17 @@
'use strict'; 'use strict';
module.exports = { module.exports = {
// home many modifications to cache before writing SCHEMA_VERSION: '1.0',
// how many modifications to cache before writing
BULK_BATCH_SIZE: 150, BULK_BATCH_SIZE: 150,
// how often to clear expired messages // how often to clear expired messages
GC_INTERVAL: 10 * 60 * 1000, GC_INTERVAL: 10 * 60 * 1000,
// artificail delay between deleting next expired message in ms // artificail delay between deleting next expired message in ms
GC_DELAY_DELETE: 100, // set to 0 to disable
GC_DELAY_DELETE: 80,
MAX_STORAGE: 1 * (1024 * 1024 * 1024), MAX_STORAGE: 1 * (1024 * 1024 * 1024),
MAX_RECIPIENTS: 2000, MAX_RECIPIENTS: 2000,
@ -18,15 +21,13 @@ module.exports = {
MAILBOX_COUNTER_TTL: 24 * 3600, MAILBOX_COUNTER_TTL: 24 * 3600,
SCHEMA_VERSION: '1.0',
// how much plaintext to store in a full text indexed field // how much plaintext to store in a full text indexed field
MAX_PLAINTEXT_INDEXED: 2 * 1024, MAX_PLAINTEXT_INDEXED: 1 * 1024,
// how much plaintext to store before truncating // how much plaintext to store before truncating
MAX_PLAINTEXT_CONTENT: 100 * 1024, MAX_PLAINTEXT_CONTENT: 100 * 1024,
// how much HTML content to store. not indexed // how much HTML content to store before truncating. not indexed
MAX_HTML_CONTENT: 300 * 1024, MAX_HTML_CONTENT: 300 * 1024,
MAX_AUTOREPLY_INTERVAL: 4 * 24 * 3600 * 1000, MAX_AUTOREPLY_INTERVAL: 4 * 24 * 3600 * 1000,