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 +1 -1
- package/README.md +139 -52
- package/index.js +145 -167
- package/package.json +13 -13
- package/CHANGELOG.md +0 -1
- package/HISTORY.md +0 -1
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
<a href="https://
|
|
4
|
-
<img src="https://
|
|
1
|
+
[](https://github.com/sponsors/veliovgroup)
|
|
2
|
+
[](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
|
-
|
|
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/
|
|
22
|
-
- [With single SMTP](https://github.com/
|
|
23
|
-
- [With multiple SMTP](https://github.com/
|
|
24
|
-
- [As Micro-Service](https://github.com/
|
|
25
|
-
- [Features](https://github.com/
|
|
26
|
-
- [Installation](https://github.com/
|
|
27
|
-
- [Meteor.js Installation](https://github.com/
|
|
28
|
-
- [Meteor.js Installation](https://github.com/
|
|
29
|
-
- [Usage example](https://github.com/
|
|
30
|
-
- [API](https://github.com/
|
|
31
|
-
- [*Constructor*](https://github.com/
|
|
32
|
-
- [`.send()`](https://github.com/
|
|
33
|
-
- [Default Template](https://github.com/
|
|
34
|
-
- [Custom Templates](https://github.com/
|
|
35
|
-
- [~92% tests coverage](https://github.com/
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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,
|
|
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
|
|
123
|
-
- When concatenation is enabled - Same emails
|
|
124
|
-
- Balancing for multiple nodemailer's transports, two modes - `backup` and `balancing`.
|
|
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
|
-
-
|
|
132
|
+
- Templating support with [Mustache](https://mustache.github.io/)-like placeholders
|
|
127
133
|
|
|
128
134
|
## Installation
|
|
129
135
|
|
|
130
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
347
|
+
### Installation & Import (*via NPM*):
|
|
273
348
|
|
|
274
|
-
Install NPM
|
|
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
|
-
|
|
361
|
+
### Installation & Import (*via Atmosphere*):
|
|
287
362
|
|
|
288
|
-
Install Atmosphere
|
|
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/
|
|
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/
|
|
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/
|
|
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
|
|
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
|
-
-
|
|
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
|
|
2
|
-
const
|
|
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
|
|
151
|
-
this.concatSubject
|
|
152
|
-
this.concatDelimiter
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
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.
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
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
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
if (
|
|
347
|
-
this.
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
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
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
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
|
-
|
|
396
|
-
|
|
397
|
-
_logError('
|
|
352
|
+
|
|
353
|
+
return;
|
|
354
|
+
});
|
|
355
|
+
} catch (e) {
|
|
356
|
+
_logError('Exception during runtime:', e);
|
|
357
|
+
this.___handleError(task, e, {});
|
|
398
358
|
}
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
|
|
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 =
|
|
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
|
|
445
|
-
const _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
|
|
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.
|
|
4
|
-
"description": "Bulletproof email queue on top of NodeMailer
|
|
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/
|
|
39
|
+
"url": "git://github.com/veliovgroup/Mail-Time.git"
|
|
40
40
|
},
|
|
41
41
|
"bugs": {
|
|
42
|
-
"url": "https://github.com/
|
|
42
|
+
"url": "https://github.com/veliovgroup/Mail-Time/issues"
|
|
43
43
|
},
|
|
44
|
-
"homepage": "https://github.com/
|
|
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": "^
|
|
52
|
+
"josk": "^3.0.0"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
|
-
"bson": "^4.
|
|
56
|
-
"bson-ext": "^
|
|
57
|
-
"chai": "^4.
|
|
58
|
-
"mocha": "^
|
|
59
|
-
"mongodb": "^
|
|
60
|
-
"nodemailer": "^6.
|
|
61
|
-
"nodemailer-direct-transport": "^3.
|
|
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
|