mail-time 1.4.0 → 3.0.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/README.md CHANGED
@@ -5,9 +5,9 @@
5
5
 
6
6
  # MailTime
7
7
 
8
- "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. Mail-Time made for single-server and horizontally scaled multi-server setups in mind.
8
+ "Mail-Time" is NPM package for mail queue management. Build on top of the [`nodemailer`](https://github.com/nodemailer/nodemailer) package. Mail-Time made for single-server and horizontally scaled multi-server setups in mind.
9
9
 
10
- Every `MailTime` instance can have `type` configured as *Server* or *Client*.
10
+ Every `MailTime` instance can have `type` configured as *Server* or *Client*. *Server* type of `MailTime` is great for creating an emailing micro-service app.
11
11
 
12
12
  The main difference between *Server* and *Client* `type` is that the *Server* handles the queue and __sends__ email. While the *Client* only __adds__ emails into the queue.
13
13
 
@@ -16,21 +16,37 @@ The main difference between *Server* and *Client* `type` is that the *Server* ha
16
16
  - [How it works?](https://github.com/veliovgroup/mail-time?tab=readme-ov-file#how-it-works)
17
17
  - [With single SMTP](https://github.com/veliovgroup/mail-time?tab=readme-ov-file#single-point-of-failure)
18
18
  - [With multiple SMTP](https://github.com/veliovgroup/mail-time?tab=readme-ov-file#multiple-smtp-providers)
19
- - [For Clusters](https://github.com/veliovgroup/mail-time?tab=readme-ov-file#sending-emails-from-cluster-of-servers)
19
+ - [For horizontally-scaled apps](https://github.com/veliovgroup/mail-time?tab=readme-ov-file#sending-emails-from-cluster-of-servers)
20
20
  - [Features](https://github.com/veliovgroup/mail-time?tab=readme-ov-file#features)
21
21
  - [Installation](https://github.com/veliovgroup/mail-time?tab=readme-ov-file#installation)
22
22
  - [Meteor.js usage](https://github.com/veliovgroup/mail-time/blob/master/docs/meteor.md)
23
- - [Usage example](https://github.com/veliovgroup/mail-time?tab=readme-ov-file#basic-usage)
23
+ - [Usage examples](https://github.com/veliovgroup/mail-time?tab=readme-ov-file#basic-usage)
24
+ - [Require/Import `mail-time`](https://github.com/veliovgroup/mail-time?tab=readme-ov-file#1-require-package)
25
+ - [Create NodeMailer's transports](https://github.com/veliovgroup/mail-time?tab=readme-ov-file#2-create-nodemailers-transports)
26
+ - [Initiate `mail-time`](https://github.com/veliovgroup/mail-time?tab=readme-ov-file#3-initiate-mail-time)
27
+ - [Connect to Redis](https://github.com/veliovgroup/mail-time?tab=readme-ov-file#3a-initiate-and-connect-to-redis)
28
+ - [Connect to MongoDB](https://github.com/veliovgroup/mail-time?tab=readme-ov-file#3b-initiate-and-connect-to-mongodb)
29
+ - [Initiate as *Client* instance](https://github.com/veliovgroup/mail-time?tab=readme-ov-file#3c-optionally-create-client-type-of-mailtime)
30
+ - [Initiate two `MailTime` instances within the single app](https://github.com/veliovgroup/mail-time?tab=readme-ov-file#two-mailtime-instances-usage-example)
31
+ - [Use templates](https://github.com/veliovgroup/mail-time?tab=readme-ov-file#passing-variables-to-the-template)
32
+ - Different storage configurations:
33
+ - [Use MongoDB for queue and scheduler](https://github.com/veliovgroup/mail-time?tab=readme-ov-file#using-mongodb-for-queue-and-scheduler)
34
+ - [Use MongoDB for queue and Redis for scheduler](https://github.com/veliovgroup/mail-time?tab=readme-ov-file#using-mongodb-for-queue-and-redis-for-scheduler)
35
+ - [Use Redis for queue and MongoDB for scheduler](https://github.com/veliovgroup/mail-time?tab=readme-ov-file#using-redis-for-queue-and-mongodb-for-scheduler)
36
+ - [Use Redis for queue and scheduler](https://github.com/veliovgroup/mail-time?tab=readme-ov-file#using-redis-for-queue-and-scheduler)
24
37
  - [API](https://github.com/veliovgroup/mail-time?tab=readme-ov-file#api)
25
- - [*Constructor*](https://github.com/veliovgroup/mail-time?tab=readme-ov-file#new-mailtimeopts-constructor)
26
- - [`.send()`](https://github.com/veliovgroup/mail-time?tab=readme-ov-file#sendmailopts--callback)
38
+ - [`new MailTime` *Constructor*](https://github.com/veliovgroup/mail-time?tab=readme-ov-file#new-mailtimeopts-constructor)
39
+ - [`new RedisQueue` *Constructor*](https://github.com/veliovgroup/mail-time?tab=readme-ov-file#new-redisqueueopts-constructor)
40
+ - [`new MongoQueue` *Constructor*](https://github.com/veliovgroup/mail-time?tab=readme-ov-file#new-mongoqueueopts-constructor)
41
+ - [`.sendMail()`](https://github.com/veliovgroup/mail-time?tab=readme-ov-file#sendmailopts)
42
+ - [`.cancelMail()`](https://github.com/veliovgroup/mail-time?tab=readme-ov-file#cancelmailuuid)
27
43
  - [Default Template](https://github.com/veliovgroup/mail-time?tab=readme-ov-file#static-mailtimetemplate)
28
44
  - [Custom Templates](https://github.com/veliovgroup/mail-time?tab=readme-ov-file#template-example)
29
- - [~92% tests coverage](https://github.com/veliovgroup/mail-time?tab=readme-ov-file#testing)
45
+ - [Running tests](https://github.com/veliovgroup/mail-time?tab=readme-ov-file#testing)
30
46
 
31
47
  ## Main features:
32
48
 
33
- - 👨‍🔬 ~92% tests coverage;
49
+ - 👨‍🔬 ~94% tests coverage;
34
50
  - 📦 Two simple dependencies, written from scratch for top performance;
35
51
  - 🏢 Synchronize email queue across multiple (horizontally scaled) servers;
36
52
  - 💪 Bulletproof design, built-in retries.
@@ -41,7 +57,7 @@ Redundant solution for email transmission.
41
57
 
42
58
  ### Single point of failure
43
59
 
44
- Issue - mitigate a single point of failure:
60
+ Issue - mitigate a single point of failure via persistent queue and re-send attempts
45
61
 
46
62
  ```ascii
47
63
  |----------------| |------| |------------------|
@@ -71,7 +87,7 @@ it will keep the letter in the queue and retry to send it again
71
87
 
72
88
  ### Multiple SMTP providers
73
89
 
74
- Rotate or backup email transports by using multiple SMTP providers
90
+ Rotate email transports by using multiple SMTP providers. *MailTime* support two strategies `backup` (*rotate when failed*) and `balancer` (*round-robin rotation*)
75
91
 
76
92
  ```ascii
77
93
  |--------|
@@ -91,9 +107,9 @@ Rotate or backup email transports by using multiple SMTP providers
91
107
 
92
108
  It is common to have horizontally scaled "Cluster" of servers for load-balancing and for durability.
93
109
 
94
- 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. But in "Cluster" implementation — each server will attempt to send the same email. MailTime built to avoid sending the same email multiple times to a user from horizontally scaled applications.
110
+ 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. But in "Cluster" implementation — each server will attempt to send the same email. *MailTime* built to avoid sending the same email multiple times to a user from horizontally scaled applications.
95
111
 
96
- For the maximum durability and agility each Application Server can run MailTime in the "Server" mode:
112
+ For the maximum durability and agility each Application Server can run *MailTime* in the "Server" mode:
97
113
 
98
114
  ```ascii
99
115
  |===================THE=CLUSTER===================| |=QUEUE=|
@@ -103,7 +119,7 @@ For the maximum durability and agility each Application Server can run MailTime
103
119
  | |-----\----| |----\-----| |----\-----| | | | |-------------|
104
120
  | \---------------\----------------\----------> | |--------| | ^_^ |
105
121
  | | | |-->| SMTP 2 |-->| Happy users |
106
- | Each "App Server" or "Cluster Node" | | | |--------| |-------------|
122
+ | Each "App Server" | | | |--------| |-------------|
107
123
  | runs MailTime as a "Server" | | | /
108
124
  | for the maximum durability | | | |--------| /
109
125
  | | | |-->| SMTP 3 |-----/
@@ -111,7 +127,7 @@ For the maximum durability and agility each Application Server can run MailTime
111
127
  |=================================================| |=======|
112
128
  ```
113
129
 
114
- To split roles MailTime can run on a dedicated machine as micro-service. This case is great for private email servers with implemented authentication via rDNS and PTR records:
130
+ To split roles *MailTime* can run on a dedicated machine as micro-service. This case is great for private email servers with implemented authentication via rDNS and PTR records:
115
131
 
116
132
  ```ascii
117
133
  |===================THE=CLUSTER===================| |=QUEUE=| |===Mail=Time===|
@@ -131,17 +147,16 @@ To split roles MailTime can run on a dedicated machine as micro-service. This ca
131
147
 
132
148
  ## Features
133
149
 
134
- - Queue - Managed via MongoDB, will survive server reboots and failures
135
- - Support for multiple server setups - "Cluster", Phusion Passenger instances, Load Balanced solutions, etc.
136
- - Emails concatenation by addressee email - Reduce amount of sent emails to a single user with concatenation, and avoid mistakenly doubled emails
137
- - 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
138
- - 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
139
- - Sending retries for network and other failures
140
- - Templating support with [Mustache](https://mustache.github.io/)-like placeholders
150
+ - __Email Queue__ - Managed via MongoDB, Redis, or [Custom Queue](https://github.com/veliovgroup/mail-time/blob/master/docs/queue-api.md). Storage-based queue will survive server reboots and failures
151
+ - __Made for horizontally scaled multi-server setups__ - *MailTime* is made to run in multi-server environments, like "Clusters", multiple app instances, load balanced solutions, and replications. *MailTime* is the perfect fit for applications scaled on a single machine, multiple virtual servers, multiple "bare metal" servers, within single or multiple data centers
152
+ - __Email concatenation__ - Reduce amount of sent emails to a single user with concatenation, and avoid mistakenly duplicated emails. When "email concatenation" is enabled the same emails (*checked by addressee and content*) won't be sent twice. If emails are sent multiple times, due to issues in logic or application failures, - enable "email concatenation" to solve this behavior
153
+ - __Multiple NodeMailer/SMTP transports__ — Support for multiple SMPT transports implemented in two modes - `backup` and `balancing`. Use this feature to reduce the cost of SMTP services and add extra layer of durability. When one of the transports is failing to send an email — `mail-time` will switch to the next one
154
+ - __Sending retries__ — Built-in retries for failed to send emails due to network or other failures
155
+ - __Templating__ — Built with support of [Mustache](https://mustache.github.io/)-like placeholders, see [templating docs](https://github.com/veliovgroup/mail-time?tab=readme-ov-file#passing-variables-to-the-template)
141
156
 
142
157
  ## Installation
143
158
 
144
- 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:
159
+ To implement Server functionality — begin with installing `nodemailer`, although this package meant to be used with `nodemailer`, it's not added as the dependency, as `nodemailer` not needed by Client, and to give freedom to choose `nodemailer`'s version to fit every project needs:
145
160
 
146
161
  ```shell
147
162
  npm install --save nodemailer
@@ -162,19 +177,38 @@ npm install --save mail-time@=0.1.7
162
177
 
163
178
  ## Basic usage
164
179
 
165
- Require package:
180
+ Setup Nodemailer's transports, Queue storage, and *MailTime* instance
181
+
182
+ ### Steps to get started
183
+
184
+ See steps 1-4 below to learn about different parts of *MailTime* library and how it can get used. From configuration options to sending email
185
+
186
+ 1. [Require `mail-time` package](https://github.com/veliovgroup/mail-time?tab=readme-ov-file#1-require-package)
187
+ 2. [Create NodeMailer's transports](https://github.com/veliovgroup/mail-time?tab=readme-ov-file#2-create-nodemailers-transports)
188
+ 3. [Initiate `mail-time` *server*](https://github.com/veliovgroup/mail-time?tab=readme-ov-file#3-initiate-mail-time)
189
+ - a. [Connect to Redis](https://github.com/veliovgroup/mail-time?tab=readme-ov-file#3a-initiate-and-connect-to-redis); Or
190
+ - b. [Connect to MongoDB](https://github.com/veliovgroup/mail-time?tab=readme-ov-file#3b-initiate-and-connect-to-mongodb); And
191
+ - c. [*optionally*] [initiate `mail-time` as *client*](https://github.com/veliovgroup/mail-time?tab=readme-ov-file#3c-optionally-create-client-type-of-mailtime)
192
+ 4. [Start sending emails](https://github.com/veliovgroup/mail-time?tab=readme-ov-file#4-send-email)
193
+
194
+ #### 1. Require package
166
195
 
167
196
  ```js
168
197
  // import as ES Module
169
- import MailTime from 'mail-time';
198
+ import { MailTime, MongoQueue, RedisQueue } from 'mail-time';
170
199
 
171
- // requires as CommonJS
172
- const MailTime = require('mail-time');
200
+ // require as CommonJS
201
+ const { MailTime, MongoQueue, RedisQueue } = require('mail-time');
173
202
  ```
174
203
 
175
- Create nodemailer's transports, for details see [`nodemailer` docs](https://github.com/nodemailer/nodemailer/tree/v2#setting-up):
204
+ #### 2. Create NodeMailer's transports
205
+
206
+ For compatibility and flexibility *MailTime* has no dependency on `nodemailer` it should be installed and imported manually. Create one or more "SMTP transports" before initializing new *MailTime* instance.
207
+
208
+ For details and full list of options available in `.createTransport()` see [`nodemailer` docs](https://nodemailer.com/smtp/)
176
209
 
177
210
  ```js
211
+ // transports.js
178
212
  import nodemailer from 'nodemailer';
179
213
  // Use DIRECT transport
180
214
  // and enable sending email from localhost
@@ -223,62 +257,123 @@ transports.push(nodemailer.createTransport({
223
257
  pass: 'xxx'
224
258
  },
225
259
  }));
260
+
261
+ export { transports };
226
262
  ```
227
263
 
228
- 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:
264
+ #### 3. Initiate `mail-time`
265
+
266
+ Create new instance of *MailTime* in the *Server* mode, — it will be able to __send__ and __add__ emails to the queue.
267
+
268
+ #### 3a. Initiate and connect to Redis
269
+
270
+ Connecting to Redis before initiating `new MailTime` instance:
229
271
 
230
272
  ```js
231
- const MailTime = require('mail-time');
232
- const MongoClient = require('mongodb').MongoClient;
273
+ // mail-queue.js
274
+ import { MailTime, RedisQueue } from 'mail-time';
275
+ import { createClient } from 'redis';
276
+ import { transports } from './transports.js';
277
+
278
+ // Use REDIS_URL environment variable to store connection string to MongoDB
279
+ // example: "REDIS_URL=redis://127.0.0.1:6379/myapp node mail-micro-service.js"
280
+ const redisClient = await createClient({ url: process.env.REDIS_URL }).connect();
281
+ const mailQueue = new MailTime({
282
+ transports,
283
+ queue: new MongoQueue({
284
+ client: redisClient,
285
+ }),
286
+ josk: {
287
+ adapter: {
288
+ type: 'redis',
289
+ client: redisClient,
290
+ }
291
+ },
292
+ template: MailTime.Template // Use default template
293
+ from(transport) {
294
+ // To pass spam-filters `from` field should be correctly set
295
+ // for each transport, check `transport` object for more options
296
+ return `"Awesome App" <${transport.options.from}>`;
297
+ },
298
+ onError(error, email, details) {
299
+ console.log(`Email "${email.mailOptions.subject}" wasn't sent to ${email.mailOptions.to}`, error, details);
300
+ },
301
+ onSent(email, details) {
302
+ console.log(`Email "${email.mailOptions.subject}" successfully sent to ${email.mailOptions.to}`, details);
303
+ },
304
+ });
233
305
 
234
- const dbName = 'databaseName';
306
+ export { mailQueue };
307
+ ```
308
+
309
+ #### 3b. Initiate and connect to MongoDB
310
+
311
+ Connecting to MongoDB before initiating `new MailTime` instance:
312
+
313
+ ```js
314
+ // mail-queue.js
315
+ import { MailTime, MongoQueue } from 'mail-time';
316
+ import { MongoClient } from 'mongodb';
317
+ import { transports } from './transports.js';
235
318
 
236
319
  // Use MONGO_URL environment variable to store connection string to MongoDB
237
320
  // example: "MONGO_URL=mongodb://127.0.0.1:27017/myapp node mail-micro-service.js"
238
- MongoClient.connect(process.env.MONGO_URL, (error, client) => {
239
- const db = client.db(dbName);
240
-
241
- const mailQueue = new MailTime({
242
- db, // MongoDB
243
- type: 'server',
244
- strategy: 'balancer', // Transports will be used in round robin chain
245
- transports,
246
- from(transport) {
247
- // To pass spam-filters `from` field should be correctly set
248
- // for each transport, check `transport` object for more options
249
- return `"Awesome App" <${transport.options.from}>`;
250
- },
251
- concatEmails: true, // Concatenate emails to the same addressee
252
- concatDelimiter: '<h1>{{{subject}}}</h1>', // Start each concatenated email with it's own subject
253
- template: MailTime.Template // Use default template
254
- });
321
+ const mongodb = (await MongoClient.connect(process.env.MONGO_URL)).db('database');
322
+ const mailQueue = new MailTime({
323
+ transports,
324
+ queue: new MongoQueue({
325
+ db: mongodb,
326
+ }),
327
+ josk: {
328
+ adapter: {
329
+ type: 'mongo',
330
+ db: mongodb,
331
+ }
332
+ },
333
+ template: MailTime.Template // Use default template
334
+ from(transport) {
335
+ // To pass spam-filters `from` field should be correctly set
336
+ // for each transport, check `transport` object for more options
337
+ return `"Awesome App" <${transport.options.from}>`;
338
+ },
339
+ onError(error, email, details) {
340
+ console.log(`Email "${email.mailOptions.subject}" wasn't sent to ${email.mailOptions.to}`, error, details);
341
+ },
342
+ onSent(email, details) {
343
+ console.log(`Email "${email.mailOptions.subject}" successfully sent to ${email.mailOptions.to}`, details);
344
+ },
255
345
  });
256
- ```
257
346
 
258
- 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:
347
+ export { mailQueue };
348
+ ```
259
349
 
260
- ```js
261
- const MailTime = require('mail-time');
262
- const MongoClient = require('mongodb').MongoClient;
350
+ #### 3c. Optionally create *Client* type of *MailTime*
263
351
 
264
- const dbName = 'databaseName';
352
+ 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
265
353
 
266
- MongoClient.connect(process.env.MONGO_URL, (error, client) => {
267
- const db = client.db(dbName);
354
+ ```js
355
+ // mail-queue.js
356
+ import { MailTime, RedisQueue } from 'mail-time';
357
+ import { createClient } from 'redis';
268
358
 
269
- const mailQueue = new MailTime({
270
- db,
271
- type: 'client',
272
- strategy: 'balancer', // Transports will be used in round robin chain
273
- concatEmails: true // Concatenate emails to the same address
274
- });
359
+ const mailQueue = new MailTime({
360
+ type: 'client',
361
+ queue: new RedisQueue({
362
+ client: await createClient({ url: 'redis://url' }).connect()
363
+ }),
275
364
  });
365
+
366
+ export { mailQueue };
276
367
  ```
277
368
 
278
- Send email example:
369
+ #### 4. Send email
370
+
371
+ Import created `mailQueue` where needed and call `.sendMail()` method. See [NodeMailer's message configuration documentation](https://nodemailer.com/message/) for details
279
372
 
280
373
  ```js
281
- mailQueue.sendMail({
374
+ import { mailQueue } from './mail-queue.js';
375
+
376
+ await mailQueue.sendMail({
282
377
  to: 'user@gmail.com',
283
378
  subject: 'You\'ve got an email!',
284
379
  text: 'Plain text message',
@@ -286,41 +381,174 @@ mailQueue.sendMail({
286
381
  });
287
382
  ```
288
383
 
384
+ ### Using MongoDB for queue and scheduler
385
+
386
+ *MailTime* uses separate storage for Queue management and Scheduler. In the example below MongoDB is used for both
387
+
388
+ ```js
389
+ import { MailTime, MongoQueue } from 'mail-time';
390
+ import { MongoClient } from 'mongodb';
391
+ import { transports } from './transports.js';
392
+
393
+ const db = (await MongoClient.connect('mongodb://url')).db('database');
394
+ const mailQueue = new MailTime({
395
+ queue: new MongoQueue({
396
+ db: db,
397
+ }),
398
+ josk: {
399
+ adapter: {
400
+ type: 'mongo',
401
+ db: db,
402
+ }
403
+ },
404
+ transports,
405
+ from(transport) {
406
+ // To pass spam-filters `from` field should be correctly set
407
+ // for each transport, check `transport` object for more options
408
+ return `"Awesome App" <${transport.options.from}>`;
409
+ }
410
+ });
411
+ ```
412
+
413
+ ### Using MongoDB for queue and Redis for scheduler
414
+
415
+ *MailTime* uses separate storage for Queue management and Scheduler. In the example below MongoDB is used for queue and Redis is used for scheduler
416
+
417
+ ```js
418
+ import { MailTime, MongoQueue } from 'mail-time';
419
+ import { MongoClient } from 'mongodb';
420
+ import { createClient } from 'redis';
421
+ import { transports } from './transports.js';
422
+
423
+ const mailQueue = new MailTime({
424
+ queue: new MongoQueue({
425
+ db: (await MongoClient.connect('mongodb://url')).db('database'),
426
+ }),
427
+ josk: {
428
+ adapter: {
429
+ type: 'redis',
430
+ client: await createClient({ url: 'redis://url' }).connect(),
431
+ }
432
+ },
433
+ transports,
434
+ from(transport) {
435
+ return `"Awesome App" <${transport.options.from}>`;
436
+ }
437
+ });
438
+ ```
439
+
440
+ ### Using Redis for queue and MongoDB for scheduler
441
+
442
+ *MailTime* uses separate storage for Queue management and Scheduler. In the example below Redis is used for queue and MongoDB is used for scheduler
443
+
444
+ ```js
445
+ import { MailTime, RedisQueue } from 'mail-time';
446
+ import { MongoClient } from 'mongodb';
447
+ import { createClient } from 'redis';
448
+ import { transports } from './transports.js';
449
+
450
+ const mailQueue = new MailTime({
451
+ queue: new RedisQueue({
452
+ client: await createClient({ url: 'redis://url' }).connect(),
453
+ }),
454
+ josk: {
455
+ adapter: {
456
+ type: 'mongo',
457
+ db: (await MongoClient.connect('mongodb://url')).db('database'),
458
+ }
459
+ },
460
+ transports,
461
+ from(transport) {
462
+ return `"Awesome App" <${transport.options.from}>`;
463
+ }
464
+ });
465
+ ```
466
+
467
+ ### Using Redis for queue and scheduler
468
+
469
+ *MailTime* uses separate storage for Queue management and Scheduler. In the example below Redis is used for both
470
+
471
+ ```js
472
+ import { MailTime, RedisQueue } from 'mail-time';
473
+ import { MongoClient } from 'mongodb';
474
+ import { createClient } from 'redis';
475
+ import { transports } from './transports.js';
476
+
477
+ const redisClient = await createClient({ url: 'redis://url' }).connect();
478
+ const mailQueue = new MailTime({
479
+ queue: new RedisQueue({
480
+ client: redisClient,
481
+ }),
482
+ josk: {
483
+ adapter: {
484
+ type: 'redis',
485
+ client: redisClient,
486
+ }
487
+ },
488
+ transports,
489
+ from(transport) {
490
+ return `"Awesome App" <${transport.options.from}>`;
491
+ }
492
+ });
493
+ ```
494
+
289
495
  ### Two `MailTime` instances usage example
290
496
 
291
- Create two `MailTime` instances with different settings.
497
+ Create two `MailTime` instances with different settings. One for urgent (*e.g. "transactional" emails*), and another one for other types of emails (*e.g. "marketing" emails*)
292
498
 
293
499
  ```js
500
+ import { MailTime, RedisQueue } from 'mail-time';
501
+ import { createClient } from 'redis';
502
+ import { transports } from './transports.js';
503
+ const redisClient = await createClient({ url: 'redis://url' }).connect();
504
+
294
505
  // CREATE mailQueue FOR NON-URGENT EMAILS WHICH IS OKAY TO CONCATENATE
295
506
  const mailQueue = new MailTime({
296
- db: db,
297
- interval: 35,
507
+ queue: new RedisQueue({
508
+ client: redisClient,
509
+ }),
510
+ transports,
298
511
  strategy: 'backup',
299
512
  failsToNext: 1,
300
513
  concatEmails: true,
301
- concatThrottling: 16,
302
- zombieTime: 120000
514
+ josk: {
515
+ adapter: {
516
+ type: 'redis',
517
+ client: redisClient
518
+ },
519
+ zombieTime: 120000
520
+ }
303
521
  });
304
522
 
305
523
  // CREATE mailInstantQueue FOR TRANSACTIONAL EMAILS AND ALERTS
306
524
  const mailInstantQueue = new MailTime({
307
- db: db,
525
+ queue: new RedisQueue({
526
+ client: redisClient,
527
+ prefix: 'instant'
528
+ }),
529
+ transports,
308
530
  prefix: 'instant',
309
- interval: 2,
531
+ retryDelay: 2000,
310
532
  strategy: 'backup',
311
533
  failsToNext: 1,
312
534
  concatEmails: false,
313
- zombieTime: 20000
535
+ josk: {
536
+ adapter: {
537
+ type: 'redis',
538
+ client: redisClient
539
+ },
540
+ zombieTime: 20000
541
+ }
314
542
  });
315
543
 
316
- mailQueue.sendMail({
544
+ await mailQueue.sendMail({
317
545
  to: 'user@gmail.com',
318
546
  subject: 'You\'ve got an email!',
319
547
  text: 'Plain text message',
320
548
  html: '<h1>HTML</h1><p>Styled message</p>'
321
549
  });
322
550
 
323
- mailInstantQueue.sendMail({
551
+ await mailInstantQueue.sendMail({
324
552
  to: 'user@gmail.com',
325
553
  subject: 'Sign in request',
326
554
  text: 'Your OTP login code: xxxx:',
@@ -330,7 +558,7 @@ mailInstantQueue.sendMail({
330
558
 
331
559
  ### Passing variables to the template
332
560
 
333
- All options passed to the `.sendMail()` method is available inside `text`, `html`, and global templates
561
+ All options passed to the `.sendMail()` method are available inside `text`, `html`, and global templates
334
562
 
335
563
  ```js
336
564
  const templates = {
@@ -342,11 +570,11 @@ const templates = {
342
570
  };
343
571
 
344
572
  const mailQueue = new MailTime({
345
- db: db,
573
+ queue: new RedisQueue({ /* ... */ }),
346
574
  template: templates.global
347
575
  });
348
576
 
349
- mailQueue.sendMail({
577
+ await mailQueue.sendMail({
350
578
  to: 'user@gmail.com',
351
579
  subject: 'Sign-in request',
352
580
  username: 'johndoe',
@@ -362,53 +590,170 @@ All available constructor options and `.sendMail()` method API overview
362
590
 
363
591
  ### `new MailTime(opts)` constructor
364
592
 
365
- - `opts` {*Object*} - Configuration object
366
- - `opts.db` {*Db*} - [Required] Mongo's `Db` instance. For example returned in callback of `MongoClient.connect()`
367
- - `opts.type` {*String*} - [Optional] `client` or `server`, default - `server`
368
- - `opts.from` {*Function*} - [Optional] A function which returns *String* of `from` field, format: `"MyApp" <user@example.com>`
369
- - `opts.transports` {*Array*} - [Optional] An array of `nodemailer`'s transports, returned from `nodemailer.createTransport({})`
370
- - `opts.strategy` {*String*} - [Optional] `backup` or `balancer`, default - `backup`. If set to `backup`, first transport will be used unless failed to send `failsToNext` times. If set to `balancer` - transports will be used equally in round robin chain
371
- - `opts.failsToNext` {*Number*} - [Optional] After how many failed "send attempts" switch to next transport, applied only for `backup` strategy, default - `4`
372
- - `opts.prefix` {*String*} - [Optional] Use unique prefixes to create multiple `MailTime` instances on same MongoDB
373
- - `opts.maxTries` {*Number*} - [Optional] How many times resend failed emails, default - `60`
374
- - `opts.interval` {*Number*} - [Optional] Interval in *seconds* between send re-tries, default - `60`
375
- - `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)
376
- - `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`
377
- - `opts.concatEmails` {*Boolean*} - [Optional] Concatenate email by `to` field, default - `false`
378
- - `opts.concatSubject` {*String*} - [Optional] Email subject used in concatenated email, default - `Multiple notifications`
379
- - `opts.concatDelimiter` {*String*} - [Optional] HTML or plain string delimiter used between concatenated email, default - `<hr>`
380
- - `opts.concatThrottling` {*Number*} - [Optional] Time in *seconds* while emails are waiting to be concatenated, default - `60`
381
- - `opts.revolvingInterval` {*Number*} - [Optional] Interval in *milliseconds* in between queue checks, default - `256`. Recommended value — between `opts.minRevolvingDelay` and `opts.maxRevolvingDelay`
382
- - `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)
383
- - `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)
384
- - `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
385
-
386
- ### `sendMail(opts [, callback])`
387
-
388
- - Alias - `send()`
389
- - `opts` {*Object*} - Configuration object
390
- - `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
391
- - `opts.template` - Email specific template, this will override default template passed to `MailTime` constructor
392
- - `opts.concatSubject` - Email specific concatenation subject, this will override default concatenation subject passed to `MailTime` constructor
393
- - `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
394
- - `callback` {*Function*} - Callback called after the email was sent or failed to be sent. __Do not use on multi-server setup__
593
+ - `opts` {*object*} - Configuration object
594
+ - `opts.type` {*string*} - [Optional] `client` or `server`, default - `server`
595
+ - `opts.queue` {*RedisQueue*|*MongoQueue*|*CustomQueue*} - Queue storage driver instance
596
+ - `opts.transports` {*[object]*} - [*Required for "server"*] An array of `nodemailer`'s transports, returned from `nodemailer.createTransport({})`. Required for `{type: 'server'}`
597
+ - `opts.josk` {*object*} - [*Required for "server"*] [`JoSk` package](https://github.com/veliovgroup/josk#api) options
598
+ - `opts.josk.adapter` {*object*|*RedisAdapter*|*MongoAdapter*|*CustomAdapter*} - Config object or *Adapter* instance
599
+ - `opts.josk.adapter.type` {*string*} - One of `mongo` *or* `redis`; Pass `josk.adapter.type` to avoid burden of creating *Adapter* instance manually
600
+ - `opts.josk.adapter.client` {*RedisClient*} - *RedisClient* instance
601
+ - `opts.josk.adapter.db` {*Db*} - Mongo's *Db* instance
602
+ - `opts.josk[option]` {*mix*} - Any other options passed to [`JoSk` instance](https://github.com/veliovgroup/josk#api)
603
+ - `opts.from` {*function*} - [Optional] A function which returns *string* of `from` field, format: `"MyApp" <user@example.com>`
604
+ - `opts.strategy` {*string*} - [Optional] `backup` or `balancer`, default - `backup`. If set to `backup`, first transport will be used unless failed to send `failsToNext` times. If set to `balancer` - transports will be used equally in round robin chain
605
+ - `opts.failsToNext` {*number*} - [Optional] After how many failed "send attempts" switch to the next transport, applied only for `backup` strategy, default - `4`
606
+ - `opts.prefix` {*string*} - [Optional] Use unique prefixes to create multiple `MailTime` instances within the same application
607
+ - `opts.retries` {*number*} - [Optional] How many times resend failed emails, default - `60`
608
+ - `opts.retryDelay` {*number*} - [Optional] Interval in *milliseconds* between send re-tries, default - `60000`
609
+ - `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`
610
+ - `opts.concatEmails` {*boolean*} - [Optional] Concatenate email by `to` field (*e.g. to the same addressee*), default - `false`
611
+ - `opts.concatSubject` {*string*} - [Optional] Email subject used in concatenated email, default - `Multiple notifications`
612
+ - `opts.concatDelimiter` {*string*} - [Optional] HTML or plain string delimiter used between concatenated email, default - `<hr>`
613
+ - `opts.concatDelay` {*number*} - [Optional] Time in *milliseconds* while emails are waiting to be concatenated, default - `60000`
614
+ - `opts.revolvingInterval` {*number*} - [Optional] Interval in *milliseconds* in between queue checks, default - `256`
615
+ - `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
616
+ - `opts.onError(error, email, details)` {*function*} - [Optional] called when email has failed to get sent and exhausted all send attempts (`opts.retries`), called with 3 arguments:
617
+ - `error` {*Error*|*object*} - Error object
618
+ - `email` {*object*} - email's object
619
+ - `details` {*object*} - *not always present*, details from SMTP protocol
620
+ - `opts.onSent(email, details)` {*function*} - [Optional] called when email was successfully handed over to receiving/recipient's SMTP server, called with 2 arguments:
621
+ - `email` {*object*} - email's object
622
+ - `details` {*object*} - *not always present*, details from SMTP server/protocol
623
+
624
+ ```js
625
+ import { MailTime, MongoQueue, RedisQueue } from 'mail-time';
626
+ import nodemailer from 'nodemailer';
627
+ import { createClient } from 'redis';
628
+
629
+ const redisClient = await createClient({ url: 'redis://url' }).connect();
630
+
631
+ const mailQueue = new MailTime({
632
+ type: 'server',
633
+ strategy: 'backup',
634
+ prefix: 'appMailQueue',
635
+ transports: [nodemailer.createTransport({/* ... */})],
636
+ failsToNext: 4,
637
+ retries: 60,
638
+ retryDelay: 60000,
639
+ keepHistory: false,
640
+ concatEmails: false,
641
+ concatDelay: 60000,
642
+ concatDelimiter: '<hr>',
643
+ concatSubject: 'Multiple notifications',
644
+ revolvingInterval: 256,
645
+ template: '{{{html}}}',
646
+ queue: new RedisQueue({
647
+ client: redisClient,
648
+ prefix: 'appMailQueue',
649
+ }),
650
+ josk: {
651
+ adapter: {
652
+ type: 'redis',
653
+ client: redisClient,
654
+ }
655
+ },
656
+ from(transport) {
657
+ // To pass spam-filters `from` field should be correctly set
658
+ // for each transport, check `transport` object for more options
659
+ return `"App Name" <${transport.options.from}>`;
660
+ },
661
+ onError(error, email, details) {
662
+ console.log(`Email "${email.mailOptions.subject}" wasn't sent to ${email.mailOptions.to}`, error, details);
663
+ },
664
+ onSent(email, details) {
665
+ console.log(`Email "${email.mailOptions.subject}" successfully sent to ${email.mailOptions.to}`, details);
666
+ },
667
+ });
668
+
669
+ await mailQueue.sendMail({
670
+ to: 'johndoe@example.com',
671
+ subject: 'Email subject',
672
+ text: 'You have got email!',
673
+ html: '<p>You have got email!</p>',
674
+ });
675
+ ```
676
+
677
+ ### `new RedisQueue(opts)` constructor
678
+
679
+ *Create Redis Queue instance.* Use for `opts.queue` when creating *MailTime* instance
680
+
681
+ - `opts` {*object*} - Configuration object
682
+ - `opts.client` {*RedisClient*} - Required, Redis'es `RedisClient` instance, like one returned from `await redis.createClient().connect()` method
683
+ - `opts.prefix` {*string*} - Optional prefix for scope isolation; use when creating multiple `MailTime` instances within the single application
684
+
685
+ ```js
686
+ import { MailTime, RedisQueue } from 'mail-time';
687
+ import { createClient } from 'redis';
688
+
689
+ new RedisQueue({
690
+ client: await createClient({ url: 'redis://url' }).connect(),
691
+ prefix: 'appMailQueue',
692
+ });
693
+ ```
694
+
695
+ ### `new MongoQueue(opts)` constructor
696
+
697
+ *Create MongoDB Queue instance.* Use for `opts.queue` when creating *MailTime* instance
698
+
699
+ - `opts` {*object*} - Configuration object
700
+ - `opts.db` {*Db*} - Required, Mongo's `Db` instance, like one returned from `MongoClient#db()`
701
+ - `opts.prefix` {*string*} - Optional prefix for scope isolation; use when creating multiple `MailTime` instances within the single application
702
+
703
+ ```js
704
+ import { MailTime, MongoQueue } from 'mail-time';
705
+ import { MongoClient } from 'mongodb';
706
+
707
+ new MongoQueue({
708
+ db: (await MongoClient.connect('mongodb://url')).db('database'),
709
+ prefix: 'appMailQueue',
710
+ });
711
+ ```
712
+
713
+ ### `sendMail(opts)`
714
+
715
+ *Add email to the queue.* Returns `Promise<string>` unique email's `uuid`
716
+
717
+ - `opts` {*object*} - Configuration object
718
+ - `opts.sendAt` {*number*} - When email should be sent, default - `Date.now()`
719
+ - `opts.template` {*string*} - Email specific template, this will override default template passed to `MailTime` constructor
720
+ - `opts.concatSubject` {*string*} - Email specific concatenation subject, this will override default concatenation subject passed to `MailTime` constructor
721
+ - `opts[key]` {*mix*} - Other custom and NodeMailer specific options, like `text`, `html` and `to`, [learn more here](https://nodemailer.com/message/). __Note:__ if [`attachments`](https://nodemailer.com/message/attachments/) are used via `path` — file must exists on all micro-services servers
722
+
723
+ ### `cancelMail(uuid)`
724
+
725
+ *Remove email from queue.* Returns `Promise<boolean>` — `true` if cancelled or `false` if not found, was sent, or was cancelled previously. Throws *Error*
726
+
727
+ - `uuid` {*string*|*promise*} — email's `uuid` returned from `.sendEmail()` method
728
+
729
+ ```js
730
+ import { mailQueue } from './mail-queue.js';
731
+
732
+ const uuid = await mailQueue.sendMail({
733
+ to: 'johndoe@example.com',
734
+ subject: 'Email subject',
735
+ text: 'You have got email!',
736
+ html: '<p>You have got email!</p>',
737
+ });
738
+
739
+ await mailQueue.cancelMail(uuid);
740
+ ```
395
741
 
396
742
  ### `static MailTime.Template`
397
743
 
398
744
  Simple and bulletproof HTML template, see [its source](https://github.com/veliovgroup/mail-time/blob/master/template.html). Usage example:
399
745
 
400
746
  ```js
401
- const MailTime = require('mail-time');
747
+ import { MailTime, MongoQueue, RedisQueue } from 'mail-time';
748
+
402
749
  // Make it default
403
750
  const mailQueue = new MailTime({
404
- db: db, // MongoDB
405
751
  /* .. */
406
752
  template: MailTime.Template
407
753
  });
408
754
 
409
755
  // For single letter
410
756
  mailQueue.sendMail({
411
- to: 'user@gmail.com',
412
757
  /* .. */
413
758
  template: MailTime.Template
414
759
  });
@@ -432,20 +777,25 @@ mailQueue.sendMail({
432
777
  ## Testing
433
778
 
434
779
  1. Clone this package
435
- 2. In Terminal (*Console*) go to directory where package is cloned
436
- 3. Then run:
437
-
438
- Test NPM package:
780
+ 2. Start local or obtain URLs for remote MongoDB and Redis servers
781
+ 3. In Terminal (*Console*) go to directory where package was cloned
782
+ 4. Then run:
439
783
 
440
784
  ```shell
441
- # Before run tests make sure NODE_ENV === development
785
+ # Before running tests make sure NODE_ENV === development
442
786
  # Install NPM dependencies
443
787
  npm install --save-dev
444
788
 
445
- # Before run tests you need to have running MongoDB
446
- DEBUG="true" EMAIL_DOMAIN="example.com" MONGO_URL="mongodb://127.0.0.1:27017/npm-mail-time-test-001" npm test
789
+ # DEFAULT RUN
790
+ REDIS_URL="redis://127.0.0.1:6379" MONGO_URL="mongodb://127.0.0.1:27017/npm-mail-time-test-001" npm test
791
+
792
+ # OPTIONALLY RUN WITH CUSTOM DOMAIN
793
+ EMAIL_DOMAIN="your-domain.com" REDIS_URL="redis://127.0.0.1:6379" MONGO_URL="mongodb://127.0.0.1:27017/npm-mail-time-test-001" npm test
794
+
795
+ # IF SOME TESTS ARE FAILING: ENABLE DEBUG
796
+ DEBUG="true" REDIS_URL="redis://127.0.0.1:6379" MONGO_URL="mongodb://127.0.0.1:27017/npm-mail-time-test-001" npm test
447
797
 
448
- # Be patient, tests are taking around 2 mins
798
+ # Be patient, tests are taking around 8 mins
449
799
  ```
450
800
 
451
801
  ## Support this project: