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 +468 -118
- package/adapters/blank-example.js +230 -0
- package/adapters/mongo.js +310 -0
- package/adapters/redis.js +302 -0
- package/helpers.js +11 -0
- package/index.cjs +968 -341
- package/index.js +407 -392
- package/package.json +8 -7
package/README.md
CHANGED
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
|
|
6
6
|
# MailTime
|
|
7
7
|
|
|
8
|
-
"Mail-Time" is
|
|
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
|
|
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
|
|
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
|
-
- [
|
|
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
|
-
- [
|
|
45
|
+
- [Running tests](https://github.com/veliovgroup/mail-time?tab=readme-ov-file#testing)
|
|
30
46
|
|
|
31
47
|
## Main features:
|
|
32
48
|
|
|
33
|
-
- 👨🔬 ~
|
|
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
|
|
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"
|
|
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
|
-
-
|
|
135
|
-
-
|
|
136
|
-
-
|
|
137
|
-
-
|
|
138
|
-
-
|
|
139
|
-
-
|
|
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 —
|
|
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
|
-
|
|
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
|
-
//
|
|
172
|
-
const MailTime = require('mail-time');
|
|
200
|
+
// require as CommonJS
|
|
201
|
+
const { MailTime, MongoQueue, RedisQueue } = require('mail-time');
|
|
173
202
|
```
|
|
174
203
|
|
|
175
|
-
Create
|
|
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
|
-
|
|
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
|
-
|
|
232
|
-
|
|
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
|
-
|
|
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
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
db,
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
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
|
-
|
|
347
|
+
export { mailQueue };
|
|
348
|
+
```
|
|
259
349
|
|
|
260
|
-
|
|
261
|
-
const MailTime = require('mail-time');
|
|
262
|
-
const MongoClient = require('mongodb').MongoClient;
|
|
350
|
+
#### 3c. Optionally create *Client* type of *MailTime*
|
|
263
351
|
|
|
264
|
-
|
|
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
|
-
|
|
267
|
-
|
|
354
|
+
```js
|
|
355
|
+
// mail-queue.js
|
|
356
|
+
import { MailTime, RedisQueue } from 'mail-time';
|
|
357
|
+
import { createClient } from 'redis';
|
|
268
358
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
297
|
-
|
|
507
|
+
queue: new RedisQueue({
|
|
508
|
+
client: redisClient,
|
|
509
|
+
}),
|
|
510
|
+
transports,
|
|
298
511
|
strategy: 'backup',
|
|
299
512
|
failsToNext: 1,
|
|
300
513
|
concatEmails: true,
|
|
301
|
-
|
|
302
|
-
|
|
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
|
-
|
|
525
|
+
queue: new RedisQueue({
|
|
526
|
+
client: redisClient,
|
|
527
|
+
prefix: 'instant'
|
|
528
|
+
}),
|
|
529
|
+
transports,
|
|
308
530
|
prefix: 'instant',
|
|
309
|
-
|
|
531
|
+
retryDelay: 2000,
|
|
310
532
|
strategy: 'backup',
|
|
311
533
|
failsToNext: 1,
|
|
312
534
|
concatEmails: false,
|
|
313
|
-
|
|
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
|
|
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
|
-
|
|
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` {*
|
|
366
|
-
- `opts.
|
|
367
|
-
- `opts.
|
|
368
|
-
- `opts.
|
|
369
|
-
- `opts.
|
|
370
|
-
- `opts.
|
|
371
|
-
- `opts.
|
|
372
|
-
- `opts.
|
|
373
|
-
- `opts.
|
|
374
|
-
- `opts.
|
|
375
|
-
- `opts.
|
|
376
|
-
- `opts.
|
|
377
|
-
- `opts.
|
|
378
|
-
- `opts.
|
|
379
|
-
- `opts.
|
|
380
|
-
- `opts.
|
|
381
|
-
- `opts.
|
|
382
|
-
- `opts.
|
|
383
|
-
- `opts.
|
|
384
|
-
- `opts.
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
-
|
|
389
|
-
- `
|
|
390
|
-
- `
|
|
391
|
-
- `
|
|
392
|
-
- `opts.
|
|
393
|
-
- `
|
|
394
|
-
- `
|
|
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
|
-
|
|
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.
|
|
436
|
-
3.
|
|
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
|
|
785
|
+
# Before running tests make sure NODE_ENV === development
|
|
442
786
|
# Install NPM dependencies
|
|
443
787
|
npm install --save-dev
|
|
444
788
|
|
|
445
|
-
#
|
|
446
|
-
|
|
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
|
|
798
|
+
# Be patient, tests are taking around 8 mins
|
|
449
799
|
```
|
|
450
800
|
|
|
451
801
|
## Support this project:
|