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 +1 -1
- package/HISTORY.md +1 -1
- package/LICENSE +1 -1
- package/README.md +109 -97
- package/index.js +144 -157
- package/package.json +13 -13
package/CHANGELOG.md
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
For full changelog see [releases](https://github.com/veliovgroup/
|
|
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/
|
|
1
|
+
For full history see [releases](https://github.com/veliovgroup/mail-time/releases) in GitHub
|
package/LICENSE
CHANGED
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/
|
|
20
|
-
- [With single SMTP](https://github.com/veliovgroup/
|
|
21
|
-
- [With multiple SMTP](https://github.com/veliovgroup/
|
|
22
|
-
- [As Micro-Service](https://github.com/veliovgroup/
|
|
23
|
-
- [Features](https://github.com/veliovgroup/
|
|
24
|
-
- [Installation](https://github.com/veliovgroup/
|
|
25
|
-
- [Meteor.js
|
|
26
|
-
- [
|
|
27
|
-
- [
|
|
28
|
-
- [
|
|
29
|
-
- [
|
|
30
|
-
- [
|
|
31
|
-
|
|
32
|
-
- [
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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,
|
|
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`.
|
|
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
|
-
-
|
|
131
|
+
- Templating support with [Mustache](https://mustache.github.io/)-like placeholders
|
|
127
132
|
|
|
128
133
|
## Installation
|
|
129
134
|
|
|
130
|
-
|
|
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
|
-
//
|
|
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:
|
|
172
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
216
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
243
|
-
const dbName
|
|
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
|
-
|
|
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
|
-
|
|
275
|
+
Create two `MailTime` instances with different settings.
|
|
281
276
|
|
|
282
277
|
```js
|
|
283
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
291
|
-
|
|
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
|
-
|
|
315
|
+
### Passing variables to the template
|
|
295
316
|
|
|
296
|
-
|
|
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
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
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:
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
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`,
|
|
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
|
|
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
|
|
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
|
-
-
|
|
445
|
-
-
|
|
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
|
|
2
|
-
const
|
|
3
|
-
const merge
|
|
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
|
|
107
|
-
this.debug
|
|
108
|
-
this.prefix
|
|
109
|
-
this.maxTries
|
|
110
|
-
this.interval
|
|
111
|
-
this.template
|
|
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.
|
|
130
|
-
this.
|
|
131
|
-
this.
|
|
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
|
|
146
|
-
this.concatSubject
|
|
147
|
-
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>';
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
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
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
if (
|
|
332
|
-
this.
|
|
333
|
-
|
|
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
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
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
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
352
|
+
|
|
353
|
+
return;
|
|
354
|
+
});
|
|
355
|
+
} catch (e) {
|
|
356
|
+
_logError('Exception during runtime:', e);
|
|
357
|
+
this.___handleError(task, e, {});
|
|
379
358
|
}
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
|
|
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 =
|
|
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
|
|
427
|
-
const _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.
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
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
|
|
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
|
|
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
|
|
4
|
-
"description": "Bulletproof email queue on top of NodeMailer
|
|
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/
|
|
39
|
+
"url": "git://github.com/veliovgroup/mail-time.git"
|
|
40
40
|
},
|
|
41
41
|
"bugs": {
|
|
42
|
-
"url": "https://github.com/veliovgroup/
|
|
42
|
+
"url": "https://github.com/veliovgroup/mail-time/issues"
|
|
43
43
|
},
|
|
44
|
-
"homepage": "https://github.com/veliovgroup/
|
|
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.1"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
|
-
"bson": "^4.
|
|
56
|
-
"bson-ext": "^
|
|
57
|
-
"chai": "^4.3.
|
|
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.7.0",
|
|
60
|
+
"nodemailer": "^6.7.5",
|
|
61
|
+
"nodemailer-direct-transport": "^3.0.7"
|
|
62
62
|
}
|
|
63
63
|
}
|