mail-time 1.2.5 → 1.3.1

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