mail-time 1.2.6 → 1.3.2

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/CHANGELOG.md CHANGED
@@ -1 +1 @@
1
- For full changelog see [releases](https://github.com/veliovgroup/Mail-Time/releases) in GitHub
1
+ For full changelog see [releases](https://github.com/veliovgroup/mail-time/releases) in GitHub
package/HISTORY.md CHANGED
@@ -1 +1 @@
1
- For full history see [releases](https://github.com/veliovgroup/Mail-Time/releases) in GitHub
1
+ For full history see [releases](https://github.com/veliovgroup/mail-time/releases) in GitHub
package/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2021, 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
@@ -14,23 +14,28 @@ Every `MailTime` instance can be configured to be a *Server* or *Client*.
14
14
  Main difference of *Server* from *Client* - *Server* handles queue and actually sends email.
15
15
  While *Client* is only put emails into the queue.
16
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
+
17
23
  ## ToC
18
24
 
19
- - [How it works?](https://github.com/veliovgroup/Mail-Time#how-it-works)
20
- - [With single SMTP](https://github.com/veliovgroup/Mail-Time#single-point-of-failure)
21
- - [With multiple SMTP](https://github.com/veliovgroup/Mail-Time#multiple-smtp-providers)
22
- - [As Micro-Service](https://github.com/veliovgroup/Mail-Time#cluster-issue)
23
- - [Features](https://github.com/veliovgroup/Mail-Time#features)
24
- - [Installation](https://github.com/veliovgroup/Mail-Time#installation)
25
- - [Meteor.js Installation](https://github.com/veliovgroup/Mail-Time#installation--import-via-npm): as [NPM Package](https://www.npmjs.com/package/mail-time)
26
- - [Meteor.js Installation](https://github.com/veliovgroup/Mail-Time#installation--import-via-atmosphere): as [Atmosphere package](https://atmospherejs.com/ostrio/mailer)
27
- - [Usage example](https://github.com/veliovgroup/Mail-Time#basic-usage)
28
- - [API](https://github.com/veliovgroup/Mail-Time#api)
29
- - [*Constructor*](https://github.com/veliovgroup/Mail-Time#new-mailtimeopts-constructor)
30
- - [`.send()`](https://github.com/veliovgroup/Mail-Time#sendmailopts--callback)
31
- - [Default Template](https://github.com/veliovgroup/Mail-Time#static-mailtimetemplate)
32
- - [Custom Templates](https://github.com/veliovgroup/Mail-Time#template-example)
33
- - [~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 usage](https://github.com/veliovgroup/mail-time/blob/master/docs/meteor.md)
32
+ - [Usage example](https://github.com/veliovgroup/mail-time#basic-usage)
33
+ - [API](https://github.com/veliovgroup/mail-time#api)
34
+ - [*Constructor*](https://github.com/veliovgroup/mail-time#new-mailtimeopts-constructor)
35
+ - [`.send()`](https://github.com/veliovgroup/mail-time#sendmailopts--callback)
36
+ - [Default Template](https://github.com/veliovgroup/mail-time#static-mailtimetemplate)
37
+ - [Custom Templates](https://github.com/veliovgroup/mail-time#template-example)
38
+ - [~92% tests coverage](https://github.com/veliovgroup/mail-time#testing)
34
39
 
35
40
  ## Main features:
36
41
 
@@ -45,7 +50,7 @@ Redundant solution for email transmission.
45
50
 
46
51
  ### Single point of failure
47
52
 
48
- Issue - classic solution with the single point of failure:
53
+ Issue - classic solution with a single point of failure:
49
54
 
50
55
  ```ascii
51
56
  |----------------| |------| |------------------|
@@ -93,11 +98,11 @@ Backup scheme with multiple SMTP providers
93
98
 
94
99
  ### Cluster issue
95
100
 
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.
101
+ 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
102
 
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.
103
+ 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
104
 
100
- Here is how we solve this issue:
105
+ Here is how this issue is solved by using MailTime:
101
106
 
102
107
  ```ascii
103
108
  |===================THE=CLUSTER===================| |=QUEUE=| |===Mail=Time===|
@@ -117,17 +122,17 @@ Here is how we solve this issue:
117
122
 
118
123
  ## Features
119
124
 
120
- - Queue - Managed via MongoDB, and will survive server reboots and failures
125
+ - Queue - Managed via MongoDB, will survive server reboots and failures
121
126
  - Support for multiple server setups - "Cluster", Phusion Passenger instances, Load Balanced solutions, etc.
122
127
  - Emails concatenation by addressee email - Reduce amount of sent emails to a single user with concatenation, and avoid mistakenly doubled emails
123
128
  - 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
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
129
+ - 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
130
  - Sending retries for network and other failures
126
- - Template support with Mustache-like placeholders
131
+ - Templating support with [Mustache](https://mustache.github.io/)-like placeholders
127
132
 
128
133
  ## Installation
129
134
 
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:
135
+ 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
136
 
132
137
  ```shell
133
138
  npm install --save nodemailer
@@ -151,14 +156,14 @@ Require package:
151
156
  const MailTime = require('mail-time');
152
157
  ```
153
158
 
154
- Create nodemailer's transports (see [nodemailer docs](https://github.com/nodemailer/nodemailer/tree/v2#setting-up)):
159
+ Create nodemailer's transports (see [`nodemailer` docs](https://github.com/nodemailer/nodemailer/tree/v2#setting-up)):
155
160
 
156
161
  ```js
157
162
  const transports = [];
158
163
  const nodemailer = require('nodemailer');
159
164
 
160
165
  // Use DIRECT transport
161
- // To enable sending email from localhost
166
+ // and enable sending email from localhost
162
167
  // install "nodemailer-direct-transport" NPM package:
163
168
  const directTransport = require('nodemailer-direct-transport');
164
169
  const directTransportOpts = {
@@ -168,8 +173,8 @@ const directTransportOpts = {
168
173
  from: 'no-reply@example.com',
169
174
  };
170
175
  transports.push(nodemailer.createTransport(directTransport(directTransportOpts)));
171
- // IMPORTANT: Copy-paste passed options from directTransport() to
172
- // transport's "options" property, to make sure it's available to MailTime package:
176
+ // IMPORTANT: Add `.options` to a newly created transport,
177
+ // this is necessary to make sure options are available to MailTime package:
173
178
  transports[0].options = directTransportOpts;
174
179
 
175
180
  // Private SMTP
@@ -204,16 +209,15 @@ transports.push(nodemailer.createTransport({
204
209
  }));
205
210
  ```
206
211
 
207
- Create `mail-time` *Server*, it is able to send and add emails to the queue.
208
- We will need connect to MongoDB first:
212
+ 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
213
 
210
214
  ```js
215
+ const MailTime = require('mail-time');
211
216
  const MongoClient = require('mongodb').MongoClient;
212
- const MailTime = require('mail-time');
213
- const dbName = 'DatabaseName';
214
217
 
215
- // We're using environment variable MONGO_URL
216
- // to store connection string to MongoDB
218
+ const dbName = 'databaseName';
219
+
220
+ // Use MONGO_URL environment variable to store connection string to MongoDB
217
221
  // example: "MONGO_URL=mongodb://127.0.0.1:27017/myapp node mail-micro-service.js"
218
222
  MongoClient.connect(process.env.MONGO_URL, (error, client) => {
219
223
  const db = client.db(dbName);
@@ -226,7 +230,7 @@ MongoClient.connect(process.env.MONGO_URL, (error, client) => {
226
230
  from(transport) {
227
231
  // To pass spam-filters `from` field should be correctly set
228
232
  // for each transport, check `transport` object for more options
229
- return '"Awesome App" <' + transport.options.from + '>';
233
+ return `"Awesome App" <${transport.options.from}>`;
230
234
  },
231
235
  concatEmails: true, // Concatenate emails to the same addressee
232
236
  concatDelimiter: '<h1>{{{subject}}}</h1>', // Start each concatenated email with it's own subject
@@ -235,12 +239,13 @@ MongoClient.connect(process.env.MONGO_URL, (error, client) => {
235
239
  });
236
240
  ```
237
241
 
238
- Create the *Client* to add emails to queue from other application units, like UI unit:
242
+ 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
243
 
240
244
  ```js
245
+ const MailTime = require('mail-time');
241
246
  const MongoClient = require('mongodb').MongoClient;
242
- const MailTime = require('mail-time');
243
- const dbName = 'DatabaseName';
247
+
248
+ const dbName = 'databaseName';
244
249
 
245
250
  MongoClient.connect(process.env.MONGO_URL, (error, client) => {
246
251
  const db = client.db(dbName);
@@ -254,7 +259,7 @@ MongoClient.connect(process.env.MONGO_URL, (error, client) => {
254
259
  });
255
260
  ```
256
261
 
257
- Send email:
262
+ Send email example:
258
263
 
259
264
  ```js
260
265
  mailQueue.sendMail({
@@ -265,79 +270,80 @@ mailQueue.sendMail({
265
270
  });
266
271
  ```
267
272
 
268
- ## Meteor.js usage:
269
-
270
- ### Meteor.js Installation:
271
-
272
- #### Installation & Import (*via NPM*):
273
-
274
- Install NPM *MailTime* package:
275
-
276
- ```shell
277
- meteor npm install --save mail-time
278
- ```
273
+ ### Two `MailTime` instances usage example
279
274
 
280
- ES6 Import:
275
+ Create two `MailTime` instances with different settings.
281
276
 
282
277
  ```js
283
- import MailTime from 'mail-time';
284
- ```
278
+ // CREATE mailQueue 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
+ });
285
288
 
286
- #### Installation & Import (*via Atmosphere*):
289
+ // CREATE mailInstantQueue 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
+ });
287
299
 
288
- Install Atmosphere *ostrio:mailer* package:
300
+ mailQueue.sendMail({
301
+ to: 'user@gmail.com',
302
+ subject: 'You\'ve got an email!',
303
+ text: 'Plain text message',
304
+ html: '<h1>HTML</h1><p>Styled message</p>'
305
+ });
289
306
 
290
- ```shell
291
- meteor add ostrio:mailer
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
+ });
292
313
  ```
293
314
 
294
- ES6 Import:
315
+ ### Passing variables to the template
295
316
 
296
- ```js
297
- import MailTime from 'meteor/ostrio:mailer';
298
- ```
299
-
300
- ### Usage:
317
+ All options passed to the `.sendMail()` method is available inside `text`, `html`, and global templates
301
318
 
302
319
  ```js
303
- import { MongoInternals } from 'meteor/mongo';
304
-
305
- import MailTime from 'mail-time';
306
- import nodemailer from 'nodemailer';
307
- // Use DIRECT transport
308
- // To enable sending email from localhost
309
- // install "nodemailer-direct-transport" NPM package:
310
- import directTransport from 'nodemailer-direct-transport';
311
-
312
- const transports = [];
313
- const directTransportOpts = {
314
- pool: false,
315
- direct: true,
316
- name: 'mail.example.com',
317
- from: 'no-reply@example.com',
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}}! <p>Copy your login code below:</p> <pre><code>{{code}}</code></pre>`
325
+ }
318
326
  };
319
- transports.push(nodemailer.createTransport(directTransport(directTransportOpts)));
320
- // IMPORTANT: Copy-paste passed options from directTransport() to
321
- // transport's "options" property, to make sure it's available to MailTime package:
322
- transports[0].options = directTransportOpts;
323
-
324
- ////////////////////////
325
- // For more transports example see sections above and read nodemailer's docs
326
- ////////////////////////
327
327
 
328
328
  const mailQueue = new MailTime({
329
- db: MongoInternals.defaultRemoteCollectionDriver().mongo.db, // MongoDB
330
- transports,
331
- from(transport) {
332
- // To pass spam-filters `from` field should be correctly set
333
- // for each transport, check `transport` object for more options
334
- return '"Awesome App" <' + transport.options.from + '>';
335
- }
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
336
340
  });
337
341
  ```
338
342
 
339
343
  ## API
340
344
 
345
+ All available constructor options and `.sendMail()` method API overview
346
+
341
347
  ### `new MailTime(opts)` constructor
342
348
 
343
349
  - `opts` {*Object*} - Configuration object
@@ -368,15 +374,15 @@ const mailQueue = new MailTime({
368
374
  - `opts.sendAt` {*Date*} - When email should be sent, default - `new Date()` use with caution on multi-server setup at different location with the different time-zones
369
375
  - `opts.template` - Email specific template, this will override default template passed to `MailTime` constructor
370
376
  - `opts.concatSubject` - Email specific concatenation subject, this will override default concatenation subject passed to `MailTime` constructor
371
- - `opts[key]` {*Mix*} - Other custom and NodeMailer specific options, like `text`, `html` and `to`, see more [here](https://github.com/nodemailer/nodemailer/tree/v2#e-mail-message-fields). Note `attachments` should work only via `path`, and file must exists on all micro-services servers
377
+ - `opts[key]` {*Mix*} - Other custom and NodeMailer specific options, like `text`, `html` and `to`, [learn more here](https://github.com/nodemailer/nodemailer/tree/v2#e-mail-message-fields). Note `attachments` should work only via `path`, and file must exists on all micro-services servers
372
378
  - `callback` {*Function*} - Callback called after the email was sent or failed to be sent. __Do not use on multi-server setup__
373
379
 
374
380
  ### `static MailTime.Template`
375
381
 
376
- Simple and bulletproof HTML template, see its [source](https://github.com/veliovgroup/Mail-Time/blob/master/template.html). Usage:
382
+ Simple and bulletproof HTML template, see [its source](https://github.com/veliovgroup/mail-time/blob/master/template.html). Usage example:
377
383
 
378
384
  ```js
379
- const MailTime = require('mail-time');
385
+ const MailTime = require('mail-time');
380
386
  // Make it default
381
387
  const mailQueue = new MailTime({
382
388
  db: db, // MongoDB
@@ -394,6 +400,8 @@ mailQueue.sendMail({
394
400
 
395
401
  ### Template Example
396
402
 
403
+ Pass custom template via `template` property to `.sendMail()` method
404
+
397
405
  ```js
398
406
  mailQueue.sendMail({
399
407
  to: 'user@gmail.com',
@@ -441,6 +449,10 @@ DEBUG="true" EMAIL_DOMAIN="example.com" MONGO_URL="mongodb://127.0.0.1:27017/met
441
449
 
442
450
  ## Support this project:
443
451
 
444
- - [Sponsor via GitHub](https://github.com/sponsors/dr-dimitru) — support open source contributions on a regular basis
445
- - [Support via PayPal](https://paypal.me/veliovgroup) — support open source contributions once
452
+ - Star on [GitHub](https://github.com/veliovgroup/mail-time)
453
+ - Star on [NPM](https://www.npmjs.com/package/mail-time)
454
+ - Star on [Atmosphere](https://atmospherejs.com/ostrio/mailer)
455
+ - [Sponsor maintainer via GitHub](https://github.com/sponsors/dr-dimitru) — support open source with one-time contribution or on a regular basis
456
+ - [Sponsor veliovgroup via GitHub](https://github.com/sponsors/veliovgroup) — support company behind this package
457
+ - [Support via PayPal](https://paypal.me/veliovgroup) — support our open source contributions
446
458
  - 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,6 +1,6 @@
1
- const JoSk = require('josk');
2
- const NoOp = () => {};
3
- const merge = require('deepmerge');
1
+ const JoSk = require('josk');
2
+ const noop = () => {};
3
+ const merge = require('deepmerge');
4
4
  const _debug = (isDebug, ...args) => {
5
5
  if (isDebug) {
6
6
  console.info.call(console, '[DEBUG] [mail-time]', `${new Date}`, ...args);
@@ -102,13 +102,13 @@ module.exports = class MailTime {
102
102
  throw new Error('[mail-time] MongoDB database {db} option is required, like returned from `MongoClient.connect`');
103
103
  }
104
104
 
105
- this.callbacks = {};
106
- this.type = (!opts.type || (opts.type !== 'client' && opts.type !== 'server')) ? 'server' : opts.type;
107
- this.debug = (opts.debug !== true) ? false : true;
108
- this.prefix = opts.prefix || '';
109
- this.maxTries = ((opts.maxTries && !isNaN(opts.maxTries)) ? parseInt(opts.maxTries) : 59) + 1;
110
- this.interval = ((opts.interval && !isNaN(opts.interval)) ? parseInt(opts.interval) : 60) * 1000;
111
- this.template = (typeof opts.template === 'string') ? opts.template : '{{{html}}}';
105
+ this.callbacks = {};
106
+ this.type = (!opts.type || (opts.type !== 'client' && opts.type !== 'server')) ? 'server' : opts.type;
107
+ this.debug = (opts.debug !== true) ? false : true;
108
+ this.prefix = opts.prefix || '';
109
+ this.maxTries = ((opts.maxTries && !isNaN(opts.maxTries)) ? parseInt(opts.maxTries) : 59) + 1;
110
+ this.interval = ((opts.interval && !isNaN(opts.interval)) ? parseInt(opts.interval) : 60) * 1000;
111
+ this.template = (typeof opts.template === 'string') ? opts.template : '{{{html}}}';
112
112
  this.zombieTime = opts.zombieTime || 32786;
113
113
  this.keepHistory = opts.keepHistory || false;
114
114
 
@@ -124,11 +124,11 @@ module.exports = class MailTime {
124
124
  this.zombieTime = 8192;
125
125
  }
126
126
 
127
- this.strategy = (opts.strategy === 'backup' || opts.strategy === 'balancer') ? opts.strategy : 'backup';
128
127
  this.failsToNext = (opts.failsToNext && !isNaN(opts.failsToNext)) ? parseInt(opts.failsToNext) : 4;
129
- this.transports = opts.transports || [];
130
- this.transport = 0;
131
- this.from = (() => {
128
+ this.strategy = (opts.strategy === 'backup' || opts.strategy === 'balancer') ? opts.strategy : 'backup';
129
+ this.transports = opts.transports || [];
130
+ this.transport = 0;
131
+ this.from = (() => {
132
132
  if (typeof opts.from === 'string') {
133
133
  return () => {
134
134
  return opts.from;
@@ -142,9 +142,9 @@ module.exports = class MailTime {
142
142
  return false;
143
143
  })();
144
144
 
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>';
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>';
148
148
  this.concatThrottling = ((opts.concatThrottling && !isNaN(opts.concatThrottling)) ? parseInt(opts.concatThrottling) : 60) * 1000;
149
149
 
150
150
  if (this.concatThrottling < 2048) {
@@ -163,7 +163,7 @@ module.exports = class MailTime {
163
163
  }
164
164
 
165
165
  this.collection = opts.db.collection('__mailTimeQueue__' + this.prefix);
166
- this.collection.createIndex({ to: 1, isSent: 1 }, (indexError) => {
166
+ this.collection.createIndex({ to: 1, isSent: 1, sendAt: 1 }, (indexError) => {
167
167
  if (indexError) {
168
168
  _logError('[createIndex]', indexError);
169
169
  }
@@ -192,7 +192,7 @@ module.exports = class MailTime {
192
192
  // mailOptions.Other nodeMailer `sendMail` options...
193
193
 
194
194
  if (this.type === 'server') {
195
- this.scheduler = new JoSk({
195
+ const scheduler = new JoSk({
196
196
  db: opts.db,
197
197
  debug: this.debug,
198
198
  prefix: `mailTimeQueue${this.prefix}`,
@@ -202,7 +202,7 @@ module.exports = class MailTime {
202
202
  maxRevolvingDelay: this.maxRevolvingDelay
203
203
  });
204
204
 
205
- this.scheduler.setInterval(this.___send.bind(this), this.revolvingInterval, `mailTimeQueue${this.prefix}`);
205
+ scheduler.setInterval(this.___send.bind(this), this.revolvingInterval, `mailTimeQueue${this.prefix}`);
206
206
  }
207
207
  }
208
208
 
@@ -272,14 +272,13 @@ module.exports = class MailTime {
272
272
  return _mailOpts;
273
273
  }
274
274
 
275
- /*
276
- @memberOf MailTime
277
- @name ___send
278
- @param ready {Function} - See JoSk NPM package
279
- @returns {void}
275
+ /**
276
+ * @memberOf MailTime
277
+ * @name ___send
278
+ * @param ready {Function} - See JoSk NPM package
279
+ * @returns {void}
280
280
  */
281
281
  ___send(ready) {
282
- let finished = 0;
283
282
  const cursor = this.collection.find({
284
283
  isSent: false,
285
284
  sendAt: {
@@ -299,113 +298,96 @@ module.exports = class MailTime {
299
298
  }
300
299
  });
301
300
 
302
- cursor.count((countError, count) => {
303
- if (countError) {
304
- ready();
305
- cursor.close();
306
- _logError('[___send] [count] [countError]', countError);
307
- return;
308
- }
309
-
310
- if (count) {
311
- cursor.forEach((task) => {
312
- this.collection.updateOne({
313
- _id: task._id
314
- }, {
315
- $set: {
316
- isSent: true
317
- },
318
- $inc: {
319
- tries: 1
320
- }
321
- }, (updateError) => {
322
- if (count === ++finished) {
323
- 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;
324
321
  }
322
+ transportIndex = this.transport;
323
+ transport = this.transports[transportIndex];
324
+ } else {
325
+ transportIndex = task.transport;
326
+ transport = this.transports[transportIndex];
327
+ }
325
328
 
326
- if (updateError) {
327
- this.___handleError(task, updateError, {});
328
- } else {
329
- let transport;
330
- let transportIndex;
331
- if (this.strategy === 'balancer') {
332
- this.transport = this.transport + 1;
333
- if (this.transport >= this.transports.length) {
334
- this.transport = 0;
335
- }
336
- transportIndex = this.transport;
337
- transport = this.transports[this.transport];
338
- } else {
339
- transportIndex = task.transport;
340
- 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;
341
337
  }
342
338
 
343
- try {
344
- const _mailOpts = this.___compileMailOpts(transport, task);
345
-
346
- _debug(this.debug, '[sendMail] [sending] To:', _mailOpts.to);
347
- transport.sendMail(_mailOpts, (error, info) => {
348
- if (error) {
349
- this.___handleError(task, error, info);
350
- return;
351
- }
352
-
353
- if (info.accepted && !info.accepted.length) {
354
- this.___handleError(task, 'Message not accepted or Greeting never received', info);
355
- return;
356
- }
357
-
358
- _debug(this.debug, `email successfully sent, attempts: #${task.tries}, transport #${transportIndex} to: `, _mailOpts.to);
359
- if (this.keepHistory) {
360
- this.___triggerCallbacks(void 0, task, info);
361
- } else {
362
- this.collection.deleteOne({ _id: task._id }, () => {
363
- this.___triggerCallbacks(void 0, task, info);
364
- });
365
- }
366
-
367
- return;
339
+ if (info.accepted && !info.accepted.length) {
340
+ this.___handleError(task, 'Message not accepted or Greeting never received', info);
341
+ return;
342
+ }
343
+
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);
368
350
  });
369
- } catch (e) {
370
- _logError('Exception during runtime:', e);
371
- this.___handleError(task, e, {});
372
351
  }
373
- }
374
- });
375
- }, (forEachError) => {
376
- cursor.close();
377
- if (forEachError) {
378
- _logError('[___send] [forEach] [forEachError]', forEachError);
352
+
353
+ return;
354
+ });
355
+ } catch (e) {
356
+ _logError('Exception during runtime:', e);
357
+ this.___handleError(task, e, {});
379
358
  }
380
- });
381
- } else {
382
- ready();
383
- cursor.close();
359
+ }
360
+ });
361
+ }, (forEachError) => {
362
+ ready();
363
+ cursor.close();
364
+ if (forEachError) {
365
+ _logError('[___send] [forEach] [forEachError]', forEachError);
384
366
  }
385
367
  });
386
368
  }
387
369
 
388
- /*
389
- @memberOf MailTime
390
- @name send
391
- @description alias of `sendMail`
392
- @returns {void}
370
+ /**
371
+ * @memberOf MailTime
372
+ * @name send
373
+ * @description alias of `sendMail`
374
+ * @returns {void}
393
375
  */
394
376
  send(opts, callback) {
395
377
  this.sendMail(opts, callback);
396
378
  }
397
379
 
398
- /*
399
- @memberOf MailTime
400
- @name sendMail
401
- @param opts {Object} - Letter options with next properties:
402
- @param opts.sendAt {Date} - When email should be sent
403
- @param opts.template {String} - Template string
404
- @param opts[key] {mix} - Other MailOptions according to NodeMailer lib documentation
405
- @param callback {Function} - [OPTIONAL] Callback function
406
- @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}
407
389
  */
408
- sendMail(opts = {}, callback = NoOp) {
390
+ sendMail(opts = {}, callback = noop) {
409
391
  _debug(this.debug, '[sendMail] [attempt] To:', opts.to);
410
392
  if (!opts.html && !opts.text) {
411
393
  throw new Error('`html` nor `text` field is presented, at least one of those fields is required');
@@ -423,8 +405,8 @@ module.exports = class MailTime {
423
405
  opts.concatSubject = false;
424
406
  }
425
407
 
426
- let _sendAt = opts.sendAt;
427
- const _template = opts.template;
408
+ let _sendAt = opts.sendAt;
409
+ const _template = opts.template;
428
410
  const _concatSubject = opts.concatSubject;
429
411
  delete opts.sendAt;
430
412
  delete opts.template;
@@ -437,7 +419,10 @@ module.exports = class MailTime {
437
419
  if (this.concatEmails) {
438
420
  this.collection.findOne({
439
421
  to: opts.to,
440
- isSent: false
422
+ isSent: false,
423
+ sendAt: {
424
+ $lte: new Date(+_sendAt + this.concatThrottling)
425
+ }
441
426
  }, {
442
427
  projection: {
443
428
  _id: 1,
@@ -500,13 +485,13 @@ module.exports = class MailTime {
500
485
  return;
501
486
  }
502
487
 
503
- /*
504
- @memberOf MailTime
505
- @name ___handleError
506
- @param task {Object} - Email task record form Mongo
507
- @param error {mix} - Error String/Object/Error
508
- @param info {Object} - Info object returned from nodemailer
509
- @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}
510
495
  */
511
496
  ___handleError(task, error, info) {
512
497
  if (!task) {
@@ -514,12 +499,14 @@ module.exports = class MailTime {
514
499
  }
515
500
 
516
501
  if (task.tries >= this.maxTries) {
517
- this.collection.deleteOne({
518
- _id: task._id
519
- }, (deleteError) => {
520
- _debug(this.debug, `Giving up trying send email after ${task.tries} attempts to: `, task.mailOptions[0].to, error, deleteError);
521
- this.___triggerCallbacks(error, task, info);
522
- });
502
+ if (!this.keepHistory) {
503
+ this.collection.deleteOne({
504
+ _id: task._id
505
+ }, (deleteError) => {
506
+ _debug(this.debug, `Giving up trying send email after ${task.tries} attempts to: `, task.mailOptions[0].to, error, deleteError);
507
+ this.___triggerCallbacks(error, task, info);
508
+ });
509
+ }
523
510
  } else {
524
511
  let transportIndex = task.transport;
525
512
 
@@ -544,16 +531,16 @@ module.exports = class MailTime {
544
531
  }
545
532
  }
546
533
 
547
- /*
548
- @memberOf MailTime
549
- @name ___addToQueue
550
- @param opts {Object} - Letter options with next properties:
551
- @param opts.sendAt {Date} - When email should be sent
552
- @param opts.template {String} - Email template
553
- @param opts.mailOptions {Object} - MailOptions according to NodeMailer lib
554
- @param opts.concatSubject {String} - Email subject used when sending multiple concatenated emails
555
- @param callback {Function} - [OPTIONAL] Callback function
556
- @returns {void}
534
+ /**
535
+ * @memberOf MailTime
536
+ * @name ___addToQueue
537
+ * @param opts {Object} - Letter options with next properties:
538
+ * @param opts.sendAt {Date} - When email should be sent
539
+ * @param opts.template {String} - Email template
540
+ * @param opts.mailOptions {Object} - MailOptions according to NodeMailer lib
541
+ * @param opts.concatSubject {String} - Email subject used when sending multiple concatenated emails
542
+ * @param callback {Function} - [OPTIONAL] Callback function
543
+ * @returns {void}
557
544
  */
558
545
  ___addToQueue(opts, callback) {
559
546
  _debug(this.debug, '[sendMail] [adding to queue] To:', opts.mailOptions.to);
@@ -587,16 +574,16 @@ module.exports = class MailTime {
587
574
  });
588
575
  }
589
576
 
590
- /*
591
- @memberOf MailTime
592
- @name ___render
593
- @param string {String} - Template with Spacebars/Blaze/Mustache-like placeholders
594
- @param replacements {Object} - Blaze/Mustache-like helpers Object
595
- @returns {String}
577
+ /**
578
+ * @memberOf MailTime
579
+ * @name ___render
580
+ * @param string {String} - Template with Spacebars/Blaze/Mustache-like placeholders
581
+ * @param replacements {Object} - Blaze/Mustache-like helpers Object
582
+ * @returns {String}
596
583
  */
597
584
  ___render(_string, replacements) {
598
585
  let i;
599
- let string = _string;
586
+ let string = _string;
600
587
  const matchHTML = string.match(/\{{3}\s?([a-zA-Z0-9\-\_]+)\s?\}{3}/g);
601
588
  if (matchHTML) {
602
589
  for (i = 0; i < matchHTML.length; i++) {
@@ -606,7 +593,7 @@ module.exports = class MailTime {
606
593
  }
607
594
  }
608
595
 
609
- const matchStr = string.match(/\{{2}\s?([a-zA-Z0-9\-\_]+)\s?\}{2}/g);
596
+ const matchStr = string.match(/\{{2}\s?([a-zA-Z0-9\-\_]+)\s?\}{2}/g);
610
597
  if (matchStr) {
611
598
  for (i = 0; i < matchStr.length; i++) {
612
599
  if (replacements[matchStr[i].replace('{{', '').replace('}}', '').trim()]) {
@@ -617,13 +604,13 @@ module.exports = class MailTime {
617
604
  return string;
618
605
  }
619
606
 
620
- /*
621
- @memberOf MailTime
622
- @name ___triggerCallbacks
623
- @param error {mix} - Error thrown during sending email
624
- @param task {Object} - Task record from mongodb
625
- @param info {Object} - Info object returned from NodeMailer's `sendMail` method
626
- @returns {void 0}
607
+ /**
608
+ * @memberOf MailTime
609
+ * @name ___triggerCallbacks
610
+ * @param error {mix} - Error thrown during sending email
611
+ * @param task {Object} - Task record from mongodb
612
+ * @param info {Object} - Info object returned from NodeMailer's `sendMail` method
613
+ * @returns {void 0}
627
614
  */
628
615
  ___triggerCallbacks(error, task, info) {
629
616
  const _id = task._id.toHexString();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mail-time",
3
- "version": "1.2.6",
4
- "description": "Bulletproof email queue on top of NodeMailer with support of multiple clusters and servers setup",
3
+ "version": "1.3.2",
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.4.0"
52
+ "josk": "^3.0.1"
53
53
  },
54
54
  "devDependencies": {
55
- "bson": "^4.2.2",
56
- "bson-ext": "^2.0.5",
57
- "chai": "^4.3.0",
58
- "mocha": "^8.2.1",
59
- "mongodb": "^3.6.4",
60
- "nodemailer": "^6.4.17",
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.7.0",
60
+ "nodemailer": "^6.7.5",
61
+ "nodemailer-direct-transport": "^3.0.7"
62
62
  }
63
63
  }