@storyous/mailer 1.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 +21 -0
- package/__tests__/mailer.test.js +63 -0
- package/__tests__/mailer.test.ts +62 -0
- package/lib/index.js +61 -0
- package/lib/index.ts +75 -0
- package/lib/mailSender.js +81 -0
- package/lib/mailSender.ts +125 -0
- package/package.json +43 -0
- package/tsconfig.json +8 -0
package/README.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# `mailer`
|
|
2
|
+
|
|
3
|
+
## Usage
|
|
4
|
+
|
|
5
|
+
``` typescript
|
|
6
|
+
import mailer from '@storyous/mailer';
|
|
7
|
+
|
|
8
|
+
const sender = mailer.createSender({
|
|
9
|
+
from: 'test@storyous.cz',
|
|
10
|
+
to: 'test@storyous.cz',
|
|
11
|
+
template: 'some.template',
|
|
12
|
+
defaultLang: 'cs',
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
await sender.send({
|
|
16
|
+
bcc: ['dev@storyous.cz', 'dev2@storyous.cz'],
|
|
17
|
+
cc: ['dev@storyous.cz', 'dev2@storyous.cz'],
|
|
18
|
+
replyTo: 'dev@storyous.cz',
|
|
19
|
+
lang: 'en',
|
|
20
|
+
});
|
|
21
|
+
```
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const test_utils_1 = __importDefault(require("@storyous/test-utils"));
|
|
7
|
+
const assert_1 = __importDefault(require("assert"));
|
|
8
|
+
const mocha_1 = require("mocha");
|
|
9
|
+
const lib_1 = __importDefault(require("../lib"));
|
|
10
|
+
const defaultOptions = {
|
|
11
|
+
from: 'test@storyous.cz',
|
|
12
|
+
to: 'test@storyous.cz',
|
|
13
|
+
template: 'some.template',
|
|
14
|
+
defaultLang: 'cs',
|
|
15
|
+
};
|
|
16
|
+
const sender = lib_1.default.createSender(defaultOptions);
|
|
17
|
+
(0, mocha_1.describe)('packages/mailer', () => {
|
|
18
|
+
test_utils_1.default.init(undefined, undefined, lib_1.default);
|
|
19
|
+
(0, mocha_1.it)('should send email', async () => {
|
|
20
|
+
await sender.send({
|
|
21
|
+
to: ['dev@storyous.cz', 'dev2@storyous.cz'],
|
|
22
|
+
bcc: ['dev@storyous.cz', 'dev2@storyous.cz'],
|
|
23
|
+
cc: ['dev@storyous.cz', 'dev2@storyous.cz'],
|
|
24
|
+
replyTo: 'dev@storyous.cz',
|
|
25
|
+
});
|
|
26
|
+
const sentEmails = test_utils_1.default.getSentMails();
|
|
27
|
+
assert_1.default.deepStrictEqual(sentEmails.length, 1);
|
|
28
|
+
});
|
|
29
|
+
(0, mocha_1.it)('should send email with person lang', async () => {
|
|
30
|
+
await sender.send({
|
|
31
|
+
person: { lang: 'cs' },
|
|
32
|
+
});
|
|
33
|
+
const sentEmails = test_utils_1.default.getSentMails();
|
|
34
|
+
assert_1.default.deepStrictEqual(sentEmails.length, 1);
|
|
35
|
+
});
|
|
36
|
+
(0, mocha_1.it)('should send email with lang', async () => {
|
|
37
|
+
await sender.send({
|
|
38
|
+
lang: 'cs',
|
|
39
|
+
});
|
|
40
|
+
const sentEmails = test_utils_1.default.getSentMails();
|
|
41
|
+
assert_1.default.deepStrictEqual(sentEmails.length, 1);
|
|
42
|
+
});
|
|
43
|
+
(0, mocha_1.it)('should throw exception when options are empty', async () => {
|
|
44
|
+
try {
|
|
45
|
+
await lib_1.default.sendMail({});
|
|
46
|
+
assert_1.default.fail('The method must throw an exception.');
|
|
47
|
+
}
|
|
48
|
+
catch (e) {
|
|
49
|
+
assert_1.default.deepStrictEqual(e.message, 'I need to know who this email is being sent to :-(');
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
(0, mocha_1.it)('should throw exception when transporter is not initialized', async () => {
|
|
53
|
+
lib_1.default._transporter = undefined;
|
|
54
|
+
try {
|
|
55
|
+
await lib_1.default.sendMail({});
|
|
56
|
+
assert_1.default.fail('The method must throw an exception.');
|
|
57
|
+
}
|
|
58
|
+
catch (e) {
|
|
59
|
+
assert_1.default.deepStrictEqual(e.message, 'The transporter is not initialized yet! Use createSender before.');
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWFpbGVyLnRlc3QuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJtYWlsZXIudGVzdC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7OztBQUFBLHNFQUE2QztBQUM3QyxvREFBNEI7QUFDNUIsaUNBQXFDO0FBQ3JDLGlEQUE0QjtBQUU1QixNQUFNLGNBQWMsR0FBRztJQUNuQixJQUFJLEVBQUUsa0JBQWtCO0lBQ3hCLEVBQUUsRUFBRSxrQkFBa0I7SUFDdEIsUUFBUSxFQUFFLGVBQWU7SUFDekIsV0FBVyxFQUFFLElBQUk7Q0FDcEIsQ0FBQztBQUNGLE1BQU0sTUFBTSxHQUFHLGFBQU0sQ0FBQyxZQUFZLENBQUMsY0FBYyxDQUFDLENBQUM7QUFFbkQsSUFBQSxnQkFBUSxFQUFDLGlCQUFpQixFQUFFLEdBQUcsRUFBRTtJQUM3QixvQkFBUyxDQUFDLElBQUksQ0FBQyxTQUFTLEVBQUUsU0FBUyxFQUFFLGFBQU0sQ0FBQyxDQUFDO0lBRTdDLElBQUEsVUFBRSxFQUFDLG1CQUFtQixFQUFFLEtBQUssSUFBSSxFQUFFO1FBQy9CLE1BQU0sTUFBTSxDQUFDLElBQUksQ0FBQztZQUNkLEVBQUUsRUFBRSxDQUFDLGlCQUFpQixFQUFFLGtCQUFrQixDQUFDO1lBQzNDLEdBQUcsRUFBRSxDQUFDLGlCQUFpQixFQUFFLGtCQUFrQixDQUFDO1lBQzVDLEVBQUUsRUFBRSxDQUFDLGlCQUFpQixFQUFFLGtCQUFrQixDQUFDO1lBQzNDLE9BQU8sRUFBRSxpQkFBaUI7U0FDN0IsQ0FBQyxDQUFDO1FBQ0gsTUFBTSxVQUFVLEdBQUcsb0JBQVMsQ0FBQyxZQUFZLEVBQUUsQ0FBQztRQUM1QyxnQkFBTSxDQUFDLGVBQWUsQ0FBQyxVQUFVLENBQUMsTUFBTSxFQUFFLENBQUMsQ0FBQyxDQUFDO0lBQ2pELENBQUMsQ0FBQyxDQUFDO0lBRUgsSUFBQSxVQUFFLEVBQUMsb0NBQW9DLEVBQUUsS0FBSyxJQUFJLEVBQUU7UUFDaEQsTUFBTSxNQUFNLENBQUMsSUFBSSxDQUFDO1lBQ2QsTUFBTSxFQUFFLEVBQUUsSUFBSSxFQUFFLElBQUksRUFBRTtTQUN6QixDQUFDLENBQUM7UUFDSCxNQUFNLFVBQVUsR0FBRyxvQkFBUyxDQUFDLFlBQVksRUFBRSxDQUFDO1FBQzVDLGdCQUFNLENBQUMsZUFBZSxDQUFDLFVBQVUsQ0FBQyxNQUFNLEVBQUUsQ0FBQyxDQUFDLENBQUM7SUFDakQsQ0FBQyxDQUFDLENBQUM7SUFFSCxJQUFBLFVBQUUsRUFBQyw2QkFBNkIsRUFBRSxLQUFLLElBQUksRUFBRTtRQUN6QyxNQUFNLE1BQU0sQ0FBQyxJQUFJLENBQUM7WUFDZCxJQUFJLEVBQUUsSUFBSTtTQUNiLENBQUMsQ0FBQztRQUNILE1BQU0sVUFBVSxHQUFHLG9CQUFTLENBQUMsWUFBWSxFQUFFLENBQUM7UUFDNUMsZ0JBQU0sQ0FBQyxlQUFlLENBQUMsVUFBVSxDQUFDLE1BQU0sRUFBRSxDQUFDLENBQUMsQ0FBQztJQUNqRCxDQUFDLENBQUMsQ0FBQztJQUVILElBQUEsVUFBRSxFQUFDLCtDQUErQyxFQUFFLEtBQUssSUFBSSxFQUFFO1FBQzNELElBQUk7WUFDQSxNQUFNLGFBQU0sQ0FBQyxRQUFRLENBQUMsRUFBRSxDQUFDLENBQUM7WUFDMUIsZ0JBQU0sQ0FBQyxJQUFJLENBQUMscUNBQXFDLENBQUMsQ0FBQztTQUN0RDtRQUFDLE9BQU8sQ0FBTSxFQUFFO1lBQ2IsZ0JBQU0sQ0FBQyxlQUFlLENBQUMsQ0FBQyxDQUFDLE9BQU8sRUFBRSxvREFBb0QsQ0FBQyxDQUFDO1NBQzNGO0lBQ0wsQ0FBQyxDQUFDLENBQUM7SUFFSCxJQUFBLFVBQUUsRUFBQyw0REFBNEQsRUFBRSxLQUFLLElBQUksRUFBRTtRQUN4RSxhQUFNLENBQUMsWUFBWSxHQUFHLFNBQVMsQ0FBQztRQUNoQyxJQUFJO1lBQ0EsTUFBTSxhQUFNLENBQUMsUUFBUSxDQUFDLEVBQUUsQ0FBQyxDQUFDO1lBQzFCLGdCQUFNLENBQUMsSUFBSSxDQUFDLHFDQUFxQyxDQUFDLENBQUM7U0FDdEQ7UUFBQyxPQUFPLENBQU0sRUFBRTtZQUNiLGdCQUFNLENBQUMsZUFBZSxDQUFDLENBQUMsQ0FBQyxPQUFPLEVBQUUsa0VBQWtFLENBQUMsQ0FBQztTQUN6RztJQUNMLENBQUMsQ0FBQyxDQUFDO0FBQ1AsQ0FBQyxDQUFDLENBQUMifQ==
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import testUtils from '@storyous/test-utils';
|
|
2
|
+
import assert from 'assert';
|
|
3
|
+
import { describe, it } from 'mocha';
|
|
4
|
+
import mailer from '../lib';
|
|
5
|
+
|
|
6
|
+
const defaultOptions = {
|
|
7
|
+
from: 'test@storyous.cz',
|
|
8
|
+
to: 'test@storyous.cz',
|
|
9
|
+
template: 'some.template',
|
|
10
|
+
defaultLang: 'cs',
|
|
11
|
+
};
|
|
12
|
+
const sender = mailer.createSender(defaultOptions);
|
|
13
|
+
|
|
14
|
+
describe('packages/mailer', () => {
|
|
15
|
+
testUtils.init(undefined, undefined, mailer);
|
|
16
|
+
|
|
17
|
+
it('should send email', async () => {
|
|
18
|
+
await sender.send({
|
|
19
|
+
to: ['dev@storyous.cz', 'dev2@storyous.cz'],
|
|
20
|
+
bcc: ['dev@storyous.cz', 'dev2@storyous.cz'],
|
|
21
|
+
cc: ['dev@storyous.cz', 'dev2@storyous.cz'],
|
|
22
|
+
replyTo: 'dev@storyous.cz',
|
|
23
|
+
});
|
|
24
|
+
const sentEmails = testUtils.getSentMails();
|
|
25
|
+
assert.deepStrictEqual(sentEmails.length, 1);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should send email with person lang', async () => {
|
|
29
|
+
await sender.send({
|
|
30
|
+
person: { lang: 'cs' },
|
|
31
|
+
});
|
|
32
|
+
const sentEmails = testUtils.getSentMails();
|
|
33
|
+
assert.deepStrictEqual(sentEmails.length, 1);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should send email with lang', async () => {
|
|
37
|
+
await sender.send({
|
|
38
|
+
lang: 'cs',
|
|
39
|
+
});
|
|
40
|
+
const sentEmails = testUtils.getSentMails();
|
|
41
|
+
assert.deepStrictEqual(sentEmails.length, 1);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should throw exception when options are empty', async () => {
|
|
45
|
+
try {
|
|
46
|
+
await mailer.sendMail({});
|
|
47
|
+
assert.fail('The method must throw an exception.');
|
|
48
|
+
} catch (e: any) {
|
|
49
|
+
assert.deepStrictEqual(e.message, 'I need to know who this email is being sent to :-(');
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('should throw exception when transporter is not initialized', async () => {
|
|
54
|
+
mailer._transporter = undefined;
|
|
55
|
+
try {
|
|
56
|
+
await mailer.sendMail({});
|
|
57
|
+
assert.fail('The method must throw an exception.');
|
|
58
|
+
} catch (e: any) {
|
|
59
|
+
assert.deepStrictEqual(e.message, 'The transporter is not initialized yet! Use createSender before.');
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
});
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const nodemailer_1 = require("nodemailer");
|
|
4
|
+
const mailSender_1 = require("./mailSender");
|
|
5
|
+
const mailer = {
|
|
6
|
+
_transporter: undefined,
|
|
7
|
+
_logger: undefined,
|
|
8
|
+
/**
|
|
9
|
+
* RFC 2822 standard
|
|
10
|
+
* @type RegExp
|
|
11
|
+
*/
|
|
12
|
+
_emailAddressRegexp: /^([\w-]+(?:\.[\w-]+)*)@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$/,
|
|
13
|
+
createSender(options, logger) {
|
|
14
|
+
this._transporter = (0, nodemailer_1.createTransport)(options);
|
|
15
|
+
this._logger = logger;
|
|
16
|
+
return new mailSender_1.MailSender(options, this.sendMail.bind(this));
|
|
17
|
+
},
|
|
18
|
+
async sendMail(mailOptions) {
|
|
19
|
+
return new Promise((resolve, reject) => {
|
|
20
|
+
if (!this._transporter) {
|
|
21
|
+
throw new Error('The transporter is not initialized yet! Use createSender before.');
|
|
22
|
+
}
|
|
23
|
+
this._transporter.sendMail(mailOptions, (err, info) => {
|
|
24
|
+
if (err) {
|
|
25
|
+
reject(err);
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
this._logSentMail(mailOptions, info || {});
|
|
29
|
+
resolve(info);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
},
|
|
34
|
+
_logSentMail(mailOptions, resultInfo) {
|
|
35
|
+
const extractEmail = this._extractEmail.bind(this);
|
|
36
|
+
let { to } = mailOptions;
|
|
37
|
+
if (typeof mailOptions.to === 'string') {
|
|
38
|
+
to = mailOptions.to?.split(',').map(extractEmail);
|
|
39
|
+
}
|
|
40
|
+
const logMetaInfo = {
|
|
41
|
+
subject: mailOptions.subject,
|
|
42
|
+
to,
|
|
43
|
+
};
|
|
44
|
+
if (resultInfo?.accepted?.length) {
|
|
45
|
+
logMetaInfo.accepted = resultInfo.accepted.map(extractEmail);
|
|
46
|
+
}
|
|
47
|
+
if (resultInfo?.rejected?.length) {
|
|
48
|
+
logMetaInfo.rejected = resultInfo.rejected.map(extractEmail);
|
|
49
|
+
}
|
|
50
|
+
if (resultInfo?.pending?.length) {
|
|
51
|
+
logMetaInfo.pending = resultInfo.pending.map(extractEmail);
|
|
52
|
+
}
|
|
53
|
+
this._logger?.info('Mail sent', logMetaInfo);
|
|
54
|
+
},
|
|
55
|
+
_extractEmail(emailText) {
|
|
56
|
+
const result = emailText.match(this._emailAddressRegexp);
|
|
57
|
+
return result ? result[0] : '';
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
exports.default = mailer;
|
|
61
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJpbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQUFBLDJDQUEwRDtBQUcxRCw2Q0FBK0Q7QUFFL0QsTUFBTSxNQUFNLEdBQUc7SUFDWCxZQUFZLEVBQTBCLFNBQVM7SUFDL0MsT0FBTyxFQUFxQixTQUFTO0lBRXJDOzs7T0FHRztJQUNILG1CQUFtQixFQUFFLG1GQUFtRjtJQUV4RyxZQUFZLENBQUUsT0FBNEIsRUFBRSxNQUFlO1FBQ3ZELElBQUksQ0FBQyxZQUFZLEdBQUcsSUFBQSw0QkFBZSxFQUFDLE9BQU8sQ0FBQyxDQUFDO1FBQzdDLElBQUksQ0FBQyxPQUFPLEdBQUcsTUFBTSxDQUFDO1FBRXRCLE9BQU8sSUFBSSx1QkFBVSxDQUFDLE9BQU8sRUFBRSxJQUFJLENBQUMsUUFBUSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDO0lBQzdELENBQUM7SUFFRCxLQUFLLENBQUMsUUFBUSxDQUFFLFdBQW9CO1FBQ2hDLE9BQU8sSUFBSSxPQUFPLENBQUMsQ0FBQyxPQUFPLEVBQUUsTUFBTSxFQUFFLEVBQUU7WUFDbkMsSUFBSSxDQUFDLElBQUksQ0FBQyxZQUFZLEVBQUU7Z0JBQ3BCLE1BQU0sSUFBSSxLQUFLLENBQUMsa0VBQWtFLENBQUMsQ0FBQzthQUN2RjtZQUVELElBQUksQ0FBQyxZQUFZLENBQUMsUUFBUSxDQUFDLFdBQVcsRUFBRSxDQUFDLEdBQVEsRUFBRSxJQUFhLEVBQUUsRUFBRTtnQkFDaEUsSUFBSSxHQUFHLEVBQUU7b0JBQ0wsTUFBTSxDQUFDLEdBQUcsQ0FBQyxDQUFDO2lCQUNmO3FCQUFNO29CQUNILElBQUksQ0FBQyxZQUFZLENBQUMsV0FBVyxFQUFFLElBQUksSUFBSSxFQUFFLENBQUMsQ0FBQztvQkFDM0MsT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFDO2lCQUNqQjtZQUNMLENBQUMsQ0FBQyxDQUFDO1FBQ1AsQ0FBQyxDQUFDLENBQUM7SUFDUCxDQUFDO0lBRUQsWUFBWSxDQUFFLFdBQW9CLEVBQUUsVUFBZTtRQUMvQyxNQUFNLFlBQVksR0FBRyxJQUFJLENBQUMsYUFBYSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUVuRCxJQUFJLEVBQUUsRUFBRSxFQUFFLEdBQUcsV0FBVyxDQUFDO1FBQ3pCLElBQUksT0FBTyxXQUFXLENBQUMsRUFBRSxLQUFLLFFBQVEsRUFBRTtZQUNwQyxFQUFFLEdBQUcsV0FBVyxDQUFDLEVBQUUsRUFBRSxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUMsR0FBRyxDQUFDLFlBQVksQ0FBQyxDQUFDO1NBQ3JEO1FBRUQsTUFBTSxXQUFXLEdBQVE7WUFDckIsT0FBTyxFQUFFLFdBQVcsQ0FBQyxPQUFPO1lBQzVCLEVBQUU7U0FDTCxDQUFDO1FBRUYsSUFBSSxVQUFVLEVBQUUsUUFBUSxFQUFFLE1BQU0sRUFBRTtZQUM5QixXQUFXLENBQUMsUUFBUSxHQUFHLFVBQVUsQ0FBQyxRQUFRLENBQUMsR0FBRyxDQUFDLFlBQVksQ0FBQyxDQUFDO1NBQ2hFO1FBRUQsSUFBSSxVQUFVLEVBQUUsUUFBUSxFQUFFLE1BQU0sRUFBRTtZQUM5QixXQUFXLENBQUMsUUFBUSxHQUFHLFVBQVUsQ0FBQyxRQUFRLENBQUMsR0FBRyxDQUFDLFlBQVksQ0FBQyxDQUFDO1NBQ2hFO1FBRUQsSUFBSSxVQUFVLEVBQUUsT0FBTyxFQUFFLE1BQU0sRUFBRTtZQUM3QixXQUFXLENBQUMsT0FBTyxHQUFHLFVBQVUsQ0FBQyxPQUFPLENBQUMsR0FBRyxDQUFDLFlBQVksQ0FBQyxDQUFDO1NBQzlEO1FBRUQsSUFBSSxDQUFDLE9BQU8sRUFBRSxJQUFJLENBQUMsV0FBVyxFQUFFLFdBQVcsQ0FBQyxDQUFDO0lBQ2pELENBQUM7SUFFRCxhQUFhLENBQUUsU0FBaUI7UUFDNUIsTUFBTSxNQUFNLEdBQUcsU0FBUyxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsbUJBQW1CLENBQUMsQ0FBQztRQUN6RCxPQUFPLE1BQU0sQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUM7SUFDbkMsQ0FBQztDQUVKLENBQUM7QUFFRixrQkFBZSxNQUFNLENBQUMifQ==
|
package/lib/index.ts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { createTransport, Transporter } from 'nodemailer';
|
|
2
|
+
import { Options } from 'nodemailer/lib/mailer';
|
|
3
|
+
import { Logger } from 'winston';
|
|
4
|
+
import { IDefaultMailOptions, MailSender } from './mailSender';
|
|
5
|
+
|
|
6
|
+
const mailer = {
|
|
7
|
+
_transporter: <Transporter|undefined> undefined,
|
|
8
|
+
_logger: <Logger|undefined> undefined,
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* RFC 2822 standard
|
|
12
|
+
* @type RegExp
|
|
13
|
+
*/
|
|
14
|
+
_emailAddressRegexp: /^([\w-]+(?:\.[\w-]+)*)@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$/,
|
|
15
|
+
|
|
16
|
+
createSender (options: IDefaultMailOptions, logger?: Logger) {
|
|
17
|
+
this._transporter = createTransport(options);
|
|
18
|
+
this._logger = logger;
|
|
19
|
+
|
|
20
|
+
return new MailSender(options, this.sendMail.bind(this));
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
async sendMail (mailOptions: Options): Promise<any> {
|
|
24
|
+
return new Promise((resolve, reject) => {
|
|
25
|
+
if (!this._transporter) {
|
|
26
|
+
throw new Error('The transporter is not initialized yet! Use createSender before.');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
this._transporter.sendMail(mailOptions, (err: any, info: unknown) => {
|
|
30
|
+
if (err) {
|
|
31
|
+
reject(err);
|
|
32
|
+
} else {
|
|
33
|
+
this._logSentMail(mailOptions, info || {});
|
|
34
|
+
resolve(info);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
_logSentMail (mailOptions: Options, resultInfo: any) {
|
|
41
|
+
const extractEmail = this._extractEmail.bind(this);
|
|
42
|
+
|
|
43
|
+
let { to } = mailOptions;
|
|
44
|
+
if (typeof mailOptions.to === 'string') {
|
|
45
|
+
to = mailOptions.to?.split(',').map(extractEmail);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const logMetaInfo: any = {
|
|
49
|
+
subject: mailOptions.subject,
|
|
50
|
+
to,
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
if (resultInfo?.accepted?.length) {
|
|
54
|
+
logMetaInfo.accepted = resultInfo.accepted.map(extractEmail);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (resultInfo?.rejected?.length) {
|
|
58
|
+
logMetaInfo.rejected = resultInfo.rejected.map(extractEmail);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (resultInfo?.pending?.length) {
|
|
62
|
+
logMetaInfo.pending = resultInfo.pending.map(extractEmail);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
this._logger?.info('Mail sent', logMetaInfo);
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
_extractEmail (emailText: string) {
|
|
69
|
+
const result = emailText.match(this._emailAddressRegexp);
|
|
70
|
+
return result ? result[0] : '';
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export default mailer;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.MailSender = void 0;
|
|
7
|
+
const errors_1 = require("@storyous/errors");
|
|
8
|
+
const handlebars_1 = __importDefault(require("handlebars"));
|
|
9
|
+
const lodash_1 = require("lodash");
|
|
10
|
+
/**
|
|
11
|
+
* Provides shortcut for sending email
|
|
12
|
+
*/
|
|
13
|
+
class MailSender {
|
|
14
|
+
constructor(options, sendFunction) {
|
|
15
|
+
this._getTemplate = (params) => {
|
|
16
|
+
const compiledTemplate = handlebars_1.default.compile(params.template)(params);
|
|
17
|
+
const separatorRegexp = /<!--\s*\/([a-zA-Z0-9]+)\s*-->/;
|
|
18
|
+
const templatePieces = compiledTemplate.split(separatorRegexp);
|
|
19
|
+
templatePieces.shift(); // remove first blank string
|
|
20
|
+
if (templatePieces.length % 2 !== 0) {
|
|
21
|
+
throw errors_1.AppError.internal('The template is not valid.');
|
|
22
|
+
}
|
|
23
|
+
const template = {};
|
|
24
|
+
for (let i = 0; i < templatePieces.length; i += 2) {
|
|
25
|
+
template[templatePieces[i]] = templatePieces[i + 1];
|
|
26
|
+
}
|
|
27
|
+
if (template.subject) {
|
|
28
|
+
template.subject = template.subject.trim();
|
|
29
|
+
}
|
|
30
|
+
return template;
|
|
31
|
+
};
|
|
32
|
+
this._sendFunction = sendFunction;
|
|
33
|
+
this._sendTo = options.to;
|
|
34
|
+
this._sendFrom = options.from;
|
|
35
|
+
this._template = options.template;
|
|
36
|
+
this._defaultLang = options.defaultLang;
|
|
37
|
+
}
|
|
38
|
+
async send(params) {
|
|
39
|
+
params = this._completeParams(params);
|
|
40
|
+
const template = this._getTemplate(params);
|
|
41
|
+
const mailOptions = {
|
|
42
|
+
html: template.html,
|
|
43
|
+
text: template.text,
|
|
44
|
+
subject: template.subject,
|
|
45
|
+
to: params.to,
|
|
46
|
+
from: this._sendFrom,
|
|
47
|
+
replyTo: params.replyTo,
|
|
48
|
+
bcc: params.bcc,
|
|
49
|
+
cc: params.cc,
|
|
50
|
+
};
|
|
51
|
+
if (Array.isArray(mailOptions.to)) {
|
|
52
|
+
mailOptions.to = mailOptions.to.join(',');
|
|
53
|
+
}
|
|
54
|
+
if (Array.isArray(mailOptions.bcc)) {
|
|
55
|
+
mailOptions.bcc = mailOptions.bcc.join(',');
|
|
56
|
+
}
|
|
57
|
+
if (Array.isArray(mailOptions.cc)) {
|
|
58
|
+
mailOptions.cc = mailOptions.cc.join(',');
|
|
59
|
+
}
|
|
60
|
+
return this._sendFunction(mailOptions);
|
|
61
|
+
}
|
|
62
|
+
_completeParams(params) {
|
|
63
|
+
return (0, lodash_1.extend)({}, params, {
|
|
64
|
+
lang: this._getLanguageFromParams(params),
|
|
65
|
+
to: params.to || this._sendTo,
|
|
66
|
+
from: params.from || this._sendFrom,
|
|
67
|
+
template: params.template || this._template,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
_getLanguageFromParams(params) {
|
|
71
|
+
if (params.lang) {
|
|
72
|
+
return params.lang;
|
|
73
|
+
}
|
|
74
|
+
if (params?.person?.lang) {
|
|
75
|
+
return params.person.lang;
|
|
76
|
+
}
|
|
77
|
+
return this._defaultLang;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
exports.MailSender = MailSender;
|
|
81
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWFpbFNlbmRlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIm1haWxTZW5kZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7O0FBQUEsNkNBQTRDO0FBQzVDLDREQUFvQztBQUNwQyxtQ0FBZ0M7QUF1QmhDOztHQUVHO0FBQ0gsTUFBYSxVQUFVO0lBWW5CLFlBQWEsT0FBNEIsRUFBRSxZQUFpQjtRQTREcEQsaUJBQVksR0FBRyxDQUFDLE1BQXdCLEVBQUUsRUFBRTtZQUVoRCxNQUFNLGdCQUFnQixHQUFHLG9CQUFVLENBQUMsT0FBTyxDQUFDLE1BQU0sQ0FBQyxRQUFRLENBQUMsQ0FBQyxNQUFNLENBQUMsQ0FBQztZQUVyRSxNQUFNLGVBQWUsR0FBRywrQkFBK0IsQ0FBQztZQUN4RCxNQUFNLGNBQWMsR0FBRyxnQkFBZ0IsQ0FBQyxLQUFLLENBQUMsZUFBZSxDQUFDLENBQUM7WUFFL0QsY0FBYyxDQUFDLEtBQUssRUFBRSxDQUFDLENBQUMsNEJBQTRCO1lBRXBELElBQUksY0FBYyxDQUFDLE1BQU0sR0FBRyxDQUFDLEtBQUssQ0FBQyxFQUFFO2dCQUNqQyxNQUFNLGlCQUFRLENBQUMsUUFBUSxDQUFDLDRCQUE0QixDQUFDLENBQUM7YUFDekQ7WUFFRCxNQUFNLFFBQVEsR0FBUSxFQUFFLENBQUM7WUFDekIsS0FBSyxJQUFJLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQyxHQUFHLGNBQWMsQ0FBQyxNQUFNLEVBQUUsQ0FBQyxJQUFJLENBQUMsRUFBRTtnQkFDL0MsUUFBUSxDQUFDLGNBQWMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxHQUFHLGNBQWMsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUM7YUFDdkQ7WUFFRCxJQUFJLFFBQVEsQ0FBQyxPQUFPLEVBQUU7Z0JBQ2xCLFFBQVEsQ0FBQyxPQUFPLEdBQUcsUUFBUSxDQUFDLE9BQU8sQ0FBQyxJQUFJLEVBQUUsQ0FBQzthQUM5QztZQUVELE9BQU8sUUFBUSxDQUFDO1FBQ3BCLENBQUMs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
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { AppError } from '@storyous/errors';
|
|
2
|
+
import handlebars from 'handlebars';
|
|
3
|
+
import { extend } from 'lodash';
|
|
4
|
+
import { Address, Options } from 'nodemailer/lib/mailer';
|
|
5
|
+
|
|
6
|
+
export interface ISendMailOptions {
|
|
7
|
+
bcc?: string | string[],
|
|
8
|
+
cc?: string | string[],
|
|
9
|
+
from?: string,
|
|
10
|
+
lang?: string,
|
|
11
|
+
replyTo?: string | Address,
|
|
12
|
+
template?: string,
|
|
13
|
+
to?: string | string[],
|
|
14
|
+
person?: {
|
|
15
|
+
lang?: string
|
|
16
|
+
},
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface IDefaultMailOptions {
|
|
20
|
+
from: string,
|
|
21
|
+
to: string|string[],
|
|
22
|
+
template: string,
|
|
23
|
+
defaultLang: string
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Provides shortcut for sending email
|
|
28
|
+
*/
|
|
29
|
+
export class MailSender {
|
|
30
|
+
|
|
31
|
+
private readonly _sendFunction: any;
|
|
32
|
+
|
|
33
|
+
private readonly _sendTo: string|string[];
|
|
34
|
+
|
|
35
|
+
private readonly _sendFrom: string;
|
|
36
|
+
|
|
37
|
+
private readonly _template: string;
|
|
38
|
+
|
|
39
|
+
private readonly _defaultLang: string;
|
|
40
|
+
|
|
41
|
+
constructor (options: IDefaultMailOptions, sendFunction: any) {
|
|
42
|
+
this._sendFunction = sendFunction;
|
|
43
|
+
this._sendTo = options.to;
|
|
44
|
+
this._sendFrom = options.from;
|
|
45
|
+
this._template = options.template;
|
|
46
|
+
this._defaultLang = options.defaultLang;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async send (params: ISendMailOptions) {
|
|
50
|
+
params = this._completeParams(params);
|
|
51
|
+
const template = this._getTemplate(params);
|
|
52
|
+
|
|
53
|
+
const mailOptions: Options = {
|
|
54
|
+
html: template.html,
|
|
55
|
+
text: template.text,
|
|
56
|
+
subject: template.subject,
|
|
57
|
+
to: params.to,
|
|
58
|
+
from: this._sendFrom,
|
|
59
|
+
replyTo: params.replyTo,
|
|
60
|
+
bcc: params.bcc,
|
|
61
|
+
cc: params.cc,
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
if (Array.isArray(mailOptions.to)) {
|
|
65
|
+
mailOptions.to = mailOptions.to.join(',');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (Array.isArray(mailOptions.bcc)) {
|
|
69
|
+
mailOptions.bcc = mailOptions.bcc.join(',');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (Array.isArray(mailOptions.cc)) {
|
|
73
|
+
mailOptions.cc = mailOptions.cc.join(',');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return this._sendFunction(mailOptions);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
private _completeParams (params: ISendMailOptions) {
|
|
80
|
+
return extend({}, params, {
|
|
81
|
+
lang: this._getLanguageFromParams(params),
|
|
82
|
+
to: params.to || this._sendTo,
|
|
83
|
+
from: params.from || this._sendFrom,
|
|
84
|
+
template: params.template || this._template,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
private _getLanguageFromParams (params: ISendMailOptions): string {
|
|
89
|
+
|
|
90
|
+
if (params.lang) {
|
|
91
|
+
return params.lang;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (params?.person?.lang) {
|
|
95
|
+
return params.person.lang;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return this._defaultLang;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
private _getTemplate = (params: ISendMailOptions) => {
|
|
102
|
+
|
|
103
|
+
const compiledTemplate = handlebars.compile(params.template)(params);
|
|
104
|
+
|
|
105
|
+
const separatorRegexp = /<!--\s*\/([a-zA-Z0-9]+)\s*-->/;
|
|
106
|
+
const templatePieces = compiledTemplate.split(separatorRegexp);
|
|
107
|
+
|
|
108
|
+
templatePieces.shift(); // remove first blank string
|
|
109
|
+
|
|
110
|
+
if (templatePieces.length % 2 !== 0) {
|
|
111
|
+
throw AppError.internal('The template is not valid.');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const template: any = {};
|
|
115
|
+
for (let i = 0; i < templatePieces.length; i += 2) {
|
|
116
|
+
template[templatePieces[i]] = templatePieces[i + 1];
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (template.subject) {
|
|
120
|
+
template.subject = template.subject.trim();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return template;
|
|
124
|
+
};
|
|
125
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@storyous/mailer",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"homepage": "https://github.com/Storyous/mono-utils#readme",
|
|
6
|
+
"license": "ISC",
|
|
7
|
+
"main": "lib/index.ts",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"start": "tsc",
|
|
10
|
+
"build": "tsc && npm publish"
|
|
11
|
+
},
|
|
12
|
+
"mocha": {
|
|
13
|
+
"recursive": true,
|
|
14
|
+
"timeout": 15000,
|
|
15
|
+
"extension": [
|
|
16
|
+
"ts"
|
|
17
|
+
],
|
|
18
|
+
"exit": true,
|
|
19
|
+
"require": "ts-node/register"
|
|
20
|
+
},
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "git+https://github.com/Storyous/mono-utils.git"
|
|
24
|
+
},
|
|
25
|
+
"bugs": {
|
|
26
|
+
"url": "https://github.com/Storyous/mono-utils/issues"
|
|
27
|
+
},
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"access": "public",
|
|
30
|
+
"registry": "https://registry.npmjs.org/"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"@storyous/errors": "^1.0.0",
|
|
34
|
+
"handlebars": "^4.7.7",
|
|
35
|
+
"lodash": "^4.17.21",
|
|
36
|
+
"nodemailer": "^6.7.3",
|
|
37
|
+
"winston": "^3.7.2"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@storyous/test-utils": "^1.0.0"
|
|
41
|
+
},
|
|
42
|
+
"gitHead": "2ba0209d33ac7cd2fd1136a9123134f656750102"
|
|
43
|
+
}
|