@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 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
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+
4
+ "include": [
5
+ "lib",
6
+ "__tests__"
7
+ ]
8
+ }