mail-time 1.2.5 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +118 -102
- package/index.js +144 -160
- package/package.json +13 -13
- package/CHANGELOG.md +0 -1
- package/HISTORY.md +0 -1
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
[](https://github.com/sponsors/dr-dimitru)
|
|
1
2
|
[](https://paypal.me/veliovgroup)
|
|
2
3
|
<a href="https://ostr.io/info/built-by-developers-for-developers">
|
|
3
4
|
<img src="https://ostr.io/apple-touch-icon-60x60.png" height="20">
|
|
@@ -13,23 +14,28 @@ Every `MailTime` instance can be configured to be a *Server* or *Client*.
|
|
|
13
14
|
Main difference of *Server* from *Client* - *Server* handles queue and actually sends email.
|
|
14
15
|
While *Client* is only put emails into the queue.
|
|
15
16
|
|
|
17
|
+
"Mail-Time" is a micro-service package for mail queue, with *Server* and *Client* APIs. Build on top of the [`nodemailer`](https://github.com/nodemailer/nodemailer) package.
|
|
18
|
+
|
|
19
|
+
Every `MailTime` instance can get configured as *Server* or *Client*.
|
|
20
|
+
|
|
21
|
+
The main difference between *Server* and *Client* modes is that the *Server* handles the queue and __sends__ email. While the *Client* only __adds__ emails into the queue.
|
|
22
|
+
|
|
16
23
|
## ToC
|
|
17
24
|
|
|
18
|
-
- [How it works?](https://github.com/
|
|
19
|
-
- [With single SMTP](https://github.com/
|
|
20
|
-
- [With multiple SMTP](https://github.com/
|
|
21
|
-
- [As Micro-Service](https://github.com/
|
|
22
|
-
- [Features](https://github.com/
|
|
23
|
-
- [Installation](https://github.com/
|
|
24
|
-
- [Meteor.js
|
|
25
|
-
- [
|
|
26
|
-
- [
|
|
27
|
-
- [
|
|
28
|
-
- [
|
|
29
|
-
- [
|
|
30
|
-
|
|
31
|
-
- [
|
|
32
|
-
- [~92% tests coverage](https://github.com/VeliovGroup/Mail-Time#testing)
|
|
25
|
+
- [How it works?](https://github.com/veliovgroup/mail-time#how-it-works)
|
|
26
|
+
- [With single SMTP](https://github.com/veliovgroup/mail-time#single-point-of-failure)
|
|
27
|
+
- [With multiple SMTP](https://github.com/veliovgroup/mail-time#multiple-smtp-providers)
|
|
28
|
+
- [As Micro-Service](https://github.com/veliovgroup/mail-time#cluster-issue)
|
|
29
|
+
- [Features](https://github.com/veliovgroup/mail-time#features)
|
|
30
|
+
- [Installation](https://github.com/veliovgroup/mail-time#installation)
|
|
31
|
+
- [Meteor.js usage](https://github.com/veliovgroup/mail-time/blob/master/docs/meteor.md)
|
|
32
|
+
- [Usage example](https://github.com/veliovgroup/mail-time#basic-usage)
|
|
33
|
+
- [API](https://github.com/veliovgroup/mail-time#api)
|
|
34
|
+
- [*Constructor*](https://github.com/veliovgroup/mail-time#new-mailtimeopts-constructor)
|
|
35
|
+
- [`.send()`](https://github.com/veliovgroup/mail-time#sendmailopts--callback)
|
|
36
|
+
- [Default Template](https://github.com/veliovgroup/mail-time#static-mailtimetemplate)
|
|
37
|
+
- [Custom Templates](https://github.com/veliovgroup/mail-time#template-example)
|
|
38
|
+
- [~92% tests coverage](https://github.com/veliovgroup/mail-time#testing)
|
|
33
39
|
|
|
34
40
|
## Main features:
|
|
35
41
|
|
|
@@ -38,11 +44,13 @@ While *Client* is only put emails into the queue.
|
|
|
38
44
|
- 🏢 Synchronize email queue across multiple servers;
|
|
39
45
|
- 💪 Bulletproof design, built-in retries.
|
|
40
46
|
|
|
41
|
-
## How does it work
|
|
47
|
+
## How does it work?
|
|
48
|
+
|
|
49
|
+
Redundant solution for email transmission.
|
|
42
50
|
|
|
43
51
|
### Single point of failure
|
|
44
52
|
|
|
45
|
-
Issue - classic solution with
|
|
53
|
+
Issue - classic solution with a single point of failure:
|
|
46
54
|
|
|
47
55
|
```ascii
|
|
48
56
|
|----------------| |------| |------------------|
|
|
@@ -90,11 +98,11 @@ Backup scheme with multiple SMTP providers
|
|
|
90
98
|
|
|
91
99
|
### Cluster issue
|
|
92
100
|
|
|
93
|
-
|
|
101
|
+
It is common to create a "Cluster" of servers to balance the load and add a durability layer for horizontal scaling of quickly growing applications.
|
|
94
102
|
|
|
95
|
-
|
|
103
|
+
Most modern application has scheduled or recurring emails. For example, once a day — with recent news and updates. It won't be an issue with a single server setup — the server would send emails at a daily interval via timer or CRON. While in "Cluster" implementation — each server will attempt to send the same email. In such cases, users will receive multiple emails with the same content. We built MailTime to address this and other similar issues.
|
|
96
104
|
|
|
97
|
-
Here is how
|
|
105
|
+
Here is how this issue is solved by using MailTime:
|
|
98
106
|
|
|
99
107
|
```ascii
|
|
100
108
|
|===================THE=CLUSTER===================| |=QUEUE=| |===Mail=Time===|
|
|
@@ -114,17 +122,17 @@ Here is how we solve this issue:
|
|
|
114
122
|
|
|
115
123
|
## Features
|
|
116
124
|
|
|
117
|
-
- Queue - Managed via MongoDB,
|
|
125
|
+
- Queue - Managed via MongoDB, will survive server reboots and failures
|
|
118
126
|
- Support for multiple server setups - "Cluster", Phusion Passenger instances, Load Balanced solutions, etc.
|
|
119
|
-
- Emails concatenation by addressee email - Reduce amount of sent
|
|
120
|
-
- When concatenation is enabled - Same emails
|
|
121
|
-
- Balancing for multiple nodemailer's transports, two modes - `backup` and `balancing`.
|
|
127
|
+
- Emails concatenation by addressee email - Reduce amount of sent emails to a single user with concatenation, and avoid mistakenly doubled emails
|
|
128
|
+
- When concatenation is enabled - Same emails won't be sent twice, if for any reason, due to bad logic or application failure emails are sent twice or more times - this is solution to solve this annoying behavior
|
|
129
|
+
- Balancing for multiple nodemailer's transports, two modes - `backup` and `balancing`. This is the most useful feature — allowing to reduce the cost of SMTP services and add extra layer of durability. If one transport failing to send an email `mail-time` will switch to the next one
|
|
122
130
|
- Sending retries for network and other failures
|
|
123
|
-
-
|
|
131
|
+
- Templating support with [Mustache](https://mustache.github.io/)-like placeholders
|
|
124
132
|
|
|
125
133
|
## Installation
|
|
126
134
|
|
|
127
|
-
|
|
135
|
+
To implement Server functionality — as a first step install `nodemailer`, although this package meant to be used with `nodemailer`, it's not added as the dependency, as `nodemailer` not needed by Client, and you're free to choose `nodemailer`'s version to fit your project needs:
|
|
128
136
|
|
|
129
137
|
```shell
|
|
130
138
|
npm install --save nodemailer
|
|
@@ -148,14 +156,14 @@ Require package:
|
|
|
148
156
|
const MailTime = require('mail-time');
|
|
149
157
|
```
|
|
150
158
|
|
|
151
|
-
Create nodemailer's transports (see [nodemailer docs](https://github.com/nodemailer/nodemailer/tree/v2#setting-up)):
|
|
159
|
+
Create nodemailer's transports (see [`nodemailer` docs](https://github.com/nodemailer/nodemailer/tree/v2#setting-up)):
|
|
152
160
|
|
|
153
161
|
```js
|
|
154
162
|
const transports = [];
|
|
155
163
|
const nodemailer = require('nodemailer');
|
|
156
164
|
|
|
157
165
|
// Use DIRECT transport
|
|
158
|
-
//
|
|
166
|
+
// and enable sending email from localhost
|
|
159
167
|
// install "nodemailer-direct-transport" NPM package:
|
|
160
168
|
const directTransport = require('nodemailer-direct-transport');
|
|
161
169
|
const directTransportOpts = {
|
|
@@ -165,8 +173,8 @@ const directTransportOpts = {
|
|
|
165
173
|
from: 'no-reply@example.com',
|
|
166
174
|
};
|
|
167
175
|
transports.push(nodemailer.createTransport(directTransport(directTransportOpts)));
|
|
168
|
-
// IMPORTANT:
|
|
169
|
-
//
|
|
176
|
+
// IMPORTANT: Add `.options` to a newly created transport,
|
|
177
|
+
// this is necessary to make sure options are available to MailTime package:
|
|
170
178
|
transports[0].options = directTransportOpts;
|
|
171
179
|
|
|
172
180
|
// Private SMTP
|
|
@@ -201,16 +209,15 @@ transports.push(nodemailer.createTransport({
|
|
|
201
209
|
}));
|
|
202
210
|
```
|
|
203
211
|
|
|
204
|
-
|
|
205
|
-
We will need connect to MongoDB first:
|
|
212
|
+
As the next step initiate `mail-time` in the *Server* mode, it will be able to __send__ and __add__ emails to the queue. Connecting to a MongoDB before initiating `new MailTime` instance:
|
|
206
213
|
|
|
207
214
|
```js
|
|
215
|
+
const MailTime = require('mail-time');
|
|
208
216
|
const MongoClient = require('mongodb').MongoClient;
|
|
209
|
-
const MailTime = require('mail-time');
|
|
210
|
-
const dbName = 'DatabaseName';
|
|
211
217
|
|
|
212
|
-
|
|
213
|
-
|
|
218
|
+
const dbName = 'databaseName';
|
|
219
|
+
|
|
220
|
+
// Use MONGO_URL environment variable to store connection string to MongoDB
|
|
214
221
|
// example: "MONGO_URL=mongodb://127.0.0.1:27017/myapp node mail-micro-service.js"
|
|
215
222
|
MongoClient.connect(process.env.MONGO_URL, (error, client) => {
|
|
216
223
|
const db = client.db(dbName);
|
|
@@ -223,7 +230,7 @@ MongoClient.connect(process.env.MONGO_URL, (error, client) => {
|
|
|
223
230
|
from(transport) {
|
|
224
231
|
// To pass spam-filters `from` field should be correctly set
|
|
225
232
|
// for each transport, check `transport` object for more options
|
|
226
|
-
return
|
|
233
|
+
return `"Awesome App" <${transport.options.from}>`;
|
|
227
234
|
},
|
|
228
235
|
concatEmails: true, // Concatenate emails to the same addressee
|
|
229
236
|
concatDelimiter: '<h1>{{{subject}}}</h1>', // Start each concatenated email with it's own subject
|
|
@@ -232,12 +239,13 @@ MongoClient.connect(process.env.MONGO_URL, (error, client) => {
|
|
|
232
239
|
});
|
|
233
240
|
```
|
|
234
241
|
|
|
235
|
-
|
|
242
|
+
Only __one__ `MailTime` *Server* instance required to send email. In the other parts of an app (like UI units or in sub-apps) use `mail-time` in the *Client* mode to __add__ emails to queue:
|
|
236
243
|
|
|
237
244
|
```js
|
|
245
|
+
const MailTime = require('mail-time');
|
|
238
246
|
const MongoClient = require('mongodb').MongoClient;
|
|
239
|
-
|
|
240
|
-
const dbName
|
|
247
|
+
|
|
248
|
+
const dbName = 'databaseName';
|
|
241
249
|
|
|
242
250
|
MongoClient.connect(process.env.MONGO_URL, (error, client) => {
|
|
243
251
|
const db = client.db(dbName);
|
|
@@ -251,7 +259,7 @@ MongoClient.connect(process.env.MONGO_URL, (error, client) => {
|
|
|
251
259
|
});
|
|
252
260
|
```
|
|
253
261
|
|
|
254
|
-
Send email:
|
|
262
|
+
Send email example:
|
|
255
263
|
|
|
256
264
|
```js
|
|
257
265
|
mailQueue.sendMail({
|
|
@@ -262,79 +270,80 @@ mailQueue.sendMail({
|
|
|
262
270
|
});
|
|
263
271
|
```
|
|
264
272
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
### Meteor.js Installation:
|
|
268
|
-
|
|
269
|
-
#### Installation & Import (*via NPM*):
|
|
270
|
-
|
|
271
|
-
Install NPM *MailTime* package:
|
|
272
|
-
|
|
273
|
-
```shell
|
|
274
|
-
meteor npm install --save mail-time
|
|
275
|
-
```
|
|
273
|
+
### Two `MailTime` instances usage example
|
|
276
274
|
|
|
277
|
-
|
|
275
|
+
Create two `MailTime` instances with different settings.
|
|
278
276
|
|
|
279
277
|
```js
|
|
280
|
-
|
|
281
|
-
|
|
278
|
+
// CREATE mailQueue FOR NON-URGENT EMAILS WHICH IS OKAY TO CONCATENATE
|
|
279
|
+
const mailQueue = new MailTime({
|
|
280
|
+
db: db,
|
|
281
|
+
interval: 35,
|
|
282
|
+
strategy: 'backup',
|
|
283
|
+
failsToNext: 1,
|
|
284
|
+
concatEmails: true,
|
|
285
|
+
concatThrottling: 16,
|
|
286
|
+
zombieTime: 120000
|
|
287
|
+
});
|
|
282
288
|
|
|
283
|
-
|
|
289
|
+
// CREATE mailInstantQueue FOR TRANSACTIONAL EMAILS AND ALERTS
|
|
290
|
+
const mailInstantQueue = new MailTime({
|
|
291
|
+
db: db,
|
|
292
|
+
prefix: 'instant',
|
|
293
|
+
interval: 2,
|
|
294
|
+
strategy: 'backup',
|
|
295
|
+
failsToNext: 1,
|
|
296
|
+
concatEmails: false,
|
|
297
|
+
zombieTime: 20000
|
|
298
|
+
});
|
|
284
299
|
|
|
285
|
-
|
|
300
|
+
mailQueue.sendMail({
|
|
301
|
+
to: 'user@gmail.com',
|
|
302
|
+
subject: 'You\'ve got an email!',
|
|
303
|
+
text: 'Plain text message',
|
|
304
|
+
html: '<h1>HTML</h1><p>Styled message</p>'
|
|
305
|
+
});
|
|
286
306
|
|
|
287
|
-
|
|
288
|
-
|
|
307
|
+
mailInstantQueue.sendMail({
|
|
308
|
+
to: 'user@gmail.com',
|
|
309
|
+
subject: 'Sign in request',
|
|
310
|
+
text: 'Your OTP login code: xxxx:',
|
|
311
|
+
html: '<h1>Code:</h1><code>XXXX</code>'
|
|
312
|
+
});
|
|
289
313
|
```
|
|
290
314
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
```js
|
|
294
|
-
import MailTime from 'meteor/ostrio:mailer';
|
|
295
|
-
```
|
|
315
|
+
### Passing variables to the template
|
|
296
316
|
|
|
297
|
-
|
|
317
|
+
All options passed to the `.sendMail()` method is available inside `text`, `html`, and global templates
|
|
298
318
|
|
|
299
319
|
```js
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
// install "nodemailer-direct-transport" NPM package:
|
|
307
|
-
import directTransport from 'nodemailer-direct-transport';
|
|
308
|
-
|
|
309
|
-
const transports = [];
|
|
310
|
-
const directTransportOpts = {
|
|
311
|
-
pool: false,
|
|
312
|
-
direct: true,
|
|
313
|
-
name: 'mail.example.com',
|
|
314
|
-
from: 'no-reply@example.com',
|
|
320
|
+
const templates = {
|
|
321
|
+
global: '<html xmlns="http://www.w3.org/1999/xhtml"><head><title>{{subject}}</title></head><body>{{{html}}}<footer>Message sent to @{{username}} user ({{to}})</footer></body></html>',
|
|
322
|
+
signInCode: {
|
|
323
|
+
text: 'Hello @{{username}}! Here\'s your login code: {{code}}',
|
|
324
|
+
html: `<h1>Sign-in request</h1><p>Hello @{{username}}! <p>Copy your login code below:</p> <pre><code>{{code}}</code></pre>`
|
|
325
|
+
}
|
|
315
326
|
};
|
|
316
|
-
transports.push(nodemailer.createTransport(directTransport(directTransportOpts)));
|
|
317
|
-
// IMPORTANT: Copy-paste passed options from directTransport() to
|
|
318
|
-
// transport's "options" property, to make sure it's available to MailTime package:
|
|
319
|
-
transports[0].options = directTransportOpts;
|
|
320
|
-
|
|
321
|
-
////////////////////////
|
|
322
|
-
// For more transports example see sections above and read nodemailer's docs
|
|
323
|
-
////////////////////////
|
|
324
327
|
|
|
325
328
|
const mailQueue = new MailTime({
|
|
326
|
-
db:
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
329
|
+
db: db,
|
|
330
|
+
template: templates.global
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
mailQueue.sendMail({
|
|
334
|
+
to: 'user@gmail.com',
|
|
335
|
+
subject: 'Sign-in request',
|
|
336
|
+
username: 'johndoe',
|
|
337
|
+
code: 'XXXXX-YY',
|
|
338
|
+
text: templates.signInCode.text,
|
|
339
|
+
html: templates.signInCode.html
|
|
333
340
|
});
|
|
334
341
|
```
|
|
335
342
|
|
|
336
343
|
## API
|
|
337
344
|
|
|
345
|
+
All available constructor options and `.sendMail()` method API overview
|
|
346
|
+
|
|
338
347
|
### `new MailTime(opts)` constructor
|
|
339
348
|
|
|
340
349
|
- `opts` {*Object*} - Configuration object
|
|
@@ -347,15 +356,15 @@ const mailQueue = new MailTime({
|
|
|
347
356
|
- `opts.prefix` {*String*} - [Optional] Use unique prefixes to create multiple `MailTime` instances on same MongoDB
|
|
348
357
|
- `opts.maxTries` {*Number*} - [Optional] How many times resend failed emails, default - `60`
|
|
349
358
|
- `opts.interval` {*Number*} - [Optional] Interval in *seconds* between send re-tries, default - `60`
|
|
350
|
-
- `opts.zombieTime` {*Number*} - [Optional] Time in *milliseconds*, after this period - pending email will be interpreted as "*zombie*". This parameter allows to rescue pending email from "*zombie* mode" in case when: server was rebooted, exception during runtime was thrown, or caused by bad logic, default - `32786`. This option is used by package itself and passed directly to [`JoSk` package](https://github.com/
|
|
359
|
+
- `opts.zombieTime` {*Number*} - [Optional] Time in *milliseconds*, after this period - pending email will be interpreted as "*zombie*". This parameter allows to rescue pending email from "*zombie* mode" in case when: server was rebooted, exception during runtime was thrown, or caused by bad logic, default - `32786`. This option is used by package itself and passed directly to [`JoSk` package](https://github.com/veliovgroup/josk#api)
|
|
351
360
|
- `opts.keepHistory` {*Boolean*} - [Optional] By default sent emails not stored in the database. Set `{ keepHistory: true }` to keep queue task as it is in the database, default - `false`
|
|
352
361
|
- `opts.concatEmails` {*Boolean*} - [Optional] Concatenate email by `to` field, default - `false`
|
|
353
362
|
- `opts.concatSubject` {*String*} - [Optional] Email subject used in concatenated email, default - `Multiple notifications`
|
|
354
363
|
- `opts.concatDelimiter` {*String*} - [Optional] HTML or plain string delimiter used between concatenated email, default - `<hr>`
|
|
355
364
|
- `opts.concatThrottling` {*Number*} - [Optional] Time in *seconds* while emails are waiting to be concatenated, default - `60`
|
|
356
365
|
- `opts.revolvingInterval` {*Number*} - [Optional] Interval in *milliseconds* in between queue checks, default - `256`. Recommended value — between `opts.minRevolvingDelay` and `opts.maxRevolvingDelay`
|
|
357
|
-
- `opts.minRevolvingDelay` {*Number*} - [Optional] Minimum revolving delay — the minimum delay between tasks executions in *milliseconds*, default - `64`. This option is passed directly to [`JoSk` package](https://github.com/
|
|
358
|
-
- `opts.maxRevolvingDelay` {*Number*} - [Optional] Maximum revolving delay — the maximum delay between tasks executions in *milliseconds*, default - `256`. This option is passed directly to [`JoSk` package](https://github.com/
|
|
366
|
+
- `opts.minRevolvingDelay` {*Number*} - [Optional] Minimum revolving delay — the minimum delay between tasks executions in *milliseconds*, default - `64`. This option is passed directly to [`JoSk` package](https://github.com/veliovgroup/josk#api)
|
|
367
|
+
- `opts.maxRevolvingDelay` {*Number*} - [Optional] Maximum revolving delay — the maximum delay between tasks executions in *milliseconds*, default - `256`. This option is passed directly to [`JoSk` package](https://github.com/veliovgroup/josk#api)
|
|
359
368
|
- `opts.template` {*String*} - [Optional] Mustache-like template, default - `{{{html}}}`, all options passed to `sendMail` is available in Template, like `to`, `subject`, `text`, `html` or any other custom option. Use `{{opt}}` for string placeholders and `{{{opt}}}` for html placeholders
|
|
360
369
|
|
|
361
370
|
### `sendMail(opts [, callback])`
|
|
@@ -365,15 +374,15 @@ const mailQueue = new MailTime({
|
|
|
365
374
|
- `opts.sendAt` {*Date*} - When email should be sent, default - `new Date()` use with caution on multi-server setup at different location with the different time-zones
|
|
366
375
|
- `opts.template` - Email specific template, this will override default template passed to `MailTime` constructor
|
|
367
376
|
- `opts.concatSubject` - Email specific concatenation subject, this will override default concatenation subject passed to `MailTime` constructor
|
|
368
|
-
- `opts[key]` {*Mix*} - Other custom and NodeMailer specific options, like `text`, `html` and `to`,
|
|
377
|
+
- `opts[key]` {*Mix*} - Other custom and NodeMailer specific options, like `text`, `html` and `to`, [learn more here](https://github.com/nodemailer/nodemailer/tree/v2#e-mail-message-fields). Note `attachments` should work only via `path`, and file must exists on all micro-services servers
|
|
369
378
|
- `callback` {*Function*} - Callback called after the email was sent or failed to be sent. __Do not use on multi-server setup__
|
|
370
379
|
|
|
371
380
|
### `static MailTime.Template`
|
|
372
381
|
|
|
373
|
-
Simple and bulletproof HTML template, see its
|
|
382
|
+
Simple and bulletproof HTML template, see [its source](https://github.com/veliovgroup/mail-time/blob/master/template.html). Usage example:
|
|
374
383
|
|
|
375
384
|
```js
|
|
376
|
-
const MailTime
|
|
385
|
+
const MailTime = require('mail-time');
|
|
377
386
|
// Make it default
|
|
378
387
|
const mailQueue = new MailTime({
|
|
379
388
|
db: db, // MongoDB
|
|
@@ -391,6 +400,8 @@ mailQueue.sendMail({
|
|
|
391
400
|
|
|
392
401
|
### Template Example
|
|
393
402
|
|
|
403
|
+
Pass custom template via `template` property to `.sendMail()` method
|
|
404
|
+
|
|
394
405
|
```js
|
|
395
406
|
mailQueue.sendMail({
|
|
396
407
|
to: 'user@gmail.com',
|
|
@@ -438,5 +449,10 @@ DEBUG="true" EMAIL_DOMAIN="example.com" MONGO_URL="mongodb://127.0.0.1:27017/met
|
|
|
438
449
|
|
|
439
450
|
## Support this project:
|
|
440
451
|
|
|
441
|
-
-
|
|
452
|
+
- Star on [GitHub](https://github.com/veliovgroup/mail-time)
|
|
453
|
+
- Star on [NPM](https://www.npmjs.com/package/mail-time)
|
|
454
|
+
- Star on [Atmosphere](https://atmospherejs.com/ostrio/mailer)
|
|
455
|
+
- [Sponsor maintainer via GitHub](https://github.com/sponsors/dr-dimitru) — support open source with one-time contribution or on a regular basis
|
|
456
|
+
- [Sponsor veliovgroup via GitHub](https://github.com/sponsors/veliovgroup) — support company behind this package
|
|
457
|
+
- [Support via PayPal](https://paypal.me/veliovgroup) — support our open source contributions
|
|
442
458
|
- Use [ostr.io](https://ostr.io) — [Monitoring](https://snmp-monitoring.com), [Analytics](https://ostr.io/info/web-analytics), [WebSec](https://domain-protection.info), [Web-CRON](https://web-cron.info) and [Pre-rendering](https://prerendering.com) for a website
|
package/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
const JoSk
|
|
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);
|
|
@@ -17,12 +17,6 @@ const mongoErrorHandler = (error) => {
|
|
|
17
17
|
}
|
|
18
18
|
};
|
|
19
19
|
|
|
20
|
-
const defaultWriteConcern = {
|
|
21
|
-
w: 1,
|
|
22
|
-
j: true,
|
|
23
|
-
wtimeout: 10240
|
|
24
|
-
};
|
|
25
|
-
|
|
26
20
|
let equals;
|
|
27
21
|
equals = (a, b) => {
|
|
28
22
|
let i;
|
|
@@ -108,13 +102,13 @@ module.exports = class MailTime {
|
|
|
108
102
|
throw new Error('[mail-time] MongoDB database {db} option is required, like returned from `MongoClient.connect`');
|
|
109
103
|
}
|
|
110
104
|
|
|
111
|
-
this.callbacks
|
|
112
|
-
this.type
|
|
113
|
-
this.debug
|
|
114
|
-
this.prefix
|
|
115
|
-
this.maxTries
|
|
116
|
-
this.interval
|
|
117
|
-
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}}}';
|
|
118
112
|
this.zombieTime = opts.zombieTime || 32786;
|
|
119
113
|
this.keepHistory = opts.keepHistory || false;
|
|
120
114
|
|
|
@@ -130,11 +124,11 @@ module.exports = class MailTime {
|
|
|
130
124
|
this.zombieTime = 8192;
|
|
131
125
|
}
|
|
132
126
|
|
|
133
|
-
this.strategy = (opts.strategy === 'backup' || opts.strategy === 'balancer') ? opts.strategy : 'backup';
|
|
134
127
|
this.failsToNext = (opts.failsToNext && !isNaN(opts.failsToNext)) ? parseInt(opts.failsToNext) : 4;
|
|
135
|
-
this.
|
|
136
|
-
this.
|
|
137
|
-
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 = (() => {
|
|
138
132
|
if (typeof opts.from === 'string') {
|
|
139
133
|
return () => {
|
|
140
134
|
return opts.from;
|
|
@@ -148,9 +142,9 @@ module.exports = class MailTime {
|
|
|
148
142
|
return false;
|
|
149
143
|
})();
|
|
150
144
|
|
|
151
|
-
this.concatEmails
|
|
152
|
-
this.concatSubject
|
|
153
|
-
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>';
|
|
154
148
|
this.concatThrottling = ((opts.concatThrottling && !isNaN(opts.concatThrottling)) ? parseInt(opts.concatThrottling) : 60) * 1000;
|
|
155
149
|
|
|
156
150
|
if (this.concatThrottling < 2048) {
|
|
@@ -169,7 +163,7 @@ module.exports = class MailTime {
|
|
|
169
163
|
}
|
|
170
164
|
|
|
171
165
|
this.collection = opts.db.collection('__mailTimeQueue__' + this.prefix);
|
|
172
|
-
this.collection.createIndex({ to: 1, isSent: 1 }, (indexError) => {
|
|
166
|
+
this.collection.createIndex({ to: 1, isSent: 1, sendAt: 1 }, (indexError) => {
|
|
173
167
|
if (indexError) {
|
|
174
168
|
_logError('[createIndex]', indexError);
|
|
175
169
|
}
|
|
@@ -198,7 +192,7 @@ module.exports = class MailTime {
|
|
|
198
192
|
// mailOptions.Other nodeMailer `sendMail` options...
|
|
199
193
|
|
|
200
194
|
if (this.type === 'server') {
|
|
201
|
-
|
|
195
|
+
const scheduler = new JoSk({
|
|
202
196
|
db: opts.db,
|
|
203
197
|
debug: this.debug,
|
|
204
198
|
prefix: `mailTimeQueue${this.prefix}`,
|
|
@@ -208,7 +202,7 @@ module.exports = class MailTime {
|
|
|
208
202
|
maxRevolvingDelay: this.maxRevolvingDelay
|
|
209
203
|
});
|
|
210
204
|
|
|
211
|
-
|
|
205
|
+
scheduler.setInterval(this.___send.bind(this), this.revolvingInterval, `mailTimeQueue${this.prefix}`);
|
|
212
206
|
}
|
|
213
207
|
}
|
|
214
208
|
|
|
@@ -278,14 +272,13 @@ module.exports = class MailTime {
|
|
|
278
272
|
return _mailOpts;
|
|
279
273
|
}
|
|
280
274
|
|
|
281
|
-
|
|
282
|
-
@memberOf MailTime
|
|
283
|
-
@name ___send
|
|
284
|
-
@param ready {Function} - See JoSk NPM package
|
|
285
|
-
@returns {void}
|
|
275
|
+
/**
|
|
276
|
+
* @memberOf MailTime
|
|
277
|
+
* @name ___send
|
|
278
|
+
* @param ready {Function} - See JoSk NPM package
|
|
279
|
+
* @returns {void}
|
|
286
280
|
*/
|
|
287
281
|
___send(ready) {
|
|
288
|
-
let finished = 0;
|
|
289
282
|
const cursor = this.collection.find({
|
|
290
283
|
isSent: false,
|
|
291
284
|
sendAt: {
|
|
@@ -305,110 +298,96 @@ module.exports = class MailTime {
|
|
|
305
298
|
}
|
|
306
299
|
});
|
|
307
300
|
|
|
308
|
-
cursor.
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
ready();
|
|
301
|
+
cursor.forEach((task) => {
|
|
302
|
+
this.collection.updateOne({
|
|
303
|
+
_id: task._id
|
|
304
|
+
}, {
|
|
305
|
+
$set: {
|
|
306
|
+
isSent: true
|
|
307
|
+
},
|
|
308
|
+
$inc: {
|
|
309
|
+
tries: 1
|
|
310
|
+
}
|
|
311
|
+
}, (updateError) => {
|
|
312
|
+
if (updateError) {
|
|
313
|
+
this.___handleError(task, updateError, {});
|
|
314
|
+
} else {
|
|
315
|
+
let transport;
|
|
316
|
+
let transportIndex;
|
|
317
|
+
if (this.strategy === 'balancer') {
|
|
318
|
+
this.transport = this.transport + 1;
|
|
319
|
+
if (this.transport >= this.transports.length) {
|
|
320
|
+
this.transport = 0;
|
|
329
321
|
}
|
|
322
|
+
transportIndex = this.transport;
|
|
323
|
+
transport = this.transports[transportIndex];
|
|
324
|
+
} else {
|
|
325
|
+
transportIndex = task.transport;
|
|
326
|
+
transport = this.transports[transportIndex];
|
|
327
|
+
}
|
|
330
328
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
if (
|
|
337
|
-
this.
|
|
338
|
-
|
|
339
|
-
this.transport = 0;
|
|
340
|
-
}
|
|
341
|
-
transportIndex = this.transport;
|
|
342
|
-
transport = this.transports[this.transport];
|
|
343
|
-
} else {
|
|
344
|
-
transportIndex = task.transport;
|
|
345
|
-
transport = this.transports[task.transport];
|
|
329
|
+
try {
|
|
330
|
+
const _mailOpts = this.___compileMailOpts(transport, task);
|
|
331
|
+
|
|
332
|
+
_debug(this.debug, '[sendMail] [sending] To:', _mailOpts.to);
|
|
333
|
+
transport.sendMail(_mailOpts, (error, info) => {
|
|
334
|
+
if (error) {
|
|
335
|
+
this.___handleError(task, error, info);
|
|
336
|
+
return;
|
|
346
337
|
}
|
|
347
338
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
this.___handleError(task, 'Message not accepted or Greeting never received', info);
|
|
360
|
-
return;
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
_debug(this.debug, `email successfully sent, attempts: #${task.tries}, transport #${transportIndex} to: `, _mailOpts.to);
|
|
364
|
-
if (this.keepHistory) {
|
|
365
|
-
this.___triggerCallbacks(void 0, task, info);
|
|
366
|
-
} else {
|
|
367
|
-
this.collection.deleteOne({ _id: task._id }, defaultWriteConcern, () => {
|
|
368
|
-
this.___triggerCallbacks(void 0, task, info);
|
|
369
|
-
});
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
return;
|
|
339
|
+
if (info.accepted && !info.accepted.length) {
|
|
340
|
+
this.___handleError(task, 'Message not accepted or Greeting never received', info);
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
_debug(this.debug, `email successfully sent, attempts: #${task.tries}, transport #${transportIndex} to: `, _mailOpts.to);
|
|
345
|
+
if (this.keepHistory) {
|
|
346
|
+
this.___triggerCallbacks(void 0, task, info);
|
|
347
|
+
} else {
|
|
348
|
+
this.collection.deleteOne({ _id: task._id }, () => {
|
|
349
|
+
this.___triggerCallbacks(void 0, task, info);
|
|
373
350
|
});
|
|
374
|
-
} catch (e) {
|
|
375
|
-
_logError('Exception during runtime:', e);
|
|
376
|
-
this.___handleError(task, e, {});
|
|
377
351
|
}
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
_logError('
|
|
352
|
+
|
|
353
|
+
return;
|
|
354
|
+
});
|
|
355
|
+
} catch (e) {
|
|
356
|
+
_logError('Exception during runtime:', e);
|
|
357
|
+
this.___handleError(task, e, {});
|
|
383
358
|
}
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
}, (forEachError) => {
|
|
362
|
+
ready();
|
|
363
|
+
cursor.close();
|
|
364
|
+
if (forEachError) {
|
|
365
|
+
_logError('[___send] [forEach] [forEachError]', forEachError);
|
|
387
366
|
}
|
|
388
367
|
});
|
|
389
368
|
}
|
|
390
369
|
|
|
391
|
-
|
|
392
|
-
@memberOf MailTime
|
|
393
|
-
@name send
|
|
394
|
-
@description alias of `sendMail`
|
|
395
|
-
@returns {void}
|
|
370
|
+
/**
|
|
371
|
+
* @memberOf MailTime
|
|
372
|
+
* @name send
|
|
373
|
+
* @description alias of `sendMail`
|
|
374
|
+
* @returns {void}
|
|
396
375
|
*/
|
|
397
376
|
send(opts, callback) {
|
|
398
377
|
this.sendMail(opts, callback);
|
|
399
378
|
}
|
|
400
379
|
|
|
401
|
-
|
|
402
|
-
@memberOf MailTime
|
|
403
|
-
@name sendMail
|
|
404
|
-
@param opts {Object} - Letter options with next properties:
|
|
405
|
-
@param opts.sendAt {Date} - When email should be sent
|
|
406
|
-
@param opts.template {String} - Template string
|
|
407
|
-
@param opts[key] {mix} - Other MailOptions according to NodeMailer lib documentation
|
|
408
|
-
@param callback {Function} - [OPTIONAL] Callback function
|
|
409
|
-
@returns {void}
|
|
380
|
+
/**
|
|
381
|
+
* @memberOf MailTime
|
|
382
|
+
* @name sendMail
|
|
383
|
+
* @param opts {Object} - Letter options with next properties:
|
|
384
|
+
* @param opts.sendAt {Date} - When email should be sent
|
|
385
|
+
* @param opts.template {String} - Template string
|
|
386
|
+
* @param opts[key] {mix} - Other MailOptions according to NodeMailer lib documentation
|
|
387
|
+
* @param callback {Function} - [OPTIONAL] Callback function
|
|
388
|
+
* @returns {void}
|
|
410
389
|
*/
|
|
411
|
-
sendMail(opts = {}, callback =
|
|
390
|
+
sendMail(opts = {}, callback = noop) {
|
|
412
391
|
_debug(this.debug, '[sendMail] [attempt] To:', opts.to);
|
|
413
392
|
if (!opts.html && !opts.text) {
|
|
414
393
|
throw new Error('`html` nor `text` field is presented, at least one of those fields is required');
|
|
@@ -426,8 +405,8 @@ module.exports = class MailTime {
|
|
|
426
405
|
opts.concatSubject = false;
|
|
427
406
|
}
|
|
428
407
|
|
|
429
|
-
let _sendAt
|
|
430
|
-
const _template
|
|
408
|
+
let _sendAt = opts.sendAt;
|
|
409
|
+
const _template = opts.template;
|
|
431
410
|
const _concatSubject = opts.concatSubject;
|
|
432
411
|
delete opts.sendAt;
|
|
433
412
|
delete opts.template;
|
|
@@ -440,7 +419,10 @@ module.exports = class MailTime {
|
|
|
440
419
|
if (this.concatEmails) {
|
|
441
420
|
this.collection.findOne({
|
|
442
421
|
to: opts.to,
|
|
443
|
-
isSent: false
|
|
422
|
+
isSent: false,
|
|
423
|
+
sendAt: {
|
|
424
|
+
$lte: new Date(+_sendAt + this.concatThrottling)
|
|
425
|
+
}
|
|
444
426
|
}, {
|
|
445
427
|
projection: {
|
|
446
428
|
_id: 1,
|
|
@@ -503,13 +485,13 @@ module.exports = class MailTime {
|
|
|
503
485
|
return;
|
|
504
486
|
}
|
|
505
487
|
|
|
506
|
-
|
|
507
|
-
@memberOf MailTime
|
|
508
|
-
@name ___handleError
|
|
509
|
-
@param task {Object} - Email task record form Mongo
|
|
510
|
-
@param error {mix} - Error String/Object/Error
|
|
511
|
-
@param info {Object} - Info object returned from nodemailer
|
|
512
|
-
@returns {void}
|
|
488
|
+
/**
|
|
489
|
+
* @memberOf MailTime
|
|
490
|
+
* @name ___handleError
|
|
491
|
+
* @param task {Object} - Email task record form Mongo
|
|
492
|
+
* @param error {mix} - Error String/Object/Error
|
|
493
|
+
* @param info {Object} - Info object returned from nodemailer
|
|
494
|
+
* @returns {void}
|
|
513
495
|
*/
|
|
514
496
|
___handleError(task, error, info) {
|
|
515
497
|
if (!task) {
|
|
@@ -517,12 +499,14 @@ module.exports = class MailTime {
|
|
|
517
499
|
}
|
|
518
500
|
|
|
519
501
|
if (task.tries >= this.maxTries) {
|
|
520
|
-
this.
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
502
|
+
if (!this.keepHistory) {
|
|
503
|
+
this.collection.deleteOne({
|
|
504
|
+
_id: task._id
|
|
505
|
+
}, (deleteError) => {
|
|
506
|
+
_debug(this.debug, `Giving up trying send email after ${task.tries} attempts to: `, task.mailOptions[0].to, error, deleteError);
|
|
507
|
+
this.___triggerCallbacks(error, task, info);
|
|
508
|
+
});
|
|
509
|
+
}
|
|
526
510
|
} else {
|
|
527
511
|
let transportIndex = task.transport;
|
|
528
512
|
|
|
@@ -547,16 +531,16 @@ module.exports = class MailTime {
|
|
|
547
531
|
}
|
|
548
532
|
}
|
|
549
533
|
|
|
550
|
-
|
|
551
|
-
@memberOf MailTime
|
|
552
|
-
@name ___addToQueue
|
|
553
|
-
@param opts {Object} - Letter options with next properties:
|
|
554
|
-
@param opts.sendAt {Date} - When email should be sent
|
|
555
|
-
@param opts.template {String} - Email template
|
|
556
|
-
@param opts.mailOptions {Object} - MailOptions according to NodeMailer lib
|
|
557
|
-
@param opts.concatSubject {String} - Email subject used when sending multiple concatenated emails
|
|
558
|
-
@param callback {Function} - [OPTIONAL] Callback function
|
|
559
|
-
@returns {void}
|
|
534
|
+
/**
|
|
535
|
+
* @memberOf MailTime
|
|
536
|
+
* @name ___addToQueue
|
|
537
|
+
* @param opts {Object} - Letter options with next properties:
|
|
538
|
+
* @param opts.sendAt {Date} - When email should be sent
|
|
539
|
+
* @param opts.template {String} - Email template
|
|
540
|
+
* @param opts.mailOptions {Object} - MailOptions according to NodeMailer lib
|
|
541
|
+
* @param opts.concatSubject {String} - Email subject used when sending multiple concatenated emails
|
|
542
|
+
* @param callback {Function} - [OPTIONAL] Callback function
|
|
543
|
+
* @returns {void}
|
|
560
544
|
*/
|
|
561
545
|
___addToQueue(opts, callback) {
|
|
562
546
|
_debug(this.debug, '[sendMail] [adding to queue] To:', opts.mailOptions.to);
|
|
@@ -590,16 +574,16 @@ module.exports = class MailTime {
|
|
|
590
574
|
});
|
|
591
575
|
}
|
|
592
576
|
|
|
593
|
-
|
|
594
|
-
@memberOf MailTime
|
|
595
|
-
@name ___render
|
|
596
|
-
@param string {String} - Template with Spacebars/Blaze/Mustache-like placeholders
|
|
597
|
-
@param replacements {Object} - Blaze/Mustache-like helpers Object
|
|
598
|
-
@returns {String}
|
|
577
|
+
/**
|
|
578
|
+
* @memberOf MailTime
|
|
579
|
+
* @name ___render
|
|
580
|
+
* @param string {String} - Template with Spacebars/Blaze/Mustache-like placeholders
|
|
581
|
+
* @param replacements {Object} - Blaze/Mustache-like helpers Object
|
|
582
|
+
* @returns {String}
|
|
599
583
|
*/
|
|
600
584
|
___render(_string, replacements) {
|
|
601
585
|
let i;
|
|
602
|
-
let string
|
|
586
|
+
let string = _string;
|
|
603
587
|
const matchHTML = string.match(/\{{3}\s?([a-zA-Z0-9\-\_]+)\s?\}{3}/g);
|
|
604
588
|
if (matchHTML) {
|
|
605
589
|
for (i = 0; i < matchHTML.length; i++) {
|
|
@@ -609,7 +593,7 @@ module.exports = class MailTime {
|
|
|
609
593
|
}
|
|
610
594
|
}
|
|
611
595
|
|
|
612
|
-
const matchStr
|
|
596
|
+
const matchStr = string.match(/\{{2}\s?([a-zA-Z0-9\-\_]+)\s?\}{2}/g);
|
|
613
597
|
if (matchStr) {
|
|
614
598
|
for (i = 0; i < matchStr.length; i++) {
|
|
615
599
|
if (replacements[matchStr[i].replace('{{', '').replace('}}', '').trim()]) {
|
|
@@ -620,13 +604,13 @@ module.exports = class MailTime {
|
|
|
620
604
|
return string;
|
|
621
605
|
}
|
|
622
606
|
|
|
623
|
-
|
|
624
|
-
@memberOf MailTime
|
|
625
|
-
@name ___triggerCallbacks
|
|
626
|
-
@param error {mix} - Error thrown during sending email
|
|
627
|
-
@param task {Object} - Task record from mongodb
|
|
628
|
-
@param info {Object} - Info object returned from NodeMailer's `sendMail` method
|
|
629
|
-
@returns {void 0}
|
|
607
|
+
/**
|
|
608
|
+
* @memberOf MailTime
|
|
609
|
+
* @name ___triggerCallbacks
|
|
610
|
+
* @param error {mix} - Error thrown during sending email
|
|
611
|
+
* @param task {Object} - Task record from mongodb
|
|
612
|
+
* @param info {Object} - Info object returned from NodeMailer's `sendMail` method
|
|
613
|
+
* @returns {void 0}
|
|
630
614
|
*/
|
|
631
615
|
___triggerCallbacks(error, task, info) {
|
|
632
616
|
const _id = task._id.toHexString();
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mail-time",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Bulletproof email queue on top of NodeMailer
|
|
3
|
+
"version": "1.3.1",
|
|
4
|
+
"description": "Bulletproof email queue on top of NodeMailer for a single and multi-server setups",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"test": "mocha ./test/npm.js"
|
|
@@ -36,12 +36,12 @@
|
|
|
36
36
|
],
|
|
37
37
|
"repository": {
|
|
38
38
|
"type": "git",
|
|
39
|
-
"url": "git://github.com/
|
|
39
|
+
"url": "git://github.com/veliovgroup/mail-time.git"
|
|
40
40
|
},
|
|
41
41
|
"bugs": {
|
|
42
|
-
"url": "https://github.com/
|
|
42
|
+
"url": "https://github.com/veliovgroup/mail-time/issues"
|
|
43
43
|
},
|
|
44
|
-
"homepage": "https://github.com/
|
|
44
|
+
"homepage": "https://github.com/veliovgroup/mail-time",
|
|
45
45
|
"author": "dr.dimitru (https://veliovgroup.com)",
|
|
46
46
|
"license": "BSD-3-Clause",
|
|
47
47
|
"engines": {
|
|
@@ -49,15 +49,15 @@
|
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
51
|
"deepmerge": "^4.2.2",
|
|
52
|
-
"josk": "^
|
|
52
|
+
"josk": "^3.0.0"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
|
-
"bson": "^4.
|
|
56
|
-
"bson-ext": "^
|
|
57
|
-
"chai": "^4.
|
|
58
|
-
"mocha": "^
|
|
59
|
-
"mongodb": "^
|
|
60
|
-
"nodemailer": "^6.
|
|
61
|
-
"nodemailer-direct-transport": "^3.
|
|
55
|
+
"bson": "^4.6.4",
|
|
56
|
+
"bson-ext": "^4.0.2",
|
|
57
|
+
"chai": "^4.3.6",
|
|
58
|
+
"mocha": "^10.0.0",
|
|
59
|
+
"mongodb": "^4.6.0",
|
|
60
|
+
"nodemailer": "^6.7.5",
|
|
61
|
+
"nodemailer-direct-transport": "^3.0.7"
|
|
62
62
|
}
|
|
63
63
|
}
|
package/CHANGELOG.md
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
For full changelog see [releases](https://github.com/VeliovGroup/Mail-Time/releases) in GitHub
|
package/HISTORY.md
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
For full history see [releases](https://github.com/VeliovGroup/Mail-Time/releases) in GitHub
|