mail-time 1.4.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +468 -118
- package/adapters/blank-example.js +230 -0
- package/adapters/mongo.js +310 -0
- package/adapters/redis.js +302 -0
- package/helpers.js +11 -0
- package/index.cjs +968 -341
- package/index.js +407 -392
- package/package.json +8 -7
package/index.js
CHANGED
|
@@ -1,69 +1,12 @@
|
|
|
1
|
-
import JoSk from 'josk';
|
|
1
|
+
import { JoSk, RedisAdapter, MongoAdapter } from 'josk';
|
|
2
2
|
import merge from 'deepmerge';
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
}
|
|
9
|
-
};
|
|
10
|
-
const _logError = (...args) => {
|
|
11
|
-
console.error.call(console, '[ERROR] [mail-time]', `${new Date}`, ...args);
|
|
12
|
-
};
|
|
4
|
+
import crypto from 'crypto';
|
|
5
|
+
import { MongoQueue } from './adapters/mongo.js';
|
|
6
|
+
import { RedisQueue } from './adapters/redis.js';
|
|
7
|
+
import { debug, logError } from './helpers.js';
|
|
13
8
|
|
|
14
|
-
const
|
|
15
|
-
if (error) {
|
|
16
|
-
_logError('[mongoErrorHandler]:', error);
|
|
17
|
-
console.trace();
|
|
18
|
-
}
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Ensure (create) index on MongoDB collection, catch and log exception if thrown
|
|
23
|
-
* @function ensureIndex
|
|
24
|
-
* @param {Collection} collection - Mongo's driver Collection instance
|
|
25
|
-
* @param {object} keys - Field and value pairs where the field is the index key and the value describes the type of index for that field
|
|
26
|
-
* @param {object} opts - Set of options that controls the creation of the index
|
|
27
|
-
* @returns {void 0}
|
|
28
|
-
*/
|
|
29
|
-
const ensureIndex = async (collection, keys, opts) => {
|
|
30
|
-
try {
|
|
31
|
-
await collection.createIndex(keys, opts);
|
|
32
|
-
} catch (e) {
|
|
33
|
-
if (e.code === 85) {
|
|
34
|
-
let indexName;
|
|
35
|
-
const indexes = await collection.indexes();
|
|
36
|
-
for (const index of indexes) {
|
|
37
|
-
let drop = true;
|
|
38
|
-
for (const indexKey of Object.keys(keys)) {
|
|
39
|
-
if (typeof index.key[indexKey] === 'undefined') {
|
|
40
|
-
drop = false;
|
|
41
|
-
break;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
for (const indexKey of Object.keys(index.key)) {
|
|
46
|
-
if (typeof keys[indexKey] === 'undefined') {
|
|
47
|
-
drop = false;
|
|
48
|
-
break;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (drop) {
|
|
53
|
-
indexName = index.name;
|
|
54
|
-
break;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
if (indexName) {
|
|
59
|
-
await collection.dropIndex(indexName);
|
|
60
|
-
await collection.createIndex(keys, opts);
|
|
61
|
-
}
|
|
62
|
-
} else {
|
|
63
|
-
_logError(`[ensureIndex] Can not set ${Object.keys(keys).join(' + ')} index on "${collection._name}" collection`, { keys, opts, details: e });
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
};
|
|
9
|
+
const noop = () => {};
|
|
67
10
|
|
|
68
11
|
/**
|
|
69
12
|
* Check if entities of various types are equal
|
|
@@ -148,39 +91,81 @@ const equals = (a, b) => {
|
|
|
148
91
|
let DEFAULT_TEMPLATE = '<!DOCTYPE html><html xmlns=http://www.w3.org/1999/xhtml><meta content="text/html; charset=utf-8"http-equiv=Content-Type><meta content="width=device-width,initial-scale=1"name=viewport><title>{{subject}}</title><style>body{-webkit-font-smoothing:antialiased;-webkit-text-size-adjust:none;font-family:Tiempos,Georgia,Times,serif;font-weight:400;width:100%;height:100%;background:#fff;font-size:15px;color:#000;line-height:1.5}a{text-decoration:underline;border:0;color:#000;outline:0;color:inherit}a:hover{text-decoration:none}a[href^=sms],a[href^=tel]{text-decoration:none;color:#000;cursor:default}a img{border:none;text-decoration:none}td{font-family:Tiempos,Georgia,Times,serif;font-weight:400}hr{height:1px;border:none;width:100%;margin:0;margin-top:25px;margin-bottom:25px;background-color:#ECECEC}h1,h2,h3,h4,h5,h6{font-family:HelveticaNeue,"Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:300;line-height:normal;margin-top:35px;margin-bottom:4px;margin-left:0;margin-right:0}h1{margin:23px 15px;font-size:25px}h2{margin-top:15px;font-size:21px}h3{font-weight:400;font-size:19px;border-bottom:1px solid #ECECEC}h4{font-weight:400;font-size:18px}h5{font-weight:400;font-size:17px}h6{font-weight:600;font-size:16px}h1 a,h2 a,h3 a,h4 a,h5 a,h6 a{text-decoration:none}pre{font-family:Consolas,Menlo,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New,monospace,sans-serif;display:block;font-size:13px;padding:9.5px;margin:0 0 10px;line-height:1.42;color:#333;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px;text-align:left!important;max-width:100%;white-space:pre-wrap;width:auto;overflow:auto}code{font-size:13px;font-family:font-family: Consolas,Menlo,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New,monospace,sans-serif;border:1px solid rgba(0,0,0,.223);border-radius:2px;padding:1px 2px;word-break:break-all;word-wrap:break-word}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border:none;border-radius:0;word-break:break-all;word-wrap:break-word}td{text-align:center}table{border-collapse:collapse!important}.force-full-width{width:100%!important}</style><style media=screen>@media screen{h1,h2,h3,h4,h5,h6{font-family:\'Helvetica Neue\',Arial,sans-serif!important}td{font-family:Tiempos,Georgia,Times,serif!important}code,pre{font-family:Consolas,Menlo,Monaco,\'Lucida Console\',\'Liberation Mono\',\'DejaVu Sans Mono\',\'Bitstream Vera Sans Mono\',\'Courier New\',monospace,sans-serif!important}}</style><style media="only screen and (max-width:480px)">@media only screen and (max-width:480px){table[class=w320]{width:100%!important}}</style><body bgcolor=#FFFFFF class=body style=padding:0;margin:0;display:block;background:#fff;-webkit-text-size-adjust:none><table cellpadding=0 cellspacing=0 width=100% align=center><tr><td align=center valign=top bgcolor=#FFFFFF width=100%><center><table cellpadding=0 cellspacing=0 width=600 style="margin:0 auto"class=w320><tr><td align=center valign=top><table cellpadding=0 cellspacing=0 width=100% style="margin:0 auto;border-bottom:1px solid #ddd"bgcolor=#ECECEC><tr><td><h1>{{{subject}}}</h1></table><table cellpadding=0 cellspacing=0 width=100% style="margin:0 auto"bgcolor=#F2F2F2><tr><td><center><table cellpadding=0 cellspacing=0 width=100% style="margin:0 auto"><tr><td align=left style="text-align:left;padding:30px 25px">{{{html}}}</table></center></table></table></center></table>';
|
|
149
92
|
|
|
150
93
|
/** Class of MailTime */
|
|
151
|
-
|
|
94
|
+
class MailTime {
|
|
95
|
+
/**
|
|
96
|
+
* Create a MailTime instance
|
|
97
|
+
* @param {object} opts - configuration object
|
|
98
|
+
* @param {RedisQueue|MongoQueue|CustomQueue} opts.queue - Queue Storage Driver instance
|
|
99
|
+
* @param {string} [opts.type] - "server" or "client" type of MailTime instance
|
|
100
|
+
* @param {function} [opts.from] - A function returning *String* for `from` header, format: `"MyApp" <user@example.com>`
|
|
101
|
+
* @param {[object]} [opts.transports] - An array of `nodemailer`'s transports, returned from `nodemailer.createTransport({})`; Required for `{type: 'server'}`
|
|
102
|
+
* @param {string} [opts.strategy] - `backup` or `balancer`
|
|
103
|
+
* @param {number} [opts.failsToNext] - After how many failed "send attempts" switch to the next transport? Applied only for `backup` strategy, default - `4`
|
|
104
|
+
* @param {number} [opts.retries] - How many times resend failed emails
|
|
105
|
+
* @param {number} [opts.retryDelay] - Interval in milliseconds between send re-tries
|
|
106
|
+
* @param {boolean} [opts.keepHistory] - Keep queue task as it is in the database
|
|
107
|
+
* @param {boolean} [opts.concatEmails] - Concatenate email by `to` field, default - `false`
|
|
108
|
+
* @param {string} [opts.concatSubject] - Email subject used in concatenated email, default - `Multiple notifications`
|
|
109
|
+
* @param {string} [opts.concatDelimiter] - HTML or plain string delimiter used between concatenated email, default - `<hr>`
|
|
110
|
+
* @param {number} [opts.concatDelay] - HTML or plain string delimiter used between concatenated email, default - `<hr>`
|
|
111
|
+
* @param {number} [opts.revolvingInterval] - Interval in *milliseconds* in between queue checks, default - `256`
|
|
112
|
+
* @param {object|RedisAdapter|MongoAdapter|CustomAdapter} [opts.josk.adapter] - Interval in milliseconds between send re-tries
|
|
113
|
+
* @param {string} [opts.josk.adapter.type] - One of `mongo` or `redis`
|
|
114
|
+
* @param {string} [opts.prefix] - Optional prefix for scope isolation; use when creating multiple MailTime instances within the single application; By default prefix inherited from MailTime instance
|
|
115
|
+
*/
|
|
152
116
|
constructor (opts) {
|
|
153
117
|
if (!opts || typeof opts !== 'object' || opts === null) {
|
|
154
118
|
throw new TypeError('[mail-time] Configuration object must be passed into MailTime constructor');
|
|
155
119
|
}
|
|
156
120
|
|
|
157
|
-
if (!opts.
|
|
158
|
-
throw new Error('[mail-time]
|
|
121
|
+
if (!opts.queue || typeof opts.queue !== 'object') {
|
|
122
|
+
throw new Error('[mail-time] {queue} option is required', {
|
|
123
|
+
description: 'MailTime requires MongoQueue, RedisQueue, or CustomQueue to connect to an intermediate database'
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
this.queue = opts.queue;
|
|
128
|
+
this.queue.mailTimeInstance = this;
|
|
129
|
+
const queueMethods = ['ping', 'iterate', 'getPendingTo', 'push', 'remove', 'update', 'cancel'];
|
|
130
|
+
|
|
131
|
+
for (let i = queueMethods.length - 1; i >= 0; i--) {
|
|
132
|
+
if (typeof this.queue[queueMethods[i]] !== 'function') {
|
|
133
|
+
throw new Error(`{queue} instance is missing {${queueMethods[i]}} method that is required!`);
|
|
134
|
+
}
|
|
159
135
|
}
|
|
160
136
|
|
|
161
|
-
this.callbacks = {};
|
|
162
|
-
this.type = (!opts.type || (opts.type !== 'client' && opts.type !== 'server')) ? 'server' : opts.type;
|
|
163
137
|
this.debug = (opts.debug !== true) ? false : true;
|
|
164
|
-
this.
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
this.template = (typeof opts.template === 'string') ? opts.template : '{{{html}}}';
|
|
168
|
-
this.zombieTime = opts.zombieTime || 32786;
|
|
169
|
-
this.keepHistory = opts.keepHistory || false;
|
|
138
|
+
this._debug = (...args) => {
|
|
139
|
+
debug(this.debug, ...args);
|
|
140
|
+
};
|
|
170
141
|
|
|
171
|
-
this.
|
|
172
|
-
this.
|
|
173
|
-
this.maxRevolvingDelay = opts.maxRevolvingDelay || 2048;
|
|
142
|
+
this.type = (typeof opts.type !== 'string' || (opts.type !== 'client' && opts.type !== 'server')) ? 'server' : opts.type;
|
|
143
|
+
this.prefix = (typeof opts.prefix === 'string') ? opts.prefix : '';
|
|
174
144
|
|
|
175
|
-
if (
|
|
176
|
-
this.
|
|
145
|
+
if (typeof opts.retries === 'number') {
|
|
146
|
+
this.maxTries = opts.retries + 1;
|
|
147
|
+
} else if (typeof opts.maxTries === 'number') {
|
|
148
|
+
this.maxTries = (opts.maxTries < 1) ? 1 : 0;
|
|
149
|
+
} else {
|
|
150
|
+
this.maxTries = 60;
|
|
177
151
|
}
|
|
178
152
|
|
|
179
|
-
if (
|
|
180
|
-
this.
|
|
153
|
+
if (typeof opts.retryDelay === 'number') {
|
|
154
|
+
this.retryDelay = opts.retryDelay;
|
|
155
|
+
} else if (typeof opts.interval === 'number') {
|
|
156
|
+
this.retryDelay = opts.interval * 1000;
|
|
157
|
+
} else {
|
|
158
|
+
this.retryDelay = 60000;
|
|
181
159
|
}
|
|
182
160
|
|
|
183
|
-
this.
|
|
161
|
+
this.template = (typeof opts.template === 'string') ? opts.template : '{{{html}}}';
|
|
162
|
+
this.keepHistory = (typeof opts.keepHistory === 'boolean') ? opts.keepHistory : false;
|
|
163
|
+
this.onSent = opts.onSent || noop;
|
|
164
|
+
this.onError = opts.onError || noop;
|
|
165
|
+
|
|
166
|
+
this.revolvingInterval = opts.revolvingInterval || 1536;
|
|
167
|
+
|
|
168
|
+
this.failsToNext = (typeof opts.failsToNext === 'number') ? opts.failsToNext : 4;
|
|
184
169
|
this.strategy = (opts.strategy === 'backup' || opts.strategy === 'balancer') ? opts.strategy : 'backup';
|
|
185
170
|
this.transports = opts.transports || [];
|
|
186
171
|
this.transport = 0;
|
|
@@ -201,348 +186,247 @@ export default class MailTime {
|
|
|
201
186
|
this.concatEmails = (opts.concatEmails !== true) ? false : true;
|
|
202
187
|
this.concatSubject = (opts.concatSubject && typeof opts.concatSubject === 'string') ? opts.concatSubject : 'Multiple notifications';
|
|
203
188
|
this.concatDelimiter = (opts.concatDelimiter && typeof opts.concatDelimiter === 'string') ? opts.concatDelimiter : '<hr>';
|
|
204
|
-
this.concatThrottling = ((opts.concatThrottling && !isNaN(opts.concatThrottling)) ? parseInt(opts.concatThrottling) : 60) * 1000;
|
|
205
|
-
|
|
206
|
-
if (this.concatThrottling < 2048) {
|
|
207
|
-
this.concatThrottling = 3072;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
_debug(this.debug, 'DEBUG ON {debug: true}');
|
|
211
|
-
_debug(this.debug, `INITIALIZING [type: ${this.type}]`);
|
|
212
|
-
_debug(this.debug, `INITIALIZING [strategy: ${this.strategy}]`);
|
|
213
|
-
_debug(this.debug, `INITIALIZING [prefix: ${this.prefix}]`);
|
|
214
|
-
_debug(this.debug, `INITIALIZING [maxTries: ${this.maxTries}]`);
|
|
215
|
-
_debug(this.debug, `INITIALIZING [failsToNext: ${this.failsToNext}]`);
|
|
216
|
-
|
|
217
|
-
if (opts.type === 'server' && (this.transports.constructor !== Array || !this.transports.length)) {
|
|
218
|
-
throw new Error('[mail-time] transports is required and must be an Array, like returned from `nodemailer.createTransport`');
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
this.collection = opts.db.collection(`__mailTimeQueue__${this.prefix}`);
|
|
222
|
-
ensureIndex(this.collection, { isSent: 1, to: 1, sendAt: 1 });
|
|
223
|
-
ensureIndex(this.collection, { isSent: 1, sendAt: 1, tries: 1 }, { background: true });
|
|
224
|
-
|
|
225
|
-
// MongoDB Collection Schema:
|
|
226
|
-
// _id
|
|
227
|
-
// to {String|[String]}
|
|
228
|
-
// tries {Number} - qty of send attempts
|
|
229
|
-
// sendAt {Date} - When letter should be sent
|
|
230
|
-
// isSent {Boolean} - Email status
|
|
231
|
-
// template {String} - Template for this email
|
|
232
|
-
// transport {Number} - Last used transport
|
|
233
|
-
// concatSubject {String|Boolean} - Email concatenation subject
|
|
234
|
-
// ---
|
|
235
|
-
// mailOptions {[Object]} - Array of nodeMailer's `mailOptions`
|
|
236
|
-
// mailOptions.to {String|Array} - [REQUIRED]
|
|
237
|
-
// mailOptions.from {String}
|
|
238
|
-
// mailOptions.text {String|Boolean}
|
|
239
|
-
// mailOptions.html (String)
|
|
240
|
-
// mailOptions.subject {String}
|
|
241
|
-
// mailOptions.Other nodeMailer `sendMail` options...
|
|
242
|
-
|
|
243
|
-
if (this.type === 'server') {
|
|
244
|
-
const scheduler = new JoSk({
|
|
245
|
-
db: opts.db,
|
|
246
|
-
debug: this.debug,
|
|
247
|
-
prefix: `mailTimeQueue${this.prefix}`,
|
|
248
|
-
resetOnInit: false,
|
|
249
|
-
zombieTime: this.zombieTime,
|
|
250
|
-
minRevolvingDelay: this.minRevolvingDelay,
|
|
251
|
-
maxRevolvingDelay: this.maxRevolvingDelay
|
|
252
|
-
});
|
|
253
189
|
|
|
254
|
-
|
|
190
|
+
if (typeof opts.concatDelay === 'number') {
|
|
191
|
+
this.concatDelay = opts.concatDelay;
|
|
192
|
+
} else if (typeof opts.concatThrottling === 'number') {
|
|
193
|
+
this.concatDelay = opts.concatThrottling * 1000;
|
|
194
|
+
} else {
|
|
195
|
+
this.concatDelay = 60000;
|
|
255
196
|
}
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
static get Template() {
|
|
259
|
-
return DEFAULT_TEMPLATE;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
static set Template(newVal) {
|
|
263
|
-
DEFAULT_TEMPLATE = newVal;
|
|
264
|
-
}
|
|
265
197
|
|
|
266
|
-
|
|
267
|
-
|
|
198
|
+
this._debug('DEBUG ON {debug: true}');
|
|
199
|
+
this._debug(`INITIALIZING [type: ${this.type}]`);
|
|
200
|
+
this._debug(`INITIALIZING [strategy: ${this.strategy}]`);
|
|
201
|
+
this._debug(`INITIALIZING [josk.adapter: ${opts?.josk?.adapter}]`);
|
|
202
|
+
this._debug(`INITIALIZING [prefix: ${this.prefix}]`);
|
|
203
|
+
this._debug(`INITIALIZING [retries: ${this.retries}]`);
|
|
204
|
+
this._debug(`INITIALIZING [failsToNext: ${this.failsToNext}]`);
|
|
205
|
+
this._debug(`INITIALIZING [onError: ${this.onError}]`);
|
|
206
|
+
this._debug(`INITIALIZING [onSent: ${this.onSent}]`);
|
|
268
207
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
208
|
+
/** SERVER-SPECIFIC CHECKS AND CONFIG */
|
|
209
|
+
if (this.type === 'server') {
|
|
210
|
+
if (this.transports.constructor !== Array || !this.transports.length) {
|
|
211
|
+
throw new Error('[mail-time] {transports} is required for {type: "server"} and must be an Array, like returned from `nodemailer.createTransport`');
|
|
212
|
+
}
|
|
272
213
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
214
|
+
if (typeof opts.josk !== 'object') {
|
|
215
|
+
throw new Error('[mail-time] {josk} option is required {object} for {type: "server"}');
|
|
216
|
+
}
|
|
276
217
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
218
|
+
if (typeof opts.josk.adapter !== 'object') {
|
|
219
|
+
throw new Error('[mail-time] {josk.adapter} option is required {object} *or* custom adapter Class');
|
|
220
|
+
}
|
|
280
221
|
|
|
281
|
-
|
|
282
|
-
html: '',
|
|
283
|
-
text: '',
|
|
284
|
-
subject: ''
|
|
285
|
-
});
|
|
222
|
+
this.josk = { ...opts.josk };
|
|
286
223
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
_mailOpts.html += this.___render(this.concatDelimiter, task.mailOptions[i]) + this.___render(task.mailOptions[i].html, task.mailOptions[i]);
|
|
291
|
-
} else {
|
|
292
|
-
_mailOpts.html = this.___render(task.mailOptions[i].html, task.mailOptions[i]);
|
|
224
|
+
if (typeof opts.josk.adapter?.type === 'string' && opts.josk.adapter?.type === 'mongo') {
|
|
225
|
+
if (!opts.josk.adapter.db) {
|
|
226
|
+
throw new Error('[mail-time] {josk.adapter.db} option required for {josk.adapter.type: "mongo"}');
|
|
293
227
|
}
|
|
294
|
-
|
|
228
|
+
this.josk.adapter = new MongoAdapter({ prefix: `mailTimeQueue${this.prefix}`, ...opts.josk.adapter });
|
|
295
229
|
}
|
|
296
230
|
|
|
297
|
-
if (
|
|
298
|
-
if (
|
|
299
|
-
|
|
300
|
-
} else {
|
|
301
|
-
_mailOpts.text = this.___render(task.mailOptions[i].text, task.mailOptions[i]);
|
|
231
|
+
if (typeof opts.josk.adapter?.type === 'string' && opts.josk.adapter?.type === 'redis') {
|
|
232
|
+
if (!opts.josk.adapter.client) {
|
|
233
|
+
throw new Error('[mail-time] {josk.adapter.client} option required for {josk.adapter.type: "redis"}');
|
|
302
234
|
}
|
|
303
|
-
|
|
235
|
+
this.josk.adapter = new RedisAdapter({ prefix: `mailTimeQueue${this.prefix}`, ...opts.josk.adapter });
|
|
304
236
|
}
|
|
305
237
|
|
|
306
|
-
|
|
307
|
-
|
|
238
|
+
this.josk.minRevolvingDelay = (typeof opts.josk.minRevolvingDelay === 'number') ? opts.josk.minRevolvingDelay : 512;
|
|
239
|
+
this.josk.maxRevolvingDelay = (typeof opts.josk.maxRevolvingDelay === 'number') ? opts.josk.maxRevolvingDelay : 2048;
|
|
240
|
+
this.josk.zombieTime = (typeof opts.josk.zombieTime === 'number') ? opts.josk.zombieTime : 32786;
|
|
308
241
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
242
|
+
this.scheduler = new JoSk({
|
|
243
|
+
debug: this.debug,
|
|
244
|
+
...this.josk,
|
|
245
|
+
});
|
|
312
246
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
247
|
+
process.nextTick(async () => {
|
|
248
|
+
if ((await this.scheduler.ping()).status !== 'OK') {
|
|
249
|
+
throw new Error('[mail-time] [MailTime#ping] can not connect to storage, make sure it is available and properly configured');
|
|
250
|
+
}
|
|
251
|
+
});
|
|
316
252
|
|
|
317
|
-
|
|
318
|
-
_mailOpts.from = this.from(transport);
|
|
253
|
+
this.scheduler.setInterval(this.___iterate.bind(this), this.revolvingInterval, `mailTimeQueue${this.prefix}`);
|
|
319
254
|
}
|
|
255
|
+
}
|
|
320
256
|
|
|
321
|
-
|
|
257
|
+
static get Template() {
|
|
258
|
+
return DEFAULT_TEMPLATE;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
static set Template(newVal) {
|
|
262
|
+
DEFAULT_TEMPLATE = newVal;
|
|
322
263
|
}
|
|
323
264
|
|
|
324
265
|
/**
|
|
266
|
+
* @async
|
|
325
267
|
* @memberOf MailTime
|
|
326
|
-
* @name
|
|
327
|
-
* @
|
|
328
|
-
* @returns {
|
|
268
|
+
* @name ping
|
|
269
|
+
* @description Check package readiness and connection to Storage
|
|
270
|
+
* @returns {Promise<object>}
|
|
271
|
+
* @throws {mix}
|
|
329
272
|
*/
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
$lt: this.maxTries
|
|
338
|
-
}
|
|
339
|
-
}, {
|
|
340
|
-
projection: {
|
|
341
|
-
_id: 1,
|
|
342
|
-
tries: 1,
|
|
343
|
-
template: 1,
|
|
344
|
-
transport: 1,
|
|
345
|
-
mailOptions: 1,
|
|
346
|
-
concatSubject: 1
|
|
347
|
-
}
|
|
348
|
-
});
|
|
349
|
-
|
|
350
|
-
cursor.forEach(async (task) => {
|
|
351
|
-
try {
|
|
352
|
-
await this.collection.updateOne({
|
|
353
|
-
_id: task._id
|
|
354
|
-
}, {
|
|
355
|
-
$set: {
|
|
356
|
-
isSent: true
|
|
357
|
-
},
|
|
358
|
-
$inc: {
|
|
359
|
-
tries: 1
|
|
360
|
-
}
|
|
361
|
-
});
|
|
362
|
-
|
|
363
|
-
let transport;
|
|
364
|
-
let transportIndex;
|
|
365
|
-
if (this.strategy === 'balancer') {
|
|
366
|
-
this.transport = this.transport + 1;
|
|
367
|
-
if (this.transport >= this.transports.length) {
|
|
368
|
-
this.transport = 0;
|
|
369
|
-
}
|
|
370
|
-
transportIndex = this.transport;
|
|
371
|
-
transport = this.transports[transportIndex];
|
|
372
|
-
} else {
|
|
373
|
-
transportIndex = task.transport;
|
|
374
|
-
transport = this.transports[transportIndex];
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
const _mailOpts = this.___compileMailOpts(transport, task);
|
|
378
|
-
|
|
379
|
-
_debug(this.debug, '[sendMail] [sending] To:', _mailOpts.to);
|
|
380
|
-
transport.sendMail(_mailOpts, (error, info) => {
|
|
381
|
-
if (error) {
|
|
382
|
-
this.___handleError(task, error, info);
|
|
383
|
-
return;
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
if (info.accepted && !info.accepted.length) {
|
|
387
|
-
this.___handleError(task, 'Message not accepted or Greeting never received', info);
|
|
388
|
-
return;
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
_debug(this.debug, `email successfully sent, attempts: #${task.tries}, transport #${transportIndex} to: `, _mailOpts.to);
|
|
392
|
-
|
|
393
|
-
if (!this.keepHistory) {
|
|
394
|
-
this.collection.deleteOne({
|
|
395
|
-
_id: task._id
|
|
396
|
-
}).catch(mongoErrorHandler);
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
this.___triggerCallbacks(void 0, task, info);
|
|
400
|
-
return;
|
|
401
|
-
});
|
|
402
|
-
} catch (e) {
|
|
403
|
-
_logError('Exception during runtime:', e);
|
|
404
|
-
this.___handleError(task, e, {});
|
|
405
|
-
}
|
|
406
|
-
}).catch((forEachError) => {
|
|
407
|
-
_logError('[___send] [forEach] [forEachError]', forEachError);
|
|
408
|
-
}).finally(() => {
|
|
409
|
-
ready();
|
|
410
|
-
cursor.close();
|
|
411
|
-
});
|
|
273
|
+
async ping() {
|
|
274
|
+
this._debug('[ping]');
|
|
275
|
+
const schedulerPing = await this.scheduler.ping();
|
|
276
|
+
if (schedulerPing.status !== 'OK') {
|
|
277
|
+
return schedulerPing;
|
|
278
|
+
}
|
|
279
|
+
return await this.queue.ping();
|
|
412
280
|
}
|
|
413
281
|
|
|
414
282
|
/**
|
|
415
283
|
* @memberOf MailTime
|
|
416
284
|
* @name send
|
|
417
285
|
* @description alias of `sendMail`
|
|
418
|
-
* @returns {
|
|
286
|
+
* @returns {Promise<string>} uuid of the email
|
|
419
287
|
*/
|
|
420
|
-
send(opts
|
|
421
|
-
this.
|
|
288
|
+
async send(opts) {
|
|
289
|
+
this._debug('[send]', opts);
|
|
290
|
+
return await this.sendMail(opts);
|
|
422
291
|
}
|
|
423
292
|
|
|
424
293
|
/**
|
|
294
|
+
* @async
|
|
425
295
|
* @memberOf MailTime
|
|
426
296
|
* @name sendMail
|
|
427
|
-
* @
|
|
428
|
-
* @param opts
|
|
429
|
-
* @param opts.
|
|
430
|
-
* @param opts
|
|
431
|
-
* @param
|
|
432
|
-
* @returns {
|
|
297
|
+
* @description add email to the queue or append to existing letter if {concatEmails: true}
|
|
298
|
+
* @param opts {object} - email options
|
|
299
|
+
* @param opts.sendAt {Date|number} - When email should be sent
|
|
300
|
+
* @param opts.template {string} - Email-specific template
|
|
301
|
+
* @param opts[key] {mix} - Other NodeMailer's options
|
|
302
|
+
* @returns {Promise<string>} uuid of the email
|
|
303
|
+
* @throws {Error}
|
|
433
304
|
*/
|
|
434
|
-
sendMail(opts = {}
|
|
435
|
-
_debug(
|
|
305
|
+
async sendMail(opts = {}) {
|
|
306
|
+
this._debug('[sendMail]', opts);
|
|
436
307
|
if (!opts.html && !opts.text) {
|
|
437
308
|
throw new Error('`html` nor `text` field is presented, at least one of those fields is required');
|
|
438
309
|
}
|
|
439
310
|
|
|
440
|
-
|
|
441
|
-
|
|
311
|
+
let sendAt = opts.sendAt;
|
|
312
|
+
if (!sendAt) {
|
|
313
|
+
sendAt = Date.now();
|
|
442
314
|
}
|
|
443
315
|
|
|
444
|
-
if (
|
|
445
|
-
|
|
316
|
+
if (sendAt instanceof Date) {
|
|
317
|
+
sendAt = +sendAt;
|
|
446
318
|
}
|
|
447
319
|
|
|
448
|
-
if (typeof
|
|
449
|
-
|
|
320
|
+
if (typeof sendAt !== 'number') {
|
|
321
|
+
sendAt = Date.now();
|
|
450
322
|
}
|
|
451
323
|
|
|
452
|
-
let
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
324
|
+
let template = opts.template;
|
|
325
|
+
if (typeof template !== 'string') {
|
|
326
|
+
template = false;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
let concatSubject = opts.concatSubject;
|
|
330
|
+
if (typeof concatSubject !== 'string') {
|
|
331
|
+
concatSubject = false;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const mailOptions = { ...opts };
|
|
335
|
+
delete mailOptions.sendAt;
|
|
336
|
+
delete mailOptions.template;
|
|
337
|
+
delete mailOptions.concatSubject;
|
|
458
338
|
|
|
459
|
-
if (typeof
|
|
339
|
+
if (typeof mailOptions.to !== 'string' && (!(mailOptions.to instanceof Array) || !mailOptions.to.length)) {
|
|
460
340
|
throw new Error('[mail-time] `mailOptions.to` is required and must be a string or non-empty Array');
|
|
461
341
|
}
|
|
462
342
|
|
|
463
343
|
if (this.concatEmails) {
|
|
464
|
-
|
|
465
|
-
this.
|
|
466
|
-
to: opts.to,
|
|
467
|
-
isSent: false,
|
|
468
|
-
sendAt: {
|
|
469
|
-
$lte: _sendAt
|
|
470
|
-
}
|
|
471
|
-
}, {
|
|
472
|
-
projection: {
|
|
473
|
-
_id: 1,
|
|
474
|
-
mailOptions: 1
|
|
475
|
-
}
|
|
476
|
-
}).then((task) => {
|
|
477
|
-
if (task) {
|
|
478
|
-
const queue = task.mailOptions || [];
|
|
479
|
-
|
|
480
|
-
for (let i = 0; i < queue.length; i++) {
|
|
481
|
-
if (equals(queue[i], opts)) {
|
|
482
|
-
return;
|
|
483
|
-
}
|
|
484
|
-
}
|
|
344
|
+
sendAt = sendAt + this.concatDelay;
|
|
345
|
+
const task = await this.queue.getPendingTo(mailOptions.to, sendAt);
|
|
485
346
|
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
_id: task._id
|
|
489
|
-
}, {
|
|
490
|
-
$set: {
|
|
491
|
-
mailOptions: queue
|
|
492
|
-
}
|
|
493
|
-
}).then(() => {
|
|
494
|
-
const _id = task._id.toHexString();
|
|
495
|
-
if (!this.callbacks[_id]) {
|
|
496
|
-
this.callbacks[_id] = [];
|
|
497
|
-
}
|
|
498
|
-
this.callbacks[_id].push(callback);
|
|
499
|
-
}).catch(mongoErrorHandler);
|
|
500
|
-
return;
|
|
501
|
-
}
|
|
347
|
+
if (task) {
|
|
348
|
+
const pendingMailOptions = task.mailOptions || [];
|
|
502
349
|
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
}, callback);
|
|
509
|
-
}).catch(mongoErrorHandler);
|
|
350
|
+
for (let i = 0; i < pendingMailOptions.length; i++) {
|
|
351
|
+
if (equals(pendingMailOptions[i], mailOptions)) {
|
|
352
|
+
return task.uuid;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
510
355
|
|
|
511
|
-
|
|
356
|
+
pendingMailOptions.push(opts);
|
|
357
|
+
await this.queue.update(task, {
|
|
358
|
+
mailOptions: pendingMailOptions
|
|
359
|
+
});
|
|
360
|
+
return task.uuid;
|
|
361
|
+
}
|
|
512
362
|
}
|
|
513
363
|
|
|
514
|
-
this.___addToQueue({
|
|
515
|
-
sendAt
|
|
516
|
-
template
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
}
|
|
364
|
+
return await this.___addToQueue({
|
|
365
|
+
sendAt,
|
|
366
|
+
template,
|
|
367
|
+
concatSubject,
|
|
368
|
+
mailOptions: mailOptions,
|
|
369
|
+
});
|
|
370
|
+
}
|
|
520
371
|
|
|
521
|
-
|
|
372
|
+
/**
|
|
373
|
+
* @memberOf MailTime
|
|
374
|
+
* @name cancel
|
|
375
|
+
* @description alias of `cancelMail`
|
|
376
|
+
* @returns {Promise<boolean>} returns `true` if cancelled or `false` if not found was sent or was cancelled previously
|
|
377
|
+
*/
|
|
378
|
+
async cancel(uuid) {
|
|
379
|
+
this._debug('[cancel]', uuid);
|
|
380
|
+
return await this.cancelMail(uuid);
|
|
522
381
|
}
|
|
523
382
|
|
|
524
383
|
/**
|
|
384
|
+
* @async
|
|
385
|
+
* @memberOf MailTime
|
|
386
|
+
* @name cancelMail
|
|
387
|
+
* @description remove email from the queue or mark as `isCancelled`
|
|
388
|
+
* @param uuid {string|Promise<string>} - uuid returned from `send` or `sendMail`
|
|
389
|
+
* @returns {Promise<boolean>} returns `true` if cancelled or `false` if not found was sent or was cancelled previously
|
|
390
|
+
*/
|
|
391
|
+
async cancelMail(uuid) {
|
|
392
|
+
this._debug('[cancelMail]', uuid);
|
|
393
|
+
if (typeof uuid === 'object' && uuid instanceof Promise) {
|
|
394
|
+
return await this.queue.cancel(await uuid);
|
|
395
|
+
}
|
|
396
|
+
return await this.queue.cancel(uuid);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* @async
|
|
525
401
|
* @memberOf MailTime
|
|
526
402
|
* @name ___handleError
|
|
527
|
-
* @
|
|
528
|
-
* @param
|
|
529
|
-
* @param
|
|
530
|
-
* @
|
|
403
|
+
* @description Handle runtime errors and pass to `onError` callback
|
|
404
|
+
* @param task {object} - Email task record form Storage
|
|
405
|
+
* @param error {mix} - Error String/Object/Error
|
|
406
|
+
* @param info {object} - Info object returned from nodemailer
|
|
407
|
+
* @returns {Promise<void 0>}
|
|
531
408
|
*/
|
|
532
|
-
___handleError(task, error, info) {
|
|
409
|
+
async ___handleError(task, error, info) {
|
|
410
|
+
this._debug('[private handleError]', { task, error, info });
|
|
533
411
|
if (!task) {
|
|
534
412
|
return;
|
|
535
413
|
}
|
|
536
414
|
|
|
537
415
|
if (task.tries >= this.maxTries) {
|
|
416
|
+
task.isSent = false;
|
|
417
|
+
task.isFailed = true;
|
|
418
|
+
|
|
538
419
|
if (!this.keepHistory) {
|
|
539
|
-
this.
|
|
540
|
-
|
|
541
|
-
|
|
420
|
+
await this.queue.remove(task);
|
|
421
|
+
} else {
|
|
422
|
+
await this.queue.update(task, {
|
|
423
|
+
isSent: task.isSent,
|
|
424
|
+
isFailed: task.isFailed,
|
|
425
|
+
});
|
|
542
426
|
}
|
|
543
427
|
|
|
544
|
-
_debug(
|
|
545
|
-
this.
|
|
428
|
+
this._debug(`[private handleError] Giving up trying send email after ${task.tries} attempts to: `, task.mailOptions[0].to, error);
|
|
429
|
+
this.onError(error, task, info);
|
|
546
430
|
return;
|
|
547
431
|
}
|
|
548
432
|
|
|
@@ -555,61 +439,57 @@ export default class MailTime {
|
|
|
555
439
|
}
|
|
556
440
|
}
|
|
557
441
|
|
|
558
|
-
this.
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
sendAt: new Date(Date.now() + this.interval),
|
|
564
|
-
transport: transportIndex
|
|
565
|
-
}
|
|
566
|
-
}).catch(mongoErrorHandler);
|
|
442
|
+
await this.queue.update(task, {
|
|
443
|
+
isSent: false,
|
|
444
|
+
sendAt: Date.now() + this.retryDelay,
|
|
445
|
+
transport: transportIndex,
|
|
446
|
+
});
|
|
567
447
|
|
|
568
|
-
_debug(
|
|
448
|
+
this._debug(`[private handleError] Next re-send attempt at ${new Date(Date.now() + this.retryDelay)}: #${task.tries}/${this.maxTries}, transport #${transportIndex} to: `, task.mailOptions[0].to, error);
|
|
569
449
|
}
|
|
570
450
|
|
|
571
451
|
/**
|
|
452
|
+
* @async
|
|
572
453
|
* @memberOf MailTime
|
|
573
454
|
* @name ___addToQueue
|
|
574
|
-
* @
|
|
575
|
-
* @param opts
|
|
576
|
-
* @param opts.
|
|
577
|
-
* @param opts.
|
|
578
|
-
* @param opts.
|
|
579
|
-
* @param
|
|
580
|
-
* @returns {
|
|
455
|
+
* @description Prepare task's object and push to the queue
|
|
456
|
+
* @param opts {object} - Email options with next properties:
|
|
457
|
+
* @param opts.sendAt {number} - When email should be sent
|
|
458
|
+
* @param opts.template {string} - Email-specific template
|
|
459
|
+
* @param opts.mailOptions {object} - MailOptions according to NodeMailer lib
|
|
460
|
+
* @param opts.concatSubject {string} - Email subject used when sending concatenated email
|
|
461
|
+
* @returns {Promise<string>} message uuid
|
|
581
462
|
*/
|
|
582
|
-
___addToQueue(opts
|
|
583
|
-
_debug(
|
|
463
|
+
async ___addToQueue(opts) {
|
|
464
|
+
this._debug('[private addToQueue]', opts);
|
|
584
465
|
const task = {
|
|
466
|
+
uuid: crypto.randomUUID(),
|
|
585
467
|
tries: 0,
|
|
586
468
|
isSent: false,
|
|
587
469
|
sendAt: opts.sendAt,
|
|
470
|
+
isFailed: false,
|
|
588
471
|
template: opts.template,
|
|
589
472
|
transport: this.transport,
|
|
473
|
+
isCancelled: false,
|
|
590
474
|
mailOptions: [opts.mailOptions],
|
|
591
|
-
concatSubject: opts.concatSubject
|
|
475
|
+
concatSubject: opts.concatSubject,
|
|
592
476
|
};
|
|
593
477
|
|
|
594
478
|
if (this.concatEmails) {
|
|
595
479
|
task.to = opts.mailOptions.to;
|
|
596
480
|
}
|
|
597
481
|
|
|
598
|
-
this.
|
|
599
|
-
|
|
600
|
-
if (!this.callbacks[_id]) {
|
|
601
|
-
this.callbacks[_id] = [];
|
|
602
|
-
}
|
|
603
|
-
this.callbacks[_id].push(callback);
|
|
604
|
-
}).catch(mongoErrorHandler);
|
|
482
|
+
await this.queue.push(task);
|
|
483
|
+
return task.uuid;
|
|
605
484
|
}
|
|
606
485
|
|
|
607
486
|
/**
|
|
608
487
|
* @memberOf MailTime
|
|
609
488
|
* @name ___render
|
|
610
|
-
* @
|
|
611
|
-
* @param
|
|
612
|
-
* @
|
|
489
|
+
* @description Render templates
|
|
490
|
+
* @param string {string} - Template with Mustache-like placeholders
|
|
491
|
+
* @param replacements {object} - Blaze/Mustache-like helpers Object
|
|
492
|
+
* @returns {string}
|
|
613
493
|
*/
|
|
614
494
|
___render(_string, replacements) {
|
|
615
495
|
let i;
|
|
@@ -636,19 +516,154 @@ export default class MailTime {
|
|
|
636
516
|
|
|
637
517
|
/**
|
|
638
518
|
* @memberOf MailTime
|
|
639
|
-
* @name
|
|
640
|
-
* @
|
|
641
|
-
* @param
|
|
642
|
-
* @param info
|
|
519
|
+
* @name ___compileMailOpts
|
|
520
|
+
* @description Run various checks, compile options, and render template
|
|
521
|
+
* @param transport {object} - Current transport
|
|
522
|
+
* @param info {object} - Info object returned from NodeMailer's `sendMail` method
|
|
643
523
|
* @returns {void 0}
|
|
644
524
|
*/
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
525
|
+
___compileMailOpts(transport, task) {
|
|
526
|
+
let compiledOpts = {};
|
|
527
|
+
|
|
528
|
+
if (!transport) {
|
|
529
|
+
throw new Error('[mail-time] [sendMail] [___compileMailOpts] {transport} is not available or misconfigured!');
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
if (transport._options && typeof transport._options === 'object' && transport._options !== null && transport._options.mailOptions) {
|
|
533
|
+
compiledOpts = merge(compiledOpts, transport._options.mailOptions);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
if (transport.options && typeof transport.options === 'object' && transport.options !== null && transport.options.mailOptions) {
|
|
537
|
+
compiledOpts = merge(compiledOpts, transport.options.mailOptions);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
compiledOpts = merge(compiledOpts, {
|
|
541
|
+
html: '',
|
|
542
|
+
text: '',
|
|
543
|
+
subject: ''
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
for (let i = 0; i < task.mailOptions.length; i++) {
|
|
547
|
+
if (task.mailOptions[i].html) {
|
|
548
|
+
if (task.mailOptions.length > 1) {
|
|
549
|
+
compiledOpts.html += this.___render(this.concatDelimiter, task.mailOptions[i]) + this.___render(task.mailOptions[i].html, task.mailOptions[i]);
|
|
550
|
+
} else {
|
|
551
|
+
compiledOpts.html = this.___render(task.mailOptions[i].html, task.mailOptions[i]);
|
|
552
|
+
}
|
|
553
|
+
delete task.mailOptions[i].html;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
if (task.mailOptions[i].text) {
|
|
557
|
+
if (task.mailOptions.length > 1) {
|
|
558
|
+
compiledOpts.text += '\r\n' + this.___render(task.mailOptions[i].text, task.mailOptions[i]);
|
|
559
|
+
} else {
|
|
560
|
+
compiledOpts.text = this.___render(task.mailOptions[i].text, task.mailOptions[i]);
|
|
561
|
+
}
|
|
562
|
+
delete task.mailOptions[i].text;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
compiledOpts = merge(compiledOpts, task.mailOptions[i]);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
if (compiledOpts.html && (task.template || this.template)) {
|
|
569
|
+
compiledOpts.html = this.___render((task.template || this.template), compiledOpts);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
if (task.mailOptions.length > 1) {
|
|
573
|
+
compiledOpts.subject = task.concatSubject || this.concatSubject || compiledOpts.subject;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
if (!compiledOpts.from && this.from) {
|
|
577
|
+
compiledOpts.from = this.from(transport);
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
return compiledOpts;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
/**
|
|
584
|
+
* @async
|
|
585
|
+
* @memberOf MailTime
|
|
586
|
+
* @name ___send
|
|
587
|
+
* @description send email using nodemailer's transport
|
|
588
|
+
* @param task {object} - email's task object from Storage
|
|
589
|
+
* @returns {Promise<void 0>}
|
|
590
|
+
*/
|
|
591
|
+
async ___send(task) {
|
|
592
|
+
this._debug('[private send]', task);
|
|
593
|
+
try {
|
|
594
|
+
if (task.isSent === true || task.isFailed === true || task.isCancelled === true) {
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
task.tries++;
|
|
599
|
+
const isUpdated = await this.queue.update(task, {
|
|
600
|
+
isSent: true,
|
|
601
|
+
tries: task.tries
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
if (!isUpdated) {
|
|
605
|
+
logError('[private send] [queue.update] Not updated!');
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
let transport;
|
|
610
|
+
let transportIndex;
|
|
611
|
+
if (this.strategy === 'balancer') {
|
|
612
|
+
this.transport = this.transport + 1;
|
|
613
|
+
if (this.transport >= this.transports.length) {
|
|
614
|
+
this.transport = 0;
|
|
615
|
+
}
|
|
616
|
+
transportIndex = this.transport;
|
|
617
|
+
transport = this.transports[transportIndex];
|
|
618
|
+
} else {
|
|
619
|
+
transportIndex = task.transport;
|
|
620
|
+
transport = this.transports[transportIndex];
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
const compiledOpts = this.___compileMailOpts(transport, task);
|
|
624
|
+
|
|
625
|
+
await new Promise((resolve) => {
|
|
626
|
+
transport.sendMail(compiledOpts, async (error, info) => {
|
|
627
|
+
this._debug('[private send] [sending]', { error, info });
|
|
628
|
+
if (error) {
|
|
629
|
+
await this.___handleError(task, error, info);
|
|
630
|
+
resolve();
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
if (info.accepted && !info.accepted.length) {
|
|
635
|
+
await this.___handleError(task, new Error('Message not accepted or Greeting never received'), info);
|
|
636
|
+
resolve();
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
this._debug(`email successfully sent, attempts: #${task.tries}, transport #${transportIndex} to: `, compiledOpts.to);
|
|
641
|
+
|
|
642
|
+
if (!this.keepHistory) {
|
|
643
|
+
await this.queue.remove(task);
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
task.isSent = true;
|
|
647
|
+
this.onSent(task, info);
|
|
648
|
+
resolve();
|
|
649
|
+
});
|
|
650
650
|
});
|
|
651
|
+
} catch (e) {
|
|
652
|
+
logError('Exception during runtime:', e);
|
|
653
|
+
this.___handleError(task, e, {});
|
|
651
654
|
}
|
|
652
|
-
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
/**
|
|
658
|
+
* @memberOf MailTime
|
|
659
|
+
* @name ___iterate
|
|
660
|
+
* @description Iterate over queued tasks
|
|
661
|
+
* @returns {Promise}
|
|
662
|
+
*/
|
|
663
|
+
async ___iterate() {
|
|
664
|
+
this._debug('[private iterate]');
|
|
665
|
+
return await this.queue.iterate();
|
|
653
666
|
}
|
|
654
667
|
}
|
|
668
|
+
|
|
669
|
+
export { MailTime, MongoQueue, RedisQueue };
|