mail-time 1.2.4 → 1.3.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/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2020, dr.dimitru (Dmitry A.; Veliov Group, LLC)
1
+ Copyright (c) 2022, dr.dimitru (Dmitry A.; Veliov Group, LLC)
2
2
  All rights reserved.
3
3
 
4
4
  Redistribution and use in source and binary forms,
package/README.md CHANGED
@@ -1,12 +1,10 @@
1
- # MailTime
2
-
3
- <a href="https://www.patreon.com/bePatron?u=20396046">
4
- <img src="https://c5.patreon.com/external/logo/become_a_patron_button@2x.png" width="160">
1
+ [![support](https://img.shields.io/badge/support-GitHub-white)](https://github.com/sponsors/veliovgroup)
2
+ [![support](https://img.shields.io/badge/support-PayPal-white)](https://paypal.me/veliovgroup)
3
+ <a href="https://ostr.io/info/built-by-developers-for-developers">
4
+ <img src="https://ostr.io/apple-touch-icon-60x60.png" height="20">
5
5
  </a>
6
6
 
7
- <a href="https://ostr.io">
8
- <img src="https://ostr.io/apple-touch-icon-60x60.png" height="38">
9
- </a>
7
+ # MailTime
10
8
 
11
9
  Micro-service package for mail queue, with *Server* and *Client* API.
12
10
  Build on top of [`nodemailer`](https://github.com/nodemailer/nodemailer) package.
@@ -16,23 +14,29 @@ Every `MailTime` instance can be configured to be a *Server* or *Client*.
16
14
  Main difference of *Server* from *Client* - *Server* handles queue and actually sends email.
17
15
  While *Client* is only put emails into the queue.
18
16
 
17
+ "Mail-Time" is a micro-service package for mail queue, with *Server* and *Client* APIs. Build on top of the [`nodemailer`](https://github.com/nodemailer/nodemailer) package.
18
+
19
+ Every `MailTime` instance can get configured as *Server* or *Client*.
20
+
21
+ The main difference between *Server* and *Client* modes is that the *Server* handles the queue and __sends__ email. While the *Client* only __adds__ emails into the queue.
22
+
19
23
  ## ToC
20
24
 
21
- - [How it works?](https://github.com/VeliovGroup/Mail-Time#how-it-works)
22
- - [With single SMTP](https://github.com/VeliovGroup/Mail-Time#single-point-of-failure)
23
- - [With multiple SMTP](https://github.com/VeliovGroup/Mail-Time#multiple-smtp-providers)
24
- - [As Micro-Service](https://github.com/VeliovGroup/Mail-Time#cluster-issue)
25
- - [Features](https://github.com/VeliovGroup/Mail-Time#features)
26
- - [Installation](https://github.com/VeliovGroup/Mail-Time#installation)
27
- - [Meteor.js Installation](https://github.com/VeliovGroup/Mail-Time#installation--import-via-npm): as [NPM Package](https://www.npmjs.com/package/mail-time)
28
- - [Meteor.js Installation](https://github.com/VeliovGroup/Mail-Time#installation--import-via-atmosphere): as [Atmosphere package](https://atmospherejs.com/ostrio/mailer)
29
- - [Usage example](https://github.com/VeliovGroup/Mail-Time#basic-usage)
30
- - [API](https://github.com/VeliovGroup/Mail-Time#api)
31
- - [*Constructor*](https://github.com/VeliovGroup/Mail-Time#new-mailtimeopts-constructor)
32
- - [`.send()`](https://github.com/VeliovGroup/Mail-Time#sendmailopts--callback)
33
- - [Default Template](https://github.com/VeliovGroup/Mail-Time#static-mailtimetemplate)
34
- - [Custom Templates](https://github.com/VeliovGroup/Mail-Time#template-example)
35
- - [~92% tests coverage](https://github.com/VeliovGroup/Mail-Time#testing)
25
+ - [How it works?](https://github.com/veliovgroup/Mail-Time#how-it-works)
26
+ - [With single SMTP](https://github.com/veliovgroup/Mail-Time#single-point-of-failure)
27
+ - [With multiple SMTP](https://github.com/veliovgroup/Mail-Time#multiple-smtp-providers)
28
+ - [As Micro-Service](https://github.com/veliovgroup/Mail-Time#cluster-issue)
29
+ - [Features](https://github.com/veliovgroup/Mail-Time#features)
30
+ - [Installation](https://github.com/veliovgroup/Mail-Time#installation)
31
+ - [Meteor.js Installation](https://github.com/veliovgroup/Mail-Time#installation--import-via-npm): as [NPM Package](https://www.npmjs.com/package/mail-time)
32
+ - [Meteor.js Installation](https://github.com/veliovgroup/Mail-Time#installation--import-via-atmosphere): as [Atmosphere package](https://atmospherejs.com/ostrio/mailer)
33
+ - [Usage example](https://github.com/veliovgroup/Mail-Time#basic-usage)
34
+ - [API](https://github.com/veliovgroup/Mail-Time#api)
35
+ - [*Constructor*](https://github.com/veliovgroup/Mail-Time#new-mailtimeopts-constructor)
36
+ - [`.send()`](https://github.com/veliovgroup/Mail-Time#sendmailopts--callback)
37
+ - [Default Template](https://github.com/veliovgroup/Mail-Time#static-mailtimetemplate)
38
+ - [Custom Templates](https://github.com/veliovgroup/Mail-Time#template-example)
39
+ - [~92% tests coverage](https://github.com/veliovgroup/Mail-Time#testing)
36
40
 
37
41
  ## Main features:
38
42
 
@@ -41,11 +45,13 @@ While *Client* is only put emails into the queue.
41
45
  - 🏢 Synchronize email queue across multiple servers;
42
46
  - 💪 Bulletproof design, built-in retries.
43
47
 
44
- ## How does it work?:
48
+ ## How does it work?
49
+
50
+ Redundant solution for email transmission.
45
51
 
46
52
  ### Single point of failure
47
53
 
48
- Issue - classic solution with the single point of failure:
54
+ Issue - classic solution with a single point of failure:
49
55
 
50
56
  ```ascii
51
57
  |----------------| |------| |------------------|
@@ -93,11 +99,11 @@ Backup scheme with multiple SMTP providers
93
99
 
94
100
  ### Cluster issue
95
101
 
96
- Let's say you have an app which is growing fast. At some point, you've decided to create a "Cluster" of servers to balance the load and add durability layer.
102
+ It is common to create a "Cluster" of servers to balance the load and add a durability layer for horizontal scaling of quickly growing applications.
97
103
 
98
- Also, your application has scheduled emails, for example, once a day with recent news. While you have had single server emails was sent by some daily interval. So, after you made a "Cluster" of servers - each server has its own timer and going to send a daily email to our user. In such case - users will receive 3 emails, sounds not okay.
104
+ Most modern application has scheduled or recurring emails. For example, once a day — with recent news and updates. It won't be an issue with a single server setup — the server would send emails at a daily interval via timer or CRON. While in "Cluster" implementation — each server will attempt to send the same email. In such cases, users will receive multiple emails with the same content. We built MailTime to address this and other similar issues.
99
105
 
100
- Here is how we solve this issue:
106
+ Here is how this issue is solved by using MailTime:
101
107
 
102
108
  ```ascii
103
109
  |===================THE=CLUSTER===================| |=QUEUE=| |===Mail=Time===|
@@ -117,17 +123,17 @@ Here is how we solve this issue:
117
123
 
118
124
  ## Features
119
125
 
120
- - Queue - Managed via MongoDB, and will survive server reboots and failures
126
+ - Queue - Managed via MongoDB, will survive server reboots and failures
121
127
  - Support for multiple server setups - "Cluster", Phusion Passenger instances, Load Balanced solutions, etc.
122
- - Emails concatenation by addressee email - Reduce amount of sent email to single user with concatenation, and avoid mistakenly doubled emails
123
- - When concatenation is enabled - Same emails wouldn't be sent twice, if for any reason, due to bad logic or application failure emails is sent twice or more times - here is solution to solve this annoying behavior
124
- - Balancing for multiple nodemailer's transports, two modes - `backup` and `balancing`. Most useful feature - allows to reduce the cost of SMTP services and add durability. So, if any of used transports are failing to send an email it will switch to next one
128
+ - Emails concatenation by addressee email - Reduce amount of sent emails to a single user with concatenation, and avoid mistakenly doubled emails
129
+ - When concatenation is enabled - Same emails won't be sent twice, if for any reason, due to bad logic or application failure emails are sent twice or more times - this is solution to solve this annoying behavior
130
+ - Balancing for multiple nodemailer's transports, two modes - `backup` and `balancing`. This is the most useful feature — allowing to reduce the cost of SMTP services and add extra layer of durability. If one transport failing to send an email `mail-time` will switch to the next one
125
131
  - Sending retries for network and other failures
126
- - Template support with Mustache-like placeholders
132
+ - Templating support with [Mustache](https://mustache.github.io/)-like placeholders
127
133
 
128
134
  ## Installation
129
135
 
130
- If you're working on Server functionality - first you will need `nodemailer`, although this package is meant to be used with `nodemailer`, it's not added as the dependency, as it not needed by Client, and you're free to choose `nodemailer`'s version to fit your needs:
136
+ To implement Server functionality — as a first step install `nodemailer`, although this package meant to be used with `nodemailer`, it's not added as the dependency, as `nodemailer` not needed by Client, and you're free to choose `nodemailer`'s version to fit your project needs:
131
137
 
132
138
  ```shell
133
139
  npm install --save nodemailer
@@ -151,14 +157,14 @@ Require package:
151
157
  const MailTime = require('mail-time');
152
158
  ```
153
159
 
154
- Create nodemailer's transports (see [nodemailer docs](https://github.com/nodemailer/nodemailer/tree/v2#setting-up)):
160
+ Create nodemailer's transports (see [`nodemailer` docs](https://github.com/nodemailer/nodemailer/tree/v2#setting-up)):
155
161
 
156
162
  ```js
157
163
  const transports = [];
158
164
  const nodemailer = require('nodemailer');
159
165
 
160
166
  // Use DIRECT transport
161
- // To enable sending email from localhost
167
+ // and enable sending email from localhost
162
168
  // install "nodemailer-direct-transport" NPM package:
163
169
  const directTransport = require('nodemailer-direct-transport');
164
170
  const directTransportOpts = {
@@ -204,8 +210,7 @@ transports.push(nodemailer.createTransport({
204
210
  }));
205
211
  ```
206
212
 
207
- Create `mail-time` *Server*, it is able to send and add emails to the queue.
208
- We will need connect to MongoDB first:
213
+ As the next step initiate `mail-time` in the *Server* mode, it will be able to __send__ and __add__ emails to the queue. Connecting to a MongoDB before initiating `new MailTime` instance:
209
214
 
210
215
  ```js
211
216
  const MongoClient = require('mongodb').MongoClient;
@@ -226,7 +231,7 @@ MongoClient.connect(process.env.MONGO_URL, (error, client) => {
226
231
  from(transport) {
227
232
  // To pass spam-filters `from` field should be correctly set
228
233
  // for each transport, check `transport` object for more options
229
- return '"Awesome App" <' + transport.options.from + '>';
234
+ return `"Awesome App" <${transport.options.from}>`;
230
235
  },
231
236
  concatEmails: true, // Concatenate emails to the same addressee
232
237
  concatDelimiter: '<h1>{{{subject}}}</h1>', // Start each concatenated email with it's own subject
@@ -235,7 +240,7 @@ MongoClient.connect(process.env.MONGO_URL, (error, client) => {
235
240
  });
236
241
  ```
237
242
 
238
- Create the *Client* to add emails to queue from other application units, like UI unit:
243
+ Only __one__ `MailTime` *Server* instance required to send email. In the other parts of an app (like UI units or in sub-apps) use `mail-time` in the *Client* mode to __add__ emails to queue:
239
244
 
240
245
  ```js
241
246
  const MongoClient = require('mongodb').MongoClient;
@@ -254,44 +259,114 @@ MongoClient.connect(process.env.MONGO_URL, (error, client) => {
254
259
  });
255
260
  ```
256
261
 
257
- Send email:
262
+ Send email example:
263
+
264
+ ```js
265
+ mailQueue.sendMail({
266
+ to: 'user@gmail.com',
267
+ subject: 'You\'ve got an email!',
268
+ text: 'Plain text message',
269
+ html: '<h1>HTML</h1><p>Styled message</p>'
270
+ });
271
+ ```
272
+
273
+ ### Two `MailTime` instances usage example
274
+
275
+ Create two `MailTime` instances with different settings.
258
276
 
259
277
  ```js
278
+ // USE FOR NON-URGENT EMAILS WHICH IS OKAY TO CONCATENATE
279
+ const mailQueue = new MailTime({
280
+ db: db,
281
+ interval: 35,
282
+ strategy: 'backup',
283
+ failsToNext: 1,
284
+ concatEmails: true,
285
+ concatThrottling: 16,
286
+ zombieTime: 120000
287
+ });
288
+
289
+ // USE FOR TRANSACTIONAL EMAILS AND ALERTS
290
+ const mailInstantQueue = new MailTime({
291
+ db: db,
292
+ prefix: 'instant',
293
+ interval: 2,
294
+ strategy: 'backup',
295
+ failsToNext: 1,
296
+ concatEmails: false,
297
+ zombieTime: 20000
298
+ });
299
+
260
300
  mailQueue.sendMail({
261
301
  to: 'user@gmail.com',
262
302
  subject: 'You\'ve got an email!',
263
303
  text: 'Plain text message',
264
304
  html: '<h1>HTML</h1><p>Styled message</p>'
265
305
  });
306
+
307
+ mailInstantQueue.sendMail({
308
+ to: 'user@gmail.com',
309
+ subject: 'Sign in request',
310
+ text: 'Your OTP login code: xxxx:',
311
+ html: '<h1>Code:</h1><code>XXXX</code>'
312
+ });
313
+ ```
314
+
315
+ ### Passing variables to the template
316
+
317
+ All options passed to the `.sendMail()` method is available inside `text`, `html`, and global templates
318
+
319
+ ```js
320
+ const templates = {
321
+ global: '<html xmlns="http://www.w3.org/1999/xhtml"><head><title>{{subject}}</title></head><body>{{html}}<footer>Message sent to @{{username}} user ({{to}})</footer></body></html>',
322
+ signInCode: {
323
+ text: 'Hello @{{username}}! Here\'s your login code: {{code}}'
324
+ html: `<h1>Sign-in request</h1><p>Hello @{{username}}! Copy your login code below <pre><code>{{code}}</code></pre>`
325
+ }
326
+ }
327
+
328
+ const mailQueue = new MailTime({
329
+ db: db,
330
+ template: templates.global
331
+ });
332
+
333
+ mailQueue.sendMail({
334
+ to: 'user@gmail.com',
335
+ subject: 'Sign-in request',
336
+ username: 'johndoe',
337
+ code: 'XXXXX-YY'
338
+ text: templates.signInCode.text,
339
+ html: templates.signInCode.html
340
+ });
266
341
  ```
267
342
 
268
343
  ## Meteor.js usage:
269
344
 
270
- ### Meteor.js Installation:
345
+ Mail-Time package can be installed and used within [Meteor.js](https://docs.meteor.com/) via [NPM](https://www.npmjs.com/package/mail-time) or [Atmosphere](https://atmospherejs.com/ostrio/mailer)
271
346
 
272
- #### Installation & Import (*via NPM*):
347
+ ### Installation & Import (*via NPM*):
273
348
 
274
- Install NPM *MailTime* package:
349
+ Install [NPM `mail-time` package](https://www.npmjs.com/package/mail-time):
275
350
 
276
351
  ```shell
277
352
  meteor npm install --save mail-time
278
353
  ```
279
354
 
280
- ES6 Import:
355
+ Meteor.js: ES6 Import NPM package:
281
356
 
282
357
  ```js
283
358
  import MailTime from 'mail-time';
284
359
  ```
285
360
 
286
- #### Installation & Import (*via Atmosphere*):
361
+ ### Installation & Import (*via Atmosphere*):
287
362
 
288
- Install Atmosphere *ostrio:mailer* package:
363
+ Install [Atmosphere `ostrio:mailer` package](https://atmospherejs.com/ostrio/mailer):
289
364
 
290
365
  ```shell
291
366
  meteor add ostrio:mailer
292
367
  ```
293
368
 
294
- ES6 Import:
369
+ Meteor.js: ES6 Import atmosphere package:
295
370
 
296
371
  ```js
297
372
  import MailTime from 'meteor/ostrio:mailer';
@@ -299,6 +374,8 @@ import MailTime from 'meteor/ostrio:mailer';
299
374
 
300
375
  ### Usage:
301
376
 
377
+ `mail-time` package usage examples in Meteor.js
378
+
302
379
  ```js
303
380
  import { MongoInternals } from 'meteor/mongo';
304
381
 
@@ -338,6 +415,8 @@ const mailQueue = new MailTime({
338
415
 
339
416
  ## API
340
417
 
418
+ All available constructor options and `.sendMail()` method API overview
419
+
341
420
  ### `new MailTime(opts)` constructor
342
421
 
343
422
  - `opts` {*Object*} - Configuration object
@@ -350,14 +429,15 @@ const mailQueue = new MailTime({
350
429
  - `opts.prefix` {*String*} - [Optional] Use unique prefixes to create multiple `MailTime` instances on same MongoDB
351
430
  - `opts.maxTries` {*Number*} - [Optional] How many times resend failed emails, default - `60`
352
431
  - `opts.interval` {*Number*} - [Optional] Interval in *seconds* between send re-tries, default - `60`
353
- - `opts.zombieTime` {*Number*} - [Optional] Time in *milliseconds*, after this period - pending email will be interpreted as "*zombie*". This parameter allows to rescue pending email from "*zombie* mode" in case when: server was rebooted, exception during runtime was thrown, or caused by bad logic, default - `32786`. This option is used by package itself and passed directly to [`JoSk` package](https://github.com/VeliovGroup/josk#api)
432
+ - `opts.zombieTime` {*Number*} - [Optional] Time in *milliseconds*, after this period - pending email will be interpreted as "*zombie*". This parameter allows to rescue pending email from "*zombie* mode" in case when: server was rebooted, exception during runtime was thrown, or caused by bad logic, default - `32786`. This option is used by package itself and passed directly to [`JoSk` package](https://github.com/veliovgroup/josk#api)
433
+ - `opts.keepHistory` {*Boolean*} - [Optional] By default sent emails not stored in the database. Set `{ keepHistory: true }` to keep queue task as it is in the database, default - `false`
354
434
  - `opts.concatEmails` {*Boolean*} - [Optional] Concatenate email by `to` field, default - `false`
355
435
  - `opts.concatSubject` {*String*} - [Optional] Email subject used in concatenated email, default - `Multiple notifications`
356
436
  - `opts.concatDelimiter` {*String*} - [Optional] HTML or plain string delimiter used between concatenated email, default - `<hr>`
357
437
  - `opts.concatThrottling` {*Number*} - [Optional] Time in *seconds* while emails are waiting to be concatenated, default - `60`
358
438
  - `opts.revolvingInterval` {*Number*} - [Optional] Interval in *milliseconds* in between queue checks, default - `256`. Recommended value — between `opts.minRevolvingDelay` and `opts.maxRevolvingDelay`
359
- - `opts.minRevolvingDelay` {*Number*} - [Optional] Minimum revolving delay — the minimum delay between tasks executions in *milliseconds*, default - `64`. This option is passed directly to [`JoSk` package](https://github.com/VeliovGroup/josk#api)
360
- - `opts.maxRevolvingDelay` {*Number*} - [Optional] Maximum revolving delay — the maximum delay between tasks executions in *milliseconds*, default - `256`. This option is passed directly to [`JoSk` package](https://github.com/VeliovGroup/josk#api)
439
+ - `opts.minRevolvingDelay` {*Number*} - [Optional] Minimum revolving delay — the minimum delay between tasks executions in *milliseconds*, default - `64`. This option is passed directly to [`JoSk` package](https://github.com/veliovgroup/josk#api)
440
+ - `opts.maxRevolvingDelay` {*Number*} - [Optional] Maximum revolving delay — the maximum delay between tasks executions in *milliseconds*, default - `256`. This option is passed directly to [`JoSk` package](https://github.com/veliovgroup/josk#api)
361
441
  - `opts.template` {*String*} - [Optional] Mustache-like template, default - `{{{html}}}`, all options passed to `sendMail` is available in Template, like `to`, `subject`, `text`, `html` or any other custom option. Use `{{opt}}` for string placeholders and `{{{opt}}}` for html placeholders
362
442
 
363
443
  ### `sendMail(opts [, callback])`
@@ -372,7 +452,7 @@ const mailQueue = new MailTime({
372
452
 
373
453
  ### `static MailTime.Template`
374
454
 
375
- Simple and bulletproof HTML template, see its [source](https://github.com/VeliovGroup/Mail-Time/blob/master/template.html). Usage:
455
+ Simple and bulletproof HTML template, see [its source](https://github.com/veliovgroup/Mail-Time/blob/master/template.html). Usage example:
376
456
 
377
457
  ```js
378
458
  const MailTime = require('mail-time');
@@ -393,6 +473,8 @@ mailQueue.sendMail({
393
473
 
394
474
  ### Template Example
395
475
 
476
+ Pass custom template via `template` property to `.sendMail()` method
477
+
396
478
  ```js
397
479
  mailQueue.sendMail({
398
480
  to: 'user@gmail.com',
@@ -440,5 +522,10 @@ DEBUG="true" EMAIL_DOMAIN="example.com" MONGO_URL="mongodb://127.0.0.1:27017/met
440
522
 
441
523
  ## Support this project:
442
524
 
443
- - [Become a patron](https://www.patreon.com/bePatron?u=20396046) — support my open source contributions with monthly donation
525
+ - Star on [GitHub](https://github.com/veliovgroup/mail-time)
526
+ - Star on [NPM](https://www.npmjs.com/package/mail-time)
527
+ - Star on [Atmosphere](https://atmospherejs.com/ostrio/mailer)
528
+ - [Sponsor maintainer via GitHub](https://github.com/sponsors/dr-dimitru) — support open source with one-time contribution or on a regular basis
529
+ - [Sponsor veliovgroup via GitHub](https://github.com/sponsors/veliovgroup) — support company behind this package
530
+ - [Support via PayPal](https://paypal.me/veliovgroup) — support our open source contributions
444
531
  - Use [ostr.io](https://ostr.io) — [Monitoring](https://snmp-monitoring.com), [Analytics](https://ostr.io/info/web-analytics), [WebSec](https://domain-protection.info), [Web-CRON](https://web-cron.info) and [Pre-rendering](https://prerendering.com) for a website
package/index.js CHANGED
@@ -1,5 +1,5 @@
1
- const JoSk = require('josk');
2
- const NoOp = () => {};
1
+ const JoSk = require('josk');
2
+ const noop = () => {};
3
3
  const merge = require('deepmerge');
4
4
  const _debug = (isDebug, ...args) => {
5
5
  if (isDebug) {
@@ -17,12 +17,6 @@ const mongoErrorHandler = (error) => {
17
17
  }
18
18
  };
19
19
 
20
- const defaultWriteConcern = {
21
- w: 1,
22
- j: true,
23
- wtimeout: 10240
24
- };
25
-
26
20
  let equals;
27
21
  equals = (a, b) => {
28
22
  let i;
@@ -116,6 +110,7 @@ module.exports = class MailTime {
116
110
  this.interval = ((opts.interval && !isNaN(opts.interval)) ? parseInt(opts.interval) : 60) * 1000;
117
111
  this.template = (typeof opts.template === 'string') ? opts.template : '{{{html}}}';
118
112
  this.zombieTime = opts.zombieTime || 32786;
113
+ this.keepHistory = opts.keepHistory || false;
119
114
 
120
115
  this.revolvingInterval = opts.revolvingInterval || 1536;
121
116
  this.minRevolvingDelay = opts.minRevolvingDelay || 512;
@@ -147,9 +142,9 @@ module.exports = class MailTime {
147
142
  return false;
148
143
  })();
149
144
 
150
- this.concatEmails = (opts.concatEmails !== true) ? false : true;
151
- this.concatSubject = (opts.concatSubject && typeof opts.concatSubject === 'string') ? opts.concatSubject : 'Multiple notifications';
152
- this.concatDelimiter = (opts.concatDelimiter && typeof opts.concatDelimiter === 'string') ? opts.concatDelimiter : '<hr>';
145
+ this.concatEmails = (opts.concatEmails !== true) ? false : true;
146
+ this.concatSubject = (opts.concatSubject && typeof opts.concatSubject === 'string') ? opts.concatSubject : 'Multiple notifications';
147
+ this.concatDelimiter = (opts.concatDelimiter && typeof opts.concatDelimiter === 'string') ? opts.concatDelimiter : '<hr>';
153
148
  this.concatThrottling = ((opts.concatThrottling && !isNaN(opts.concatThrottling)) ? parseInt(opts.concatThrottling) : 60) * 1000;
154
149
 
155
150
  if (this.concatThrottling < 2048) {
@@ -168,7 +163,7 @@ module.exports = class MailTime {
168
163
  }
169
164
 
170
165
  this.collection = opts.db.collection('__mailTimeQueue__' + this.prefix);
171
- this.collection.createIndex({ to: 1, isSent: 1 }, (indexError) => {
166
+ this.collection.createIndex({ to: 1, isSent: 1, sendAt: 1 }, (indexError) => {
172
167
  if (indexError) {
173
168
  _logError('[createIndex]', indexError);
174
169
  }
@@ -197,7 +192,7 @@ module.exports = class MailTime {
197
192
  // mailOptions.Other nodeMailer `sendMail` options...
198
193
 
199
194
  if (this.type === 'server') {
200
- this.scheduler = new JoSk({
195
+ const scheduler = new JoSk({
201
196
  db: opts.db,
202
197
  debug: this.debug,
203
198
  prefix: `mailTimeQueue${this.prefix}`,
@@ -207,7 +202,7 @@ module.exports = class MailTime {
207
202
  maxRevolvingDelay: this.maxRevolvingDelay
208
203
  });
209
204
 
210
- this.scheduler.setInterval(this.___send.bind(this), this.revolvingInterval, `mailTimeQueue${this.prefix}`);
205
+ scheduler.setInterval(this.___send.bind(this), this.revolvingInterval, `mailTimeQueue${this.prefix}`);
211
206
  }
212
207
  }
213
208
 
@@ -277,32 +272,21 @@ module.exports = class MailTime {
277
272
  return _mailOpts;
278
273
  }
279
274
 
280
- /*
281
- @memberOf MailTime
282
- @name ___send
283
- @param ready {Function} - See JoSk NPM package
284
- @returns {void}
275
+ /**
276
+ * @memberOf MailTime
277
+ * @name ___send
278
+ * @param ready {Function} - See JoSk NPM package
279
+ * @returns {void}
285
280
  */
286
281
  ___send(ready) {
287
- let finished = 0;
288
282
  const cursor = this.collection.find({
289
- $or: [{
290
- isSent: false,
291
- sendAt: {
292
- $lte: new Date()
293
- },
294
- tries: {
295
- $lt: this.maxTries
296
- }
297
- }, {
298
- isSent: true,
299
- sendAt: {
300
- $lt: new Date(Date.now() - (this.zombieTime * 2))
301
- },
302
- tries: {
303
- $lt: this.maxTries
304
- }
305
- }]
283
+ isSent: false,
284
+ sendAt: {
285
+ $lte: new Date()
286
+ },
287
+ tries: {
288
+ $lt: this.maxTries
289
+ }
306
290
  }, {
307
291
  projection: {
308
292
  _id: 1,
@@ -314,116 +298,96 @@ module.exports = class MailTime {
314
298
  }
315
299
  });
316
300
 
317
- cursor.count((countError, count) => {
318
- if (countError) {
319
- ready();
320
- _logError('[___send] [count] [countError]', countError);
321
- return;
322
- }
323
-
324
- if (count) {
325
- cursor.forEach((task) => {
326
- this.collection.updateOne({
327
- _id: task._id
328
- }, {
329
- $set: {
330
- isSent: true,
331
- sendAt: new Date(Date.now() + (this.zombieTime * 2))
332
- },
333
- $inc: {
334
- tries: 1
335
- }
336
- }, defaultWriteConcern, (updateError) => {
337
- if (count === ++finished) {
338
- ready();
301
+ cursor.forEach((task) => {
302
+ this.collection.updateOne({
303
+ _id: task._id
304
+ }, {
305
+ $set: {
306
+ isSent: true
307
+ },
308
+ $inc: {
309
+ tries: 1
310
+ }
311
+ }, (updateError) => {
312
+ if (updateError) {
313
+ this.___handleError(task, updateError, {});
314
+ } else {
315
+ let transport;
316
+ let transportIndex;
317
+ if (this.strategy === 'balancer') {
318
+ this.transport = this.transport + 1;
319
+ if (this.transport >= this.transports.length) {
320
+ this.transport = 0;
339
321
  }
322
+ transportIndex = this.transport;
323
+ transport = this.transports[this.transport];
324
+ } else {
325
+ transportIndex = task.transport;
326
+ transport = this.transports[task.transport];
327
+ }
340
328
 
341
- if (updateError) {
342
- this.___handleError(task, updateError, {});
343
- } else {
344
- let transport;
345
- let transportIndex;
346
- if (this.strategy === 'balancer') {
347
- this.transport = this.transport + 1;
348
- if (this.transport >= this.transports.length) {
349
- this.transport = 0;
350
- }
351
- transportIndex = this.transport;
352
- transport = this.transports[this.transport];
353
- } else {
354
- transportIndex = task.transport;
355
- transport = this.transports[task.transport];
329
+ try {
330
+ const _mailOpts = this.___compileMailOpts(transport, task);
331
+
332
+ _debug(this.debug, '[sendMail] [sending] To:', _mailOpts.to);
333
+ transport.sendMail(_mailOpts, (error, info) => {
334
+ if (error) {
335
+ this.___handleError(task, error, info);
336
+ return;
337
+ }
338
+
339
+ if (info.accepted && !info.accepted.length) {
340
+ this.___handleError(task, 'Message not accepted or Greeting never received', info);
341
+ return;
356
342
  }
357
343
 
358
- try {
359
- const _mailOpts = this.___compileMailOpts(transport, task);
360
-
361
- _debug(this.debug, '[sendMail] [sending] To:', _mailOpts.to);
362
- transport.sendMail(_mailOpts, (error, info) => {
363
- if (error) {
364
- this.___handleError(task, error, info);
365
- return;
366
- }
367
-
368
- if (info.accepted && !info.accepted.length) {
369
- this.___handleError(task, 'Message not accepted or Greeting never received', info);
370
- return;
371
- }
372
-
373
- this.collection.deleteOne({
374
- _id: task._id
375
- }, defaultWriteConcern, () => {
376
- _debug(this.debug, `email successfully sent, attempts: #${task.tries}, transport #${transportIndex} to: `, _mailOpts.to);
377
-
378
- const _id = task._id.toHexString();
379
- if (this.callbacks[_id] && this.callbacks[_id].length) {
380
- this.callbacks[_id].forEach((cb, i) => {
381
- cb(void 0, info, task.mailOptions[i]);
382
- });
383
- }
384
- delete this.callbacks[_id];
385
- });
386
-
387
- return;
344
+ _debug(this.debug, `email successfully sent, attempts: #${task.tries}, transport #${transportIndex} to: `, _mailOpts.to);
345
+ if (this.keepHistory) {
346
+ this.___triggerCallbacks(void 0, task, info);
347
+ } else {
348
+ this.collection.deleteOne({ _id: task._id }, () => {
349
+ this.___triggerCallbacks(void 0, task, info);
388
350
  });
389
- } catch (e) {
390
- _logError('Exception during runtime:', e);
391
- this.___handleError(task, e, {});
392
351
  }
393
- }
394
- });
395
- }, (forEachError) => {
396
- if (forEachError) {
397
- _logError('[___send] [forEach] [forEachError]', forEachError);
352
+
353
+ return;
354
+ });
355
+ } catch (e) {
356
+ _logError('Exception during runtime:', e);
357
+ this.___handleError(task, e, {});
398
358
  }
399
- });
400
- } else {
401
- ready();
359
+ }
360
+ });
361
+ }, (forEachError) => {
362
+ ready();
363
+ cursor.close();
364
+ if (forEachError) {
365
+ _logError('[___send] [forEach] [forEachError]', forEachError);
402
366
  }
403
367
  });
404
368
  }
405
369
 
406
- /*
407
- @memberOf MailTime
408
- @name send
409
- @description alias of `sendMail`
410
- @returns {void}
370
+ /**
371
+ * @memberOf MailTime
372
+ * @name send
373
+ * @description alias of `sendMail`
374
+ * @returns {void}
411
375
  */
412
376
  send(opts, callback) {
413
377
  this.sendMail(opts, callback);
414
378
  }
415
379
 
416
- /*
417
- @memberOf MailTime
418
- @name sendMail
419
- @param opts {Object} - Letter options with next properties:
420
- @param opts.sendAt {Date} - When email should be sent
421
- @param opts.template {String} - Template string
422
- @param opts[key] {mix} - Other MailOptions according to NodeMailer lib documentation
423
- @param callback {Function} - [OPTIONAL] Callback function
424
- @returns {void}
380
+ /**
381
+ * @memberOf MailTime
382
+ * @name sendMail
383
+ * @param opts {Object} - Letter options with next properties:
384
+ * @param opts.sendAt {Date} - When email should be sent
385
+ * @param opts.template {String} - Template string
386
+ * @param opts[key] {mix} - Other MailOptions according to NodeMailer lib documentation
387
+ * @param callback {Function} - [OPTIONAL] Callback function
388
+ * @returns {void}
425
389
  */
426
- sendMail(opts = {}, callback = NoOp) {
390
+ sendMail(opts = {}, callback = noop) {
427
391
  _debug(this.debug, '[sendMail] [attempt] To:', opts.to);
428
392
  if (!opts.html && !opts.text) {
429
393
  throw new Error('`html` nor `text` field is presented, at least one of those fields is required');
@@ -441,8 +405,8 @@ module.exports = class MailTime {
441
405
  opts.concatSubject = false;
442
406
  }
443
407
 
444
- let _sendAt = opts.sendAt;
445
- const _template = opts.template;
408
+ let _sendAt = opts.sendAt;
409
+ const _template = opts.template;
446
410
  const _concatSubject = opts.concatSubject;
447
411
  delete opts.sendAt;
448
412
  delete opts.template;
@@ -455,7 +419,10 @@ module.exports = class MailTime {
455
419
  if (this.concatEmails) {
456
420
  this.collection.findOne({
457
421
  to: opts.to,
458
- isSent: false
422
+ isSent: false,
423
+ sendAt: {
424
+ $lte: new Date(+_sendAt + this.concatThrottling)
425
+ }
459
426
  }, {
460
427
  projection: {
461
428
  _id: 1,
@@ -518,13 +485,13 @@ module.exports = class MailTime {
518
485
  return;
519
486
  }
520
487
 
521
- /*
522
- @memberOf MailTime
523
- @name ___handleError
524
- @param task {Object} - Email task record form Mongo
525
- @param error {mix} - Error String/Object/Error
526
- @param info {Object} - Info object returned from nodemailer
527
- @returns {void}
488
+ /**
489
+ * @memberOf MailTime
490
+ * @name ___handleError
491
+ * @param task {Object} - Email task record form Mongo
492
+ * @param error {mix} - Error String/Object/Error
493
+ * @param info {Object} - Info object returned from nodemailer
494
+ * @returns {void}
528
495
  */
529
496
  ___handleError(task, error, info) {
530
497
  if (!task) {
@@ -534,16 +501,9 @@ module.exports = class MailTime {
534
501
  if (task.tries >= this.maxTries) {
535
502
  this.collection.deleteOne({
536
503
  _id: task._id
537
- }, () => {
538
- _debug(this.debug, `Giving up trying send email after ${task.tries} attempts to: `, task.mailOptions[0].to, error);
539
-
540
- const _id = task._id.toHexString();
541
- if (this.callbacks[_id] && this.callbacks[_id].length) {
542
- this.callbacks[_id].forEach((cb, i) => {
543
- cb(error, info, task.mailOptions[i]);
544
- });
545
- }
546
- delete this.callbacks[_id];
504
+ }, (deleteError) => {
505
+ _debug(this.debug, `Giving up trying send email after ${task.tries} attempts to: `, task.mailOptions[0].to, error, deleteError);
506
+ this.___triggerCallbacks(error, task, info);
547
507
  });
548
508
  } else {
549
509
  let transportIndex = task.transport;
@@ -569,16 +529,16 @@ module.exports = class MailTime {
569
529
  }
570
530
  }
571
531
 
572
- /*
573
- @memberOf MailTime
574
- @name ___addToQueue
575
- @param opts {Object} - Letter options with next properties:
576
- @param opts.sendAt {Date} - When email should be sent
577
- @param opts.template {String} - Email template
578
- @param opts.mailOptions {Object} - MailOptions according to NodeMailer lib
579
- @param opts.concatSubject {String} - Email subject used when sending multiple concatenated emails
580
- @param callback {Function} - [OPTIONAL] Callback function
581
- @returns {void}
532
+ /**
533
+ * @memberOf MailTime
534
+ * @name ___addToQueue
535
+ * @param opts {Object} - Letter options with next properties:
536
+ * @param opts.sendAt {Date} - When email should be sent
537
+ * @param opts.template {String} - Email template
538
+ * @param opts.mailOptions {Object} - MailOptions according to NodeMailer lib
539
+ * @param opts.concatSubject {String} - Email subject used when sending multiple concatenated emails
540
+ * @param callback {Function} - [OPTIONAL] Callback function
541
+ * @returns {void}
582
542
  */
583
543
  ___addToQueue(opts, callback) {
584
544
  _debug(this.debug, '[sendMail] [adding to queue] To:', opts.mailOptions.to);
@@ -612,16 +572,16 @@ module.exports = class MailTime {
612
572
  });
613
573
  }
614
574
 
615
- /*
616
- @memberOf MailTime
617
- @name ___render
618
- @param string {String} - Template with Spacebars/Blaze/Mustache-like placeholders
619
- @param replacements {Object} - Blaze/Mustache-like helpers Object
620
- @returns {String}
575
+ /**
576
+ * @memberOf MailTime
577
+ * @name ___render
578
+ * @param string {String} - Template with Spacebars/Blaze/Mustache-like placeholders
579
+ * @param replacements {Object} - Blaze/Mustache-like helpers Object
580
+ * @returns {String}
621
581
  */
622
582
  ___render(_string, replacements) {
623
583
  let i;
624
- let string = _string;
584
+ let string = _string;
625
585
  const matchHTML = string.match(/\{{3}\s?([a-zA-Z0-9\-\_]+)\s?\}{3}/g);
626
586
  if (matchHTML) {
627
587
  for (i = 0; i < matchHTML.length; i++) {
@@ -641,4 +601,22 @@ module.exports = class MailTime {
641
601
  }
642
602
  return string;
643
603
  }
604
+
605
+ /**
606
+ * @memberOf MailTime
607
+ * @name ___triggerCallbacks
608
+ * @param error {mix} - Error thrown during sending email
609
+ * @param task {Object} - Task record from mongodb
610
+ * @param info {Object} - Info object returned from NodeMailer's `sendMail` method
611
+ * @returns {void 0}
612
+ */
613
+ ___triggerCallbacks(error, task, info) {
614
+ const _id = task._id.toHexString();
615
+ if (this.callbacks[_id] && this.callbacks[_id].length) {
616
+ this.callbacks[_id].forEach((cb, i) => {
617
+ cb(error, info, task.mailOptions[i]);
618
+ });
619
+ }
620
+ delete this.callbacks[_id];
621
+ }
644
622
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mail-time",
3
- "version": "1.2.4",
4
- "description": "Bulletproof email queue on top of NodeMailer with support of multiple clusters and servers setup",
3
+ "version": "1.3.0",
4
+ "description": "Bulletproof email queue on top of NodeMailer for a single and multi-server setups",
5
5
  "main": "index.js",
6
6
  "scripts": {
7
7
  "test": "mocha ./test/npm.js"
@@ -36,12 +36,12 @@
36
36
  ],
37
37
  "repository": {
38
38
  "type": "git",
39
- "url": "git://github.com/VeliovGroup/Mail-Time.git"
39
+ "url": "git://github.com/veliovgroup/Mail-Time.git"
40
40
  },
41
41
  "bugs": {
42
- "url": "https://github.com/VeliovGroup/Mail-Time/issues"
42
+ "url": "https://github.com/veliovgroup/Mail-Time/issues"
43
43
  },
44
- "homepage": "https://github.com/VeliovGroup/Mail-Time",
44
+ "homepage": "https://github.com/veliovgroup/Mail-Time",
45
45
  "author": "dr.dimitru (https://veliovgroup.com)",
46
46
  "license": "BSD-3-Clause",
47
47
  "engines": {
@@ -49,15 +49,15 @@
49
49
  },
50
50
  "dependencies": {
51
51
  "deepmerge": "^4.2.2",
52
- "josk": "^2.2.5"
52
+ "josk": "^3.0.0"
53
53
  },
54
54
  "devDependencies": {
55
- "bson": "^4.0.4",
56
- "bson-ext": "^2.0.3",
57
- "chai": "^4.2.0",
58
- "mocha": "^7.1.2",
59
- "mongodb": "^3.5.7",
60
- "nodemailer": "^6.4.6",
61
- "nodemailer-direct-transport": "^3.3.2"
55
+ "bson": "^4.6.4",
56
+ "bson-ext": "^4.0.2",
57
+ "chai": "^4.3.6",
58
+ "mocha": "^10.0.0",
59
+ "mongodb": "^4.6.0",
60
+ "nodemailer": "^6.7.5",
61
+ "nodemailer-direct-transport": "^3.0.7"
62
62
  }
63
63
  }
package/CHANGELOG.md DELETED
@@ -1 +0,0 @@
1
- For full changelog see [releases](https://github.com/VeliovGroup/Mail-Time/releases) in GitHub
package/HISTORY.md DELETED
@@ -1 +0,0 @@
1
- For full history see [releases](https://github.com/VeliovGroup/Mail-Time/releases) in GitHub