mail-time 1.3.0 → 1.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1 -0
- package/HISTORY.md +1 -0
- package/README.md +36 -132
- package/index.js +23 -21
- package/package.json +7 -7
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
For full changelog see [releases](https://github.com/veliovgroup/mail-time/releases) in GitHub
|
package/HISTORY.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
For full history see [releases](https://github.com/veliovgroup/mail-time/releases) in GitHub
|
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
[](https://github.com/sponsors/
|
|
1
|
+
[](https://github.com/sponsors/dr-dimitru)
|
|
2
2
|
[](https://paypal.me/veliovgroup)
|
|
3
3
|
<a href="https://ostr.io/info/built-by-developers-for-developers">
|
|
4
4
|
<img src="https://ostr.io/apple-touch-icon-60x60.png" height="20">
|
|
@@ -6,37 +6,28 @@
|
|
|
6
6
|
|
|
7
7
|
# MailTime
|
|
8
8
|
|
|
9
|
-
Micro-service package for mail queue, with *Server* and *Client* API.
|
|
10
|
-
Build on top of [`nodemailer`](https://github.com/nodemailer/nodemailer) package.
|
|
11
|
-
|
|
12
|
-
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.
|
|
15
|
-
While *Client* is only put emails into the queue.
|
|
16
|
-
|
|
17
9
|
"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
10
|
|
|
19
|
-
Every `MailTime` instance can get configured as *Server* or *Client*.
|
|
11
|
+
Every `MailTime` instance can get `type` configured as *Server* or *Client*.
|
|
20
12
|
|
|
21
|
-
The main difference between *Server* and *Client*
|
|
13
|
+
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.
|
|
22
14
|
|
|
23
15
|
## ToC
|
|
24
16
|
|
|
25
|
-
- [How it works?](https://github.com/veliovgroup/
|
|
26
|
-
- [With single SMTP](https://github.com/veliovgroup/
|
|
27
|
-
- [With multiple SMTP](https://github.com/veliovgroup/
|
|
28
|
-
- [As Micro-Service](https://github.com/veliovgroup/
|
|
29
|
-
- [Features](https://github.com/veliovgroup/
|
|
30
|
-
- [Installation](https://github.com/veliovgroup/
|
|
31
|
-
- [Meteor.js
|
|
32
|
-
- [
|
|
33
|
-
- [
|
|
34
|
-
- [
|
|
35
|
-
- [
|
|
36
|
-
- [
|
|
37
|
-
|
|
38
|
-
- [
|
|
39
|
-
- [~92% tests coverage](https://github.com/veliovgroup/Mail-Time#testing)
|
|
17
|
+
- [How it works?](https://github.com/veliovgroup/mail-time#how-it-works)
|
|
18
|
+
- [With single SMTP](https://github.com/veliovgroup/mail-time#single-point-of-failure)
|
|
19
|
+
- [With multiple SMTP](https://github.com/veliovgroup/mail-time#multiple-smtp-providers)
|
|
20
|
+
- [As Micro-Service](https://github.com/veliovgroup/mail-time#cluster-issue)
|
|
21
|
+
- [Features](https://github.com/veliovgroup/mail-time#features)
|
|
22
|
+
- [Installation](https://github.com/veliovgroup/mail-time#installation)
|
|
23
|
+
- [Meteor.js usage](https://github.com/veliovgroup/mail-time/blob/master/docs/meteor.md)
|
|
24
|
+
- [Usage example](https://github.com/veliovgroup/mail-time#basic-usage)
|
|
25
|
+
- [API](https://github.com/veliovgroup/mail-time#api)
|
|
26
|
+
- [*Constructor*](https://github.com/veliovgroup/mail-time#new-mailtimeopts-constructor)
|
|
27
|
+
- [`.send()`](https://github.com/veliovgroup/mail-time#sendmailopts--callback)
|
|
28
|
+
- [Default Template](https://github.com/veliovgroup/mail-time#static-mailtimetemplate)
|
|
29
|
+
- [Custom Templates](https://github.com/veliovgroup/mail-time#template-example)
|
|
30
|
+
- [~92% tests coverage](https://github.com/veliovgroup/mail-time#testing)
|
|
40
31
|
|
|
41
32
|
## Main features:
|
|
42
33
|
|
|
@@ -174,8 +165,8 @@ const directTransportOpts = {
|
|
|
174
165
|
from: 'no-reply@example.com',
|
|
175
166
|
};
|
|
176
167
|
transports.push(nodemailer.createTransport(directTransport(directTransportOpts)));
|
|
177
|
-
// IMPORTANT:
|
|
178
|
-
//
|
|
168
|
+
// IMPORTANT: Add `.options` to a newly created transport,
|
|
169
|
+
// this is necessary to make sure options are available to MailTime package:
|
|
179
170
|
transports[0].options = directTransportOpts;
|
|
180
171
|
|
|
181
172
|
// Private SMTP
|
|
@@ -213,12 +204,12 @@ transports.push(nodemailer.createTransport({
|
|
|
213
204
|
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:
|
|
214
205
|
|
|
215
206
|
```js
|
|
207
|
+
const MailTime = require('mail-time');
|
|
216
208
|
const MongoClient = require('mongodb').MongoClient;
|
|
217
|
-
const MailTime = require('mail-time');
|
|
218
|
-
const dbName = 'DatabaseName';
|
|
219
209
|
|
|
220
|
-
|
|
221
|
-
|
|
210
|
+
const dbName = 'databaseName';
|
|
211
|
+
|
|
212
|
+
// Use MONGO_URL environment variable to store connection string to MongoDB
|
|
222
213
|
// example: "MONGO_URL=mongodb://127.0.0.1:27017/myapp node mail-micro-service.js"
|
|
223
214
|
MongoClient.connect(process.env.MONGO_URL, (error, client) => {
|
|
224
215
|
const db = client.db(dbName);
|
|
@@ -243,9 +234,10 @@ MongoClient.connect(process.env.MONGO_URL, (error, client) => {
|
|
|
243
234
|
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:
|
|
244
235
|
|
|
245
236
|
```js
|
|
237
|
+
const MailTime = require('mail-time');
|
|
246
238
|
const MongoClient = require('mongodb').MongoClient;
|
|
247
|
-
|
|
248
|
-
const dbName
|
|
239
|
+
|
|
240
|
+
const dbName = 'databaseName';
|
|
249
241
|
|
|
250
242
|
MongoClient.connect(process.env.MONGO_URL, (error, client) => {
|
|
251
243
|
const db = client.db(dbName);
|
|
@@ -275,7 +267,7 @@ mailQueue.sendMail({
|
|
|
275
267
|
Create two `MailTime` instances with different settings.
|
|
276
268
|
|
|
277
269
|
```js
|
|
278
|
-
//
|
|
270
|
+
// CREATE mailQueue FOR NON-URGENT EMAILS WHICH IS OKAY TO CONCATENATE
|
|
279
271
|
const mailQueue = new MailTime({
|
|
280
272
|
db: db,
|
|
281
273
|
interval: 35,
|
|
@@ -286,7 +278,7 @@ const mailQueue = new MailTime({
|
|
|
286
278
|
zombieTime: 120000
|
|
287
279
|
});
|
|
288
280
|
|
|
289
|
-
//
|
|
281
|
+
// CREATE mailInstantQueue FOR TRANSACTIONAL EMAILS AND ALERTS
|
|
290
282
|
const mailInstantQueue = new MailTime({
|
|
291
283
|
db: db,
|
|
292
284
|
prefix: 'instant',
|
|
@@ -318,12 +310,12 @@ All options passed to the `.sendMail()` method is available inside `text`, `html
|
|
|
318
310
|
|
|
319
311
|
```js
|
|
320
312
|
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>',
|
|
313
|
+
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
314
|
signInCode: {
|
|
323
|
-
text: 'Hello @{{username}}! Here\'s your login code: {{code}}'
|
|
324
|
-
html: `<h1>Sign-in request</h1><p>Hello @{{username}}! Copy your login code below <pre><code>{{code}}</code></pre>`
|
|
315
|
+
text: 'Hello @{{username}}! Here\'s your login code: {{code}}',
|
|
316
|
+
html: `<h1>Sign-in request</h1><p>Hello @{{username}}! <p>Copy your login code below:</p> <pre><code>{{code}}</code></pre>`
|
|
325
317
|
}
|
|
326
|
-
}
|
|
318
|
+
};
|
|
327
319
|
|
|
328
320
|
const mailQueue = new MailTime({
|
|
329
321
|
db: db,
|
|
@@ -334,85 +326,12 @@ mailQueue.sendMail({
|
|
|
334
326
|
to: 'user@gmail.com',
|
|
335
327
|
subject: 'Sign-in request',
|
|
336
328
|
username: 'johndoe',
|
|
337
|
-
code: 'XXXXX-YY'
|
|
329
|
+
code: 'XXXXX-YY',
|
|
338
330
|
text: templates.signInCode.text,
|
|
339
331
|
html: templates.signInCode.html
|
|
340
332
|
});
|
|
341
333
|
```
|
|
342
334
|
|
|
343
|
-
## Meteor.js usage:
|
|
344
|
-
|
|
345
|
-
Mail-Time package can be installed and used within [Meteor.js](https://docs.meteor.com/) via [NPM](https://www.npmjs.com/package/mail-time) or [Atmosphere](https://atmospherejs.com/ostrio/mailer)
|
|
346
|
-
|
|
347
|
-
### Installation & Import (*via NPM*):
|
|
348
|
-
|
|
349
|
-
Install [NPM `mail-time` package](https://www.npmjs.com/package/mail-time):
|
|
350
|
-
|
|
351
|
-
```shell
|
|
352
|
-
meteor npm install --save mail-time
|
|
353
|
-
```
|
|
354
|
-
|
|
355
|
-
Meteor.js: ES6 Import NPM package:
|
|
356
|
-
|
|
357
|
-
```js
|
|
358
|
-
import MailTime from 'mail-time';
|
|
359
|
-
```
|
|
360
|
-
|
|
361
|
-
### Installation & Import (*via Atmosphere*):
|
|
362
|
-
|
|
363
|
-
Install [Atmosphere `ostrio:mailer` package](https://atmospherejs.com/ostrio/mailer):
|
|
364
|
-
|
|
365
|
-
```shell
|
|
366
|
-
meteor add ostrio:mailer
|
|
367
|
-
```
|
|
368
|
-
|
|
369
|
-
Meteor.js: ES6 Import atmosphere package:
|
|
370
|
-
|
|
371
|
-
```js
|
|
372
|
-
import MailTime from 'meteor/ostrio:mailer';
|
|
373
|
-
```
|
|
374
|
-
|
|
375
|
-
### Usage:
|
|
376
|
-
|
|
377
|
-
`mail-time` package usage examples in Meteor.js
|
|
378
|
-
|
|
379
|
-
```js
|
|
380
|
-
import { MongoInternals } from 'meteor/mongo';
|
|
381
|
-
|
|
382
|
-
import MailTime from 'mail-time';
|
|
383
|
-
import nodemailer from 'nodemailer';
|
|
384
|
-
// Use DIRECT transport
|
|
385
|
-
// To enable sending email from localhost
|
|
386
|
-
// install "nodemailer-direct-transport" NPM package:
|
|
387
|
-
import directTransport from 'nodemailer-direct-transport';
|
|
388
|
-
|
|
389
|
-
const transports = [];
|
|
390
|
-
const directTransportOpts = {
|
|
391
|
-
pool: false,
|
|
392
|
-
direct: true,
|
|
393
|
-
name: 'mail.example.com',
|
|
394
|
-
from: 'no-reply@example.com',
|
|
395
|
-
};
|
|
396
|
-
transports.push(nodemailer.createTransport(directTransport(directTransportOpts)));
|
|
397
|
-
// IMPORTANT: Copy-paste passed options from directTransport() to
|
|
398
|
-
// transport's "options" property, to make sure it's available to MailTime package:
|
|
399
|
-
transports[0].options = directTransportOpts;
|
|
400
|
-
|
|
401
|
-
////////////////////////
|
|
402
|
-
// For more transports example see sections above and read nodemailer's docs
|
|
403
|
-
////////////////////////
|
|
404
|
-
|
|
405
|
-
const mailQueue = new MailTime({
|
|
406
|
-
db: MongoInternals.defaultRemoteCollectionDriver().mongo.db, // MongoDB
|
|
407
|
-
transports,
|
|
408
|
-
from(transport) {
|
|
409
|
-
// To pass spam-filters `from` field should be correctly set
|
|
410
|
-
// for each transport, check `transport` object for more options
|
|
411
|
-
return '"Awesome App" <' + transport.options.from + '>';
|
|
412
|
-
}
|
|
413
|
-
});
|
|
414
|
-
```
|
|
415
|
-
|
|
416
335
|
## API
|
|
417
336
|
|
|
418
337
|
All available constructor options and `.sendMail()` method API overview
|
|
@@ -447,15 +366,15 @@ All available constructor options and `.sendMail()` method API overview
|
|
|
447
366
|
- `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
|
|
448
367
|
- `opts.template` - Email specific template, this will override default template passed to `MailTime` constructor
|
|
449
368
|
- `opts.concatSubject` - Email specific concatenation subject, this will override default concatenation subject passed to `MailTime` constructor
|
|
450
|
-
- `opts[key]` {*Mix*} - Other custom and NodeMailer specific options, like `text`, `html` and `to`,
|
|
369
|
+
- `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
|
|
451
370
|
- `callback` {*Function*} - Callback called after the email was sent or failed to be sent. __Do not use on multi-server setup__
|
|
452
371
|
|
|
453
372
|
### `static MailTime.Template`
|
|
454
373
|
|
|
455
|
-
Simple and bulletproof HTML template, see [its source](https://github.com/veliovgroup/
|
|
374
|
+
Simple and bulletproof HTML template, see [its source](https://github.com/veliovgroup/mail-time/blob/master/template.html). Usage example:
|
|
456
375
|
|
|
457
376
|
```js
|
|
458
|
-
const MailTime
|
|
377
|
+
const MailTime = require('mail-time');
|
|
459
378
|
// Make it default
|
|
460
379
|
const mailQueue = new MailTime({
|
|
461
380
|
db: db, // MongoDB
|
|
@@ -505,21 +424,6 @@ DEBUG="true" EMAIL_DOMAIN="example.com" MONGO_URL="mongodb://127.0.0.1:27017/npm
|
|
|
505
424
|
# Be patient, tests are taking around 2 mins
|
|
506
425
|
```
|
|
507
426
|
|
|
508
|
-
Test Atmosphere (meteor.js) package:
|
|
509
|
-
|
|
510
|
-
```shell
|
|
511
|
-
# Default
|
|
512
|
-
EMAIL_DOMAIN="example.com" meteor test-packages ./ --driver-package=meteortesting:mocha
|
|
513
|
-
|
|
514
|
-
# With custom port
|
|
515
|
-
DEBUG="true" EMAIL_DOMAIN="example.com" meteor test-packages ./ --driver-package=meteortesting:mocha --port 8888
|
|
516
|
-
|
|
517
|
-
# With local MongoDB and custom port
|
|
518
|
-
DEBUG="true" EMAIL_DOMAIN="example.com" MONGO_URL="mongodb://127.0.0.1:27017/meteor-mail-time-test-001" meteor test-packages ./ --driver-package=meteortesting:mocha --port 8888
|
|
519
|
-
|
|
520
|
-
# Be patient, tests are taking around 2 mins
|
|
521
|
-
```
|
|
522
|
-
|
|
523
427
|
## Support this project:
|
|
524
428
|
|
|
525
429
|
- Star on [GitHub](https://github.com/veliovgroup/mail-time)
|
package/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const JoSk = require('josk');
|
|
2
2
|
const noop = () => {};
|
|
3
|
-
const merge
|
|
3
|
+
const merge = require('deepmerge');
|
|
4
4
|
const _debug = (isDebug, ...args) => {
|
|
5
5
|
if (isDebug) {
|
|
6
6
|
console.info.call(console, '[DEBUG] [mail-time]', `${new Date}`, ...args);
|
|
@@ -102,13 +102,13 @@ module.exports = class MailTime {
|
|
|
102
102
|
throw new Error('[mail-time] MongoDB database {db} option is required, like returned from `MongoClient.connect`');
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
-
this.callbacks
|
|
106
|
-
this.type
|
|
107
|
-
this.debug
|
|
108
|
-
this.prefix
|
|
109
|
-
this.maxTries
|
|
110
|
-
this.interval
|
|
111
|
-
this.template
|
|
105
|
+
this.callbacks = {};
|
|
106
|
+
this.type = (!opts.type || (opts.type !== 'client' && opts.type !== 'server')) ? 'server' : opts.type;
|
|
107
|
+
this.debug = (opts.debug !== true) ? false : true;
|
|
108
|
+
this.prefix = opts.prefix || '';
|
|
109
|
+
this.maxTries = ((opts.maxTries && !isNaN(opts.maxTries)) ? parseInt(opts.maxTries) : 59) + 1;
|
|
110
|
+
this.interval = ((opts.interval && !isNaN(opts.interval)) ? parseInt(opts.interval) : 60) * 1000;
|
|
111
|
+
this.template = (typeof opts.template === 'string') ? opts.template : '{{{html}}}';
|
|
112
112
|
this.zombieTime = opts.zombieTime || 32786;
|
|
113
113
|
this.keepHistory = opts.keepHistory || false;
|
|
114
114
|
|
|
@@ -124,11 +124,11 @@ module.exports = class MailTime {
|
|
|
124
124
|
this.zombieTime = 8192;
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
-
this.strategy = (opts.strategy === 'backup' || opts.strategy === 'balancer') ? opts.strategy : 'backup';
|
|
128
127
|
this.failsToNext = (opts.failsToNext && !isNaN(opts.failsToNext)) ? parseInt(opts.failsToNext) : 4;
|
|
129
|
-
this.
|
|
130
|
-
this.
|
|
131
|
-
this.
|
|
128
|
+
this.strategy = (opts.strategy === 'backup' || opts.strategy === 'balancer') ? opts.strategy : 'backup';
|
|
129
|
+
this.transports = opts.transports || [];
|
|
130
|
+
this.transport = 0;
|
|
131
|
+
this.from = (() => {
|
|
132
132
|
if (typeof opts.from === 'string') {
|
|
133
133
|
return () => {
|
|
134
134
|
return opts.from;
|
|
@@ -320,10 +320,10 @@ module.exports = class MailTime {
|
|
|
320
320
|
this.transport = 0;
|
|
321
321
|
}
|
|
322
322
|
transportIndex = this.transport;
|
|
323
|
-
transport = this.transports[
|
|
323
|
+
transport = this.transports[transportIndex];
|
|
324
324
|
} else {
|
|
325
325
|
transportIndex = task.transport;
|
|
326
|
-
transport = this.transports[
|
|
326
|
+
transport = this.transports[transportIndex];
|
|
327
327
|
}
|
|
328
328
|
|
|
329
329
|
try {
|
|
@@ -499,12 +499,14 @@ module.exports = class MailTime {
|
|
|
499
499
|
}
|
|
500
500
|
|
|
501
501
|
if (task.tries >= this.maxTries) {
|
|
502
|
-
this.
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
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
|
+
}
|
|
508
510
|
} else {
|
|
509
511
|
let transportIndex = task.transport;
|
|
510
512
|
|
|
@@ -591,7 +593,7 @@ module.exports = class MailTime {
|
|
|
591
593
|
}
|
|
592
594
|
}
|
|
593
595
|
|
|
594
|
-
const matchStr
|
|
596
|
+
const matchStr = string.match(/\{{2}\s?([a-zA-Z0-9\-\_]+)\s?\}{2}/g);
|
|
595
597
|
if (matchStr) {
|
|
596
598
|
for (i = 0; i < matchStr.length; i++) {
|
|
597
599
|
if (replacements[matchStr[i].replace('{{', '').replace('}}', '').trim()]) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mail-time",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.3",
|
|
4
4
|
"description": "Bulletproof email queue on top of NodeMailer for a single and multi-server setups",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -36,12 +36,12 @@
|
|
|
36
36
|
],
|
|
37
37
|
"repository": {
|
|
38
38
|
"type": "git",
|
|
39
|
-
"url": "git://github.com/veliovgroup/
|
|
39
|
+
"url": "git://github.com/veliovgroup/mail-time.git"
|
|
40
40
|
},
|
|
41
41
|
"bugs": {
|
|
42
|
-
"url": "https://github.com/veliovgroup/
|
|
42
|
+
"url": "https://github.com/veliovgroup/mail-time/issues"
|
|
43
43
|
},
|
|
44
|
-
"homepage": "https://github.com/veliovgroup/
|
|
44
|
+
"homepage": "https://github.com/veliovgroup/mail-time",
|
|
45
45
|
"author": "dr.dimitru (https://veliovgroup.com)",
|
|
46
46
|
"license": "BSD-3-Clause",
|
|
47
47
|
"engines": {
|
|
@@ -49,15 +49,15 @@
|
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
51
|
"deepmerge": "^4.2.2",
|
|
52
|
-
"josk": "^3.0.
|
|
52
|
+
"josk": "^3.0.2"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
55
|
"bson": "^4.6.4",
|
|
56
56
|
"bson-ext": "^4.0.2",
|
|
57
57
|
"chai": "^4.3.6",
|
|
58
58
|
"mocha": "^10.0.0",
|
|
59
|
-
"mongodb": "^4.
|
|
60
|
-
"nodemailer": "^6.7.
|
|
59
|
+
"mongodb": "^4.7.0",
|
|
60
|
+
"nodemailer": "^6.7.6",
|
|
61
61
|
"nodemailer-direct-transport": "^3.0.7"
|
|
62
62
|
}
|
|
63
63
|
}
|