@flusys/nestjs-email 1.1.0-beta → 1.1.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 +604 -0
- package/cjs/config/email.constants.js +0 -18
- package/cjs/config/index.js +0 -1
- package/cjs/controllers/email-config.controller.js +46 -4
- package/cjs/controllers/email-send.controller.js +13 -26
- package/cjs/controllers/email-template.controller.js +60 -11
- package/cjs/docs/email-swagger.config.js +18 -80
- package/cjs/dtos/email-config.dto.js +6 -106
- package/cjs/dtos/email-send.dto.js +101 -123
- package/cjs/dtos/email-template.dto.js +41 -103
- package/cjs/entities/email-config-with-company.entity.js +2 -2
- package/cjs/entities/email-config.entity.js +91 -3
- package/cjs/entities/email-template-with-company.entity.js +5 -3
- package/cjs/entities/email-template.entity.js +119 -3
- package/cjs/entities/index.js +34 -19
- package/cjs/index.js +1 -0
- package/cjs/interfaces/email-provider.interface.js +1 -3
- package/cjs/modules/email.module.js +50 -104
- package/cjs/providers/email-factory.service.js +37 -109
- package/cjs/providers/email-provider.registry.js +5 -15
- package/cjs/providers/mailgun-provider.js +54 -58
- package/cjs/providers/sendgrid-provider.js +68 -92
- package/cjs/providers/smtp-provider.js +58 -69
- package/cjs/{config → services}/email-config.service.js +9 -32
- package/cjs/services/email-datasource.provider.js +17 -104
- package/cjs/services/email-provider-config.service.js +28 -58
- package/cjs/services/email-send.service.js +120 -125
- package/cjs/services/email-template.service.js +62 -85
- package/cjs/services/index.js +2 -1
- package/cjs/utils/email-templates.util.js +64 -0
- package/cjs/utils/index.js +18 -0
- package/config/email.constants.d.ts +0 -9
- package/config/index.d.ts +0 -1
- package/controllers/email-send.controller.d.ts +5 -12
- package/controllers/email-template.controller.d.ts +5 -7
- package/dtos/email-config.dto.d.ts +5 -13
- package/dtos/email-send.dto.d.ts +17 -21
- package/dtos/email-template.dto.d.ts +5 -16
- package/entities/email-config-with-company.entity.d.ts +2 -2
- package/entities/email-config.entity.d.ts +9 -2
- package/entities/email-template-with-company.entity.d.ts +2 -2
- package/entities/email-template.entity.d.ts +13 -2
- package/entities/index.d.ts +9 -3
- package/fesm/config/email.constants.js +0 -9
- package/fesm/config/index.js +0 -1
- package/fesm/controllers/email-config.controller.js +49 -7
- package/fesm/controllers/email-send.controller.js +13 -26
- package/fesm/controllers/email-template.controller.js +61 -12
- package/fesm/docs/email-swagger.config.js +21 -86
- package/fesm/dtos/email-config.dto.js +9 -115
- package/fesm/dtos/email-send.dto.js +103 -139
- package/fesm/dtos/email-template.dto.js +43 -111
- package/fesm/entities/email-config-with-company.entity.js +2 -2
- package/fesm/entities/email-config.entity.js +92 -4
- package/fesm/entities/email-template-with-company.entity.js +5 -3
- package/fesm/entities/email-template.entity.js +120 -4
- package/fesm/entities/index.js +22 -16
- package/fesm/index.js +1 -0
- package/fesm/interfaces/email-config.interface.js +1 -3
- package/fesm/interfaces/email-module-options.interface.js +1 -3
- package/fesm/interfaces/email-provider.interface.js +1 -5
- package/fesm/interfaces/email-template.interface.js +1 -3
- package/fesm/modules/email.module.js +52 -106
- package/fesm/providers/email-factory.service.js +38 -69
- package/fesm/providers/email-provider.registry.js +6 -19
- package/fesm/providers/mailgun-provider.js +55 -63
- package/fesm/providers/sendgrid-provider.js +69 -97
- package/fesm/providers/smtp-provider.js +59 -73
- package/fesm/{config → services}/email-config.service.js +9 -32
- package/fesm/services/email-datasource.provider.js +18 -64
- package/fesm/services/email-provider-config.service.js +26 -56
- package/fesm/services/email-send.service.js +118 -123
- package/fesm/services/email-template.service.js +60 -83
- package/fesm/services/index.js +2 -1
- package/fesm/utils/email-templates.util.js +47 -0
- package/fesm/utils/index.js +1 -0
- package/index.d.ts +1 -0
- package/interfaces/email-config.interface.d.ts +6 -0
- package/interfaces/email-module-options.interface.d.ts +0 -5
- package/modules/email.module.d.ts +1 -2
- package/package.json +9 -4
- package/providers/email-factory.service.d.ts +4 -7
- package/providers/mailgun-provider.d.ts +6 -2
- package/providers/sendgrid-provider.d.ts +6 -2
- package/providers/smtp-provider.d.ts +7 -2
- package/services/email-config.service.d.ts +12 -0
- package/services/email-datasource.provider.d.ts +3 -6
- package/services/email-provider-config.service.d.ts +3 -3
- package/services/email-send.service.d.ts +11 -3
- package/services/email-template.service.d.ts +5 -4
- package/services/index.d.ts +2 -1
- package/utils/email-templates.util.d.ts +2 -0
- package/utils/index.d.ts +1 -0
- package/cjs/entities/email-config-base.entity.js +0 -111
- package/cjs/entities/email-template-base.entity.js +0 -134
- package/config/email-config.service.d.ts +0 -13
- package/entities/email-config-base.entity.d.ts +0 -11
- package/entities/email-template-base.entity.d.ts +0 -14
- package/fesm/entities/email-config-base.entity.js +0 -101
- package/fesm/entities/email-template-base.entity.js +0 -124
|
@@ -22,29 +22,19 @@ function _define_property(obj, key, value) {
|
|
|
22
22
|
return obj;
|
|
23
23
|
}
|
|
24
24
|
let EmailProviderRegistry = class EmailProviderRegistry {
|
|
25
|
-
|
|
26
|
-
* Register an email provider
|
|
27
|
-
*/ static register(providerName, providerClass) {
|
|
25
|
+
static register(providerName, providerClass) {
|
|
28
26
|
this.providers.set(providerName.toLowerCase(), providerClass);
|
|
29
27
|
}
|
|
30
|
-
|
|
31
|
-
* Get a registered provider class
|
|
32
|
-
*/ static get(providerName) {
|
|
28
|
+
static get(providerName) {
|
|
33
29
|
return this.providers.get(providerName.toLowerCase());
|
|
34
30
|
}
|
|
35
|
-
|
|
36
|
-
* Check if a provider is registered
|
|
37
|
-
*/ static has(providerName) {
|
|
31
|
+
static has(providerName) {
|
|
38
32
|
return this.providers.has(providerName.toLowerCase());
|
|
39
33
|
}
|
|
40
|
-
|
|
41
|
-
* Get all registered provider names
|
|
42
|
-
*/ static getAll() {
|
|
34
|
+
static getAll() {
|
|
43
35
|
return Array.from(this.providers.keys());
|
|
44
36
|
}
|
|
45
|
-
|
|
46
|
-
* Clear all providers (useful for testing)
|
|
47
|
-
*/ static clear() {
|
|
37
|
+
static clear() {
|
|
48
38
|
this.providers.clear();
|
|
49
39
|
}
|
|
50
40
|
};
|
|
@@ -22,19 +22,17 @@ function _define_property(obj, key, value) {
|
|
|
22
22
|
}
|
|
23
23
|
return obj;
|
|
24
24
|
}
|
|
25
|
+
const MAILGUN_API_URL = 'https://api.mailgun.net';
|
|
26
|
+
const MAILGUN_EU_API_URL = 'https://api.eu.mailgun.net';
|
|
25
27
|
let MailgunProvider = class MailgunProvider {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
*/ async initialize(config) {
|
|
29
|
-
this.config = config;
|
|
28
|
+
async initialize(config) {
|
|
29
|
+
this.domain = config.domain;
|
|
30
30
|
try {
|
|
31
|
-
// Dynamic import
|
|
32
|
-
// eslint-disable-next-line @typescript-eslint/no-implied-eval
|
|
31
|
+
// Dynamic import for optional dependency
|
|
33
32
|
const Mailgun = (await new Function('return import("mailgun.js")')()).default;
|
|
34
33
|
const FormData = (await new Function('return import("form-data")')()).default;
|
|
35
34
|
const mailgun = new Mailgun(FormData);
|
|
36
|
-
|
|
37
|
-
const url = config.region === 'eu' ? 'https://api.eu.mailgun.net' : 'https://api.mailgun.net';
|
|
35
|
+
const url = config.region === 'eu' ? MAILGUN_EU_API_URL : MAILGUN_API_URL;
|
|
38
36
|
this.client = mailgun.client({
|
|
39
37
|
username: 'api',
|
|
40
38
|
key: config.apiKey,
|
|
@@ -42,84 +40,82 @@ let MailgunProvider = class MailgunProvider {
|
|
|
42
40
|
});
|
|
43
41
|
this.logger.log(`Mailgun Provider initialized for domain: ${config.domain}`);
|
|
44
42
|
} catch (error) {
|
|
45
|
-
this.logger.error('Failed to initialize Mailgun:', error
|
|
46
|
-
throw new Error('Mailgun initialization failed.
|
|
43
|
+
this.logger.error('Failed to initialize Mailgun:', this.extractError(error));
|
|
44
|
+
throw new Error('Mailgun initialization failed. Install: npm install mailgun.js form-data');
|
|
47
45
|
}
|
|
48
46
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
*/ async sendEmail(options) {
|
|
52
|
-
if (!this.client || !this.config) {
|
|
47
|
+
async sendEmail(options) {
|
|
48
|
+
if (!this.client || !this.domain) {
|
|
53
49
|
return {
|
|
54
50
|
success: false,
|
|
55
51
|
error: 'Mailgun provider not initialized'
|
|
56
52
|
};
|
|
57
53
|
}
|
|
58
54
|
try {
|
|
59
|
-
const
|
|
60
|
-
const
|
|
61
|
-
const bcc = options.bcc ? Array.isArray(options.bcc) ? options.bcc.join(', ') : options.bcc : undefined;
|
|
62
|
-
const messageData = {
|
|
63
|
-
from: options.fromName ? `${options.fromName} <${options.from}>` : options.from,
|
|
64
|
-
to,
|
|
65
|
-
subject: options.subject
|
|
66
|
-
};
|
|
67
|
-
// Only include html/text if they have values (required for plain text templates)
|
|
68
|
-
if (options.html) messageData.html = options.html;
|
|
69
|
-
if (options.text) messageData.text = options.text;
|
|
70
|
-
// Optional fields
|
|
71
|
-
if (cc) messageData.cc = cc;
|
|
72
|
-
if (bcc) messageData.bcc = bcc;
|
|
73
|
-
if (options.replyTo) messageData['h:Reply-To'] = options.replyTo;
|
|
74
|
-
// Handle attachments
|
|
75
|
-
if (options.attachments && options.attachments.length > 0) {
|
|
76
|
-
messageData.attachment = options.attachments.map((a)=>({
|
|
77
|
-
filename: a.filename,
|
|
78
|
-
data: typeof a.content === 'string' ? Buffer.from(a.content, 'base64') : a.content,
|
|
79
|
-
contentType: a.contentType
|
|
80
|
-
}));
|
|
81
|
-
}
|
|
82
|
-
const result = await this.client.messages.create(this.config.domain, messageData);
|
|
55
|
+
const messageData = this.buildMessageData(options);
|
|
56
|
+
const result = await this.client.messages.create(this.domain, messageData);
|
|
83
57
|
this.logger.log(`Email sent via Mailgun: ${result.id}`);
|
|
84
58
|
return {
|
|
85
59
|
success: true,
|
|
86
60
|
messageId: result.id
|
|
87
61
|
};
|
|
88
62
|
} catch (error) {
|
|
89
|
-
this.
|
|
90
|
-
return {
|
|
91
|
-
success: false,
|
|
92
|
-
error: error.message
|
|
93
|
-
};
|
|
63
|
+
return this.handleError('Mailgun send failed', error);
|
|
94
64
|
}
|
|
95
65
|
}
|
|
96
|
-
|
|
97
|
-
* Send multiple emails (batch)
|
|
98
|
-
*/ async sendBulkEmails(options) {
|
|
99
|
-
// Mailgun supports batch sending with recipient variables
|
|
100
|
-
// For simplicity, we send individually here
|
|
66
|
+
async sendBulkEmails(options) {
|
|
101
67
|
return Promise.all(options.map((opt)=>this.sendEmail(opt)));
|
|
102
68
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
*/ async healthCheck() {
|
|
106
|
-
if (!this.client || !this.config) return false;
|
|
69
|
+
async healthCheck() {
|
|
70
|
+
if (!this.client || !this.domain) return false;
|
|
107
71
|
try {
|
|
108
|
-
|
|
109
|
-
await this.client.domains.get(this.config.domain);
|
|
72
|
+
await this.client.domains.get(this.domain);
|
|
110
73
|
return true;
|
|
111
74
|
} catch {
|
|
112
75
|
return false;
|
|
113
76
|
}
|
|
114
77
|
}
|
|
115
|
-
|
|
116
|
-
* Cleanup
|
|
117
|
-
*/ async close() {
|
|
78
|
+
async close() {
|
|
118
79
|
this.client = null;
|
|
119
80
|
}
|
|
81
|
+
// ─── Private Helpers ────────────────────────────────────────────────────────
|
|
82
|
+
buildMessageData(options) {
|
|
83
|
+
const data = {
|
|
84
|
+
from: options.fromName ? `${options.fromName} <${options.from}>` : options.from,
|
|
85
|
+
to: this.joinAddresses(options.to),
|
|
86
|
+
subject: options.subject
|
|
87
|
+
};
|
|
88
|
+
if (options.html) data.html = options.html;
|
|
89
|
+
if (options.text) data.text = options.text;
|
|
90
|
+
if (options.cc) data.cc = this.joinAddresses(options.cc);
|
|
91
|
+
if (options.bcc) data.bcc = this.joinAddresses(options.bcc);
|
|
92
|
+
if (options.replyTo) data['h:Reply-To'] = options.replyTo;
|
|
93
|
+
if (options.attachments?.length) {
|
|
94
|
+
data.attachment = options.attachments.map((a)=>({
|
|
95
|
+
filename: a.filename,
|
|
96
|
+
data: typeof a.content === 'string' ? Buffer.from(a.content, 'base64') : a.content,
|
|
97
|
+
contentType: a.contentType
|
|
98
|
+
}));
|
|
99
|
+
}
|
|
100
|
+
return data;
|
|
101
|
+
}
|
|
102
|
+
joinAddresses(addresses) {
|
|
103
|
+
return Array.isArray(addresses) ? addresses.join(', ') : addresses;
|
|
104
|
+
}
|
|
105
|
+
extractError(error) {
|
|
106
|
+
return error instanceof Error ? error.message : 'Unknown error';
|
|
107
|
+
}
|
|
108
|
+
handleError(context, error) {
|
|
109
|
+
const message = this.extractError(error);
|
|
110
|
+
this.logger.error(`${context}: ${message}`, error);
|
|
111
|
+
return {
|
|
112
|
+
success: false,
|
|
113
|
+
error: message
|
|
114
|
+
};
|
|
115
|
+
}
|
|
120
116
|
constructor(){
|
|
121
117
|
_define_property(this, "logger", new _common.Logger(MailgunProvider.name));
|
|
122
118
|
_define_property(this, "client", null);
|
|
123
|
-
_define_property(this, "
|
|
119
|
+
_define_property(this, "domain", null);
|
|
124
120
|
}
|
|
125
121
|
};
|
|
@@ -23,65 +23,26 @@ function _define_property(obj, key, value) {
|
|
|
23
23
|
return obj;
|
|
24
24
|
}
|
|
25
25
|
let SendGridProvider = class SendGridProvider {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
*/ async initialize(config) {
|
|
29
|
-
this.config = config;
|
|
26
|
+
async initialize(config) {
|
|
27
|
+
this.apiKey = config.apiKey;
|
|
30
28
|
try {
|
|
31
|
-
// Dynamic import
|
|
32
|
-
// eslint-disable-next-line @typescript-eslint/no-implied-eval
|
|
29
|
+
// Dynamic import to avoid bundling issues
|
|
33
30
|
const sgMailModule = await new Function('return import("@sendgrid/mail")')();
|
|
34
31
|
this.sgMail = sgMailModule.default || sgMailModule;
|
|
35
32
|
this.sgMail.setApiKey(config.apiKey);
|
|
36
33
|
this.logger.log('SendGrid Provider initialized');
|
|
37
34
|
} catch (error) {
|
|
38
|
-
this.logger.error('Failed to initialize SendGrid:', error
|
|
39
|
-
throw new Error('SendGrid initialization failed.
|
|
35
|
+
this.logger.error('Failed to initialize SendGrid:', this.extractError(error));
|
|
36
|
+
throw new Error('SendGrid initialization failed. Install: npm install @sendgrid/mail');
|
|
40
37
|
}
|
|
41
38
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
success: false,
|
|
48
|
-
error: 'SendGrid provider not initialized'
|
|
49
|
-
};
|
|
50
|
-
}
|
|
39
|
+
async sendEmail(options) {
|
|
40
|
+
if (!this.sgMail) return {
|
|
41
|
+
success: false,
|
|
42
|
+
error: 'SendGrid provider not initialized'
|
|
43
|
+
};
|
|
51
44
|
try {
|
|
52
|
-
const msg =
|
|
53
|
-
to: Array.isArray(options.to) ? options.to : [
|
|
54
|
-
options.to
|
|
55
|
-
],
|
|
56
|
-
from: options.fromName ? {
|
|
57
|
-
email: options.from,
|
|
58
|
-
name: options.fromName
|
|
59
|
-
} : options.from,
|
|
60
|
-
subject: options.subject
|
|
61
|
-
};
|
|
62
|
-
// Only include html/text if they have values (required for plain text templates)
|
|
63
|
-
if (options.html) msg.html = options.html;
|
|
64
|
-
if (options.text) msg.text = options.text;
|
|
65
|
-
// Optional fields
|
|
66
|
-
if (options.cc) {
|
|
67
|
-
msg.cc = Array.isArray(options.cc) ? options.cc : [
|
|
68
|
-
options.cc
|
|
69
|
-
];
|
|
70
|
-
}
|
|
71
|
-
if (options.bcc) {
|
|
72
|
-
msg.bcc = Array.isArray(options.bcc) ? options.bcc : [
|
|
73
|
-
options.bcc
|
|
74
|
-
];
|
|
75
|
-
}
|
|
76
|
-
if (options.replyTo) msg.replyTo = options.replyTo;
|
|
77
|
-
if (options.attachments?.length) {
|
|
78
|
-
msg.attachments = options.attachments.map((a)=>({
|
|
79
|
-
filename: a.filename,
|
|
80
|
-
content: typeof a.content === 'string' ? a.content : a.content.toString('base64'),
|
|
81
|
-
type: a.contentType,
|
|
82
|
-
disposition: 'attachment'
|
|
83
|
-
}));
|
|
84
|
-
}
|
|
45
|
+
const msg = this.buildMessage(options, true);
|
|
85
46
|
const [response] = await this.sgMail.send(msg);
|
|
86
47
|
const messageId = response.headers['x-message-id'] || response.headers['X-Message-Id'];
|
|
87
48
|
this.logger.log(`Email sent via SendGrid: ${messageId}`);
|
|
@@ -90,67 +51,82 @@ let SendGridProvider = class SendGridProvider {
|
|
|
90
51
|
messageId
|
|
91
52
|
};
|
|
92
53
|
} catch (error) {
|
|
93
|
-
this.
|
|
94
|
-
const errorMessage = error.response?.body?.errors?.[0]?.message || error.message;
|
|
95
|
-
return {
|
|
96
|
-
success: false,
|
|
97
|
-
error: errorMessage
|
|
98
|
-
};
|
|
54
|
+
return this.handleError('SendGrid send failed', error);
|
|
99
55
|
}
|
|
100
56
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
success: false,
|
|
107
|
-
error: 'SendGrid provider not initialized'
|
|
108
|
-
}));
|
|
109
|
-
}
|
|
110
|
-
// SendGrid supports batch sending with sendMultiple
|
|
111
|
-
const messages = options.map((opt)=>{
|
|
112
|
-
const msg = {
|
|
113
|
-
to: Array.isArray(opt.to) ? opt.to : [
|
|
114
|
-
opt.to
|
|
115
|
-
],
|
|
116
|
-
from: opt.fromName ? {
|
|
117
|
-
email: opt.from,
|
|
118
|
-
name: opt.fromName
|
|
119
|
-
} : opt.from,
|
|
120
|
-
subject: opt.subject
|
|
121
|
-
};
|
|
122
|
-
if (opt.html) msg.html = opt.html;
|
|
123
|
-
if (opt.text) msg.text = opt.text;
|
|
124
|
-
return msg;
|
|
125
|
-
});
|
|
57
|
+
async sendBulkEmails(options) {
|
|
58
|
+
if (!this.sgMail) return options.map(()=>({
|
|
59
|
+
success: false,
|
|
60
|
+
error: 'SendGrid provider not initialized'
|
|
61
|
+
}));
|
|
126
62
|
try {
|
|
63
|
+
const messages = options.map((opt)=>this.buildMessage(opt, false));
|
|
127
64
|
await this.sgMail.send(messages);
|
|
128
65
|
return options.map(()=>({
|
|
129
66
|
success: true
|
|
130
67
|
}));
|
|
131
68
|
} catch (error) {
|
|
132
69
|
this.logger.error('SendGrid bulk send failed:', error);
|
|
70
|
+
const errorMessage = this.extractError(error);
|
|
133
71
|
return options.map(()=>({
|
|
134
72
|
success: false,
|
|
135
|
-
error:
|
|
73
|
+
error: errorMessage
|
|
136
74
|
}));
|
|
137
75
|
}
|
|
138
76
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
*/ async healthCheck() {
|
|
142
|
-
// SendGrid doesn't have a direct health check API
|
|
143
|
-
// We verify the API key format is valid
|
|
144
|
-
return !!(this.sgMail && this.config?.apiKey?.startsWith('SG.'));
|
|
77
|
+
async healthCheck() {
|
|
78
|
+
return !!(this.sgMail && this.apiKey?.startsWith('SG.'));
|
|
145
79
|
}
|
|
146
|
-
|
|
147
|
-
* Cleanup (no-op for SendGrid)
|
|
148
|
-
*/ async close() {
|
|
80
|
+
async close() {
|
|
149
81
|
this.sgMail = null;
|
|
150
82
|
}
|
|
83
|
+
// ─── Private Helpers ────────────────────────────────────────────────────────
|
|
84
|
+
buildMessage(options, includeExtras) {
|
|
85
|
+
const msg = {
|
|
86
|
+
to: this.toArray(options.to),
|
|
87
|
+
from: options.fromName ? {
|
|
88
|
+
email: options.from,
|
|
89
|
+
name: options.fromName
|
|
90
|
+
} : options.from,
|
|
91
|
+
subject: options.subject
|
|
92
|
+
};
|
|
93
|
+
if (options.html) msg.html = options.html;
|
|
94
|
+
if (options.text) msg.text = options.text;
|
|
95
|
+
if (includeExtras) {
|
|
96
|
+
if (options.cc) msg.cc = this.toArray(options.cc);
|
|
97
|
+
if (options.bcc) msg.bcc = this.toArray(options.bcc);
|
|
98
|
+
if (options.replyTo) msg.replyTo = options.replyTo;
|
|
99
|
+
if (options.attachments?.length) {
|
|
100
|
+
msg.attachments = options.attachments.map((a)=>({
|
|
101
|
+
filename: a.filename,
|
|
102
|
+
content: typeof a.content === 'string' ? a.content : a.content.toString('base64'),
|
|
103
|
+
type: a.contentType,
|
|
104
|
+
disposition: 'attachment'
|
|
105
|
+
}));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return msg;
|
|
109
|
+
}
|
|
110
|
+
toArray(value) {
|
|
111
|
+
return Array.isArray(value) ? value : [
|
|
112
|
+
value
|
|
113
|
+
];
|
|
114
|
+
}
|
|
115
|
+
extractError(error) {
|
|
116
|
+
const sgError = error;
|
|
117
|
+
return sgError.response?.body?.errors?.[0]?.message || sgError.message || 'Unknown error';
|
|
118
|
+
}
|
|
119
|
+
handleError(context, error) {
|
|
120
|
+
const message = this.extractError(error);
|
|
121
|
+
this.logger.error(`${context}: ${message}`, error);
|
|
122
|
+
return {
|
|
123
|
+
success: false,
|
|
124
|
+
error: message
|
|
125
|
+
};
|
|
126
|
+
}
|
|
151
127
|
constructor(){
|
|
152
128
|
_define_property(this, "logger", new _common.Logger(SendGridProvider.name));
|
|
153
129
|
_define_property(this, "sgMail", null);
|
|
154
|
-
_define_property(this, "
|
|
130
|
+
_define_property(this, "apiKey", null);
|
|
155
131
|
}
|
|
156
132
|
};
|
|
@@ -63,64 +63,38 @@ function _interop_require_wildcard(obj, nodeInterop) {
|
|
|
63
63
|
}
|
|
64
64
|
return newObj;
|
|
65
65
|
}
|
|
66
|
+
const VERIFY_TIMEOUT_MS = 10_000;
|
|
67
|
+
const SEND_TIMEOUT_MS = 30_000;
|
|
66
68
|
let SmtpProvider = class SmtpProvider {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
*/ async initialize(config) {
|
|
70
|
-
this.config = config;
|
|
71
|
-
this.logger.log(`Initializing SMTP: host=${config.host}, port=${config.port}, secure=${config.secure}, user=${config.auth?.user}`);
|
|
72
|
-
// Dynamic import nodemailer
|
|
69
|
+
async initialize(config) {
|
|
70
|
+
this.logger.log(`Initializing SMTP: host=${config.host}, port=${config.port}`);
|
|
73
71
|
const nodemailer = await Promise.resolve().then(()=>/*#__PURE__*/ _interop_require_wildcard(require("nodemailer")));
|
|
74
|
-
// Determine secure mode: port 465 = implicit TLS, port 587 = STARTTLS
|
|
75
|
-
const isSecure = config.secure ?? config.port === 465;
|
|
76
72
|
this.transporter = nodemailer.createTransport({
|
|
77
73
|
host: config.host,
|
|
78
74
|
port: config.port,
|
|
79
|
-
secure:
|
|
75
|
+
secure: config.secure ?? config.port === 465,
|
|
80
76
|
auth: config.auth,
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
socketTimeout: 30000,
|
|
85
|
-
// TLS options for better compatibility
|
|
77
|
+
connectionTimeout: VERIFY_TIMEOUT_MS,
|
|
78
|
+
greetingTimeout: VERIFY_TIMEOUT_MS,
|
|
79
|
+
socketTimeout: SEND_TIMEOUT_MS,
|
|
86
80
|
tls: {
|
|
87
|
-
rejectUnauthorized:
|
|
88
|
-
minVersion: 'TLSv1.2'
|
|
81
|
+
rejectUnauthorized: config.tls?.rejectUnauthorized ?? true,
|
|
82
|
+
minVersion: config.tls?.minVersion ?? 'TLSv1.2'
|
|
89
83
|
}
|
|
90
84
|
});
|
|
91
|
-
this.
|
|
92
|
-
// Verify connection with timeout
|
|
93
|
-
try {
|
|
94
|
-
const verifyPromise = this.transporter.verify();
|
|
95
|
-
const timeoutPromise = new Promise((_, reject)=>setTimeout(()=>reject(new Error('SMTP verification timeout')), 10000));
|
|
96
|
-
await Promise.race([
|
|
97
|
-
verifyPromise,
|
|
98
|
-
timeoutPromise
|
|
99
|
-
]);
|
|
100
|
-
this.logger.log(`SMTP Provider verified successfully: ${config.host}:${config.port}`);
|
|
101
|
-
} catch (error) {
|
|
102
|
-
this.logger.warn(`SMTP verification failed: ${error.message}`);
|
|
103
|
-
// Don't throw - allow initialization even if verification fails
|
|
104
|
-
// Connection will be retried on send
|
|
105
|
-
}
|
|
85
|
+
await this.verifyConnection(config);
|
|
106
86
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
success: false,
|
|
113
|
-
error: 'SMTP provider not initialized'
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
const toAddress = Array.isArray(options.to) ? options.to.join(', ') : options.to;
|
|
117
|
-
this.logger.log(`Sending email to: ${toAddress}, subject: ${options.subject}`);
|
|
87
|
+
async sendEmail(options) {
|
|
88
|
+
if (!this.transporter) return {
|
|
89
|
+
success: false,
|
|
90
|
+
error: 'SMTP provider not initialized'
|
|
91
|
+
};
|
|
118
92
|
try {
|
|
119
93
|
const mailOptions = {
|
|
120
|
-
from:
|
|
121
|
-
to:
|
|
122
|
-
cc: options.cc ?
|
|
123
|
-
bcc: options.bcc ?
|
|
94
|
+
from: this.formatFrom(options.from, options.fromName),
|
|
95
|
+
to: this.joinAddresses(options.to),
|
|
96
|
+
cc: options.cc ? this.joinAddresses(options.cc) : undefined,
|
|
97
|
+
bcc: options.bcc ? this.joinAddresses(options.bcc) : undefined,
|
|
124
98
|
subject: options.subject,
|
|
125
99
|
text: options.text,
|
|
126
100
|
html: options.html,
|
|
@@ -132,35 +106,20 @@ let SmtpProvider = class SmtpProvider {
|
|
|
132
106
|
encoding: a.encoding
|
|
133
107
|
}))
|
|
134
108
|
};
|
|
135
|
-
this.
|
|
136
|
-
// Send with timeout
|
|
137
|
-
const sendPromise = this.transporter.sendMail(mailOptions);
|
|
138
|
-
const timeoutPromise = new Promise((_, reject)=>setTimeout(()=>reject(new Error('SMTP send timeout after 30 seconds')), 30000));
|
|
139
|
-
const info = await Promise.race([
|
|
140
|
-
sendPromise,
|
|
141
|
-
timeoutPromise
|
|
142
|
-
]);
|
|
109
|
+
const info = await this.withTimeout(this.transporter.sendMail(mailOptions), SEND_TIMEOUT_MS, 'SMTP send timeout');
|
|
143
110
|
this.logger.log(`Email sent via SMTP: ${info.messageId}`);
|
|
144
111
|
return {
|
|
145
112
|
success: true,
|
|
146
113
|
messageId: info.messageId
|
|
147
114
|
};
|
|
148
115
|
} catch (error) {
|
|
149
|
-
this.
|
|
150
|
-
return {
|
|
151
|
-
success: false,
|
|
152
|
-
error: error.message
|
|
153
|
-
};
|
|
116
|
+
return this.handleError('SMTP send failed', error);
|
|
154
117
|
}
|
|
155
118
|
}
|
|
156
|
-
|
|
157
|
-
* Send multiple emails (batch)
|
|
158
|
-
*/ async sendBulkEmails(options) {
|
|
119
|
+
async sendBulkEmails(options) {
|
|
159
120
|
return Promise.all(options.map((opt)=>this.sendEmail(opt)));
|
|
160
121
|
}
|
|
161
|
-
|
|
162
|
-
* Health check for the provider
|
|
163
|
-
*/ async healthCheck() {
|
|
122
|
+
async healthCheck() {
|
|
164
123
|
if (!this.transporter) return false;
|
|
165
124
|
try {
|
|
166
125
|
await this.transporter.verify();
|
|
@@ -169,17 +128,47 @@ let SmtpProvider = class SmtpProvider {
|
|
|
169
128
|
return false;
|
|
170
129
|
}
|
|
171
130
|
}
|
|
172
|
-
|
|
173
|
-
* Close the transporter
|
|
174
|
-
*/ async close() {
|
|
131
|
+
async close() {
|
|
175
132
|
if (this.transporter) {
|
|
176
133
|
this.transporter.close();
|
|
177
134
|
this.transporter = null;
|
|
178
135
|
}
|
|
179
136
|
}
|
|
137
|
+
// ─── Private Helpers ────────────────────────────────────────────────────────
|
|
138
|
+
async verifyConnection(config) {
|
|
139
|
+
try {
|
|
140
|
+
await this.withTimeout(this.transporter.verify(), VERIFY_TIMEOUT_MS, 'SMTP verification timeout');
|
|
141
|
+
this.logger.log(`SMTP Provider verified: ${config.host}:${config.port}`);
|
|
142
|
+
} catch (error) {
|
|
143
|
+
this.logger.warn(`SMTP verification failed: ${this.extractError(error)}`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
formatFrom(from, fromName) {
|
|
147
|
+
return fromName ? `"${fromName}" <${from}>` : from;
|
|
148
|
+
}
|
|
149
|
+
joinAddresses(addresses) {
|
|
150
|
+
return Array.isArray(addresses) ? addresses.join(', ') : addresses;
|
|
151
|
+
}
|
|
152
|
+
async withTimeout(promise, ms, message) {
|
|
153
|
+
const timeout = new Promise((_, reject)=>setTimeout(()=>reject(new Error(message)), ms));
|
|
154
|
+
return Promise.race([
|
|
155
|
+
promise,
|
|
156
|
+
timeout
|
|
157
|
+
]);
|
|
158
|
+
}
|
|
159
|
+
extractError(error) {
|
|
160
|
+
return error instanceof Error ? error.message : 'Unknown error';
|
|
161
|
+
}
|
|
162
|
+
handleError(context, error) {
|
|
163
|
+
const message = this.extractError(error);
|
|
164
|
+
this.logger.error(`${context}: ${message}`, error instanceof Error ? error.stack : undefined);
|
|
165
|
+
return {
|
|
166
|
+
success: false,
|
|
167
|
+
error: message
|
|
168
|
+
};
|
|
169
|
+
}
|
|
180
170
|
constructor(){
|
|
181
171
|
_define_property(this, "logger", new _common.Logger(SmtpProvider.name));
|
|
182
172
|
_define_property(this, "transporter", null);
|
|
183
|
-
_define_property(this, "config", null);
|
|
184
173
|
}
|
|
185
174
|
};
|
|
@@ -9,8 +9,8 @@ Object.defineProperty(exports, "EmailConfigService", {
|
|
|
9
9
|
}
|
|
10
10
|
});
|
|
11
11
|
const _common = require("@nestjs/common");
|
|
12
|
+
const _emailconstants = require("../config/email.constants");
|
|
12
13
|
const _interfaces = require("../interfaces");
|
|
13
|
-
const _emailconstants = require("./email.constants");
|
|
14
14
|
function _define_property(obj, key, value) {
|
|
15
15
|
if (key in obj) {
|
|
16
16
|
Object.defineProperty(obj, key, {
|
|
@@ -39,44 +39,21 @@ function _ts_param(paramIndex, decorator) {
|
|
|
39
39
|
};
|
|
40
40
|
}
|
|
41
41
|
let EmailConfigService = class EmailConfigService {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
*/ isCompanyFeatureEnabled() {
|
|
42
|
+
// ─── IModuleConfigService Implementation ────────────────────────────────────
|
|
43
|
+
isCompanyFeatureEnabled() {
|
|
45
44
|
return this.options.bootstrapAppConfig?.enableCompanyFeature ?? false;
|
|
46
45
|
}
|
|
47
|
-
|
|
48
|
-
* Get database mode
|
|
49
|
-
*/ getDatabaseMode() {
|
|
46
|
+
getDatabaseMode() {
|
|
50
47
|
return this.options.bootstrapAppConfig?.databaseMode ?? 'single';
|
|
51
48
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
*/ getRateLimitPerMinute() {
|
|
55
|
-
return this.options.config?.rateLimitPerMinute ?? 60;
|
|
49
|
+
isMultiTenant() {
|
|
50
|
+
return this.getDatabaseMode() === 'multi-tenant';
|
|
56
51
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
*/ isLoggingEnabled() {
|
|
60
|
-
return this.options.config?.enableLogging ?? false;
|
|
61
|
-
}
|
|
62
|
-
/**
|
|
63
|
-
* Get default provider type
|
|
64
|
-
*/ getDefaultProvider() {
|
|
65
|
-
return this.options.config?.defaultProvider;
|
|
66
|
-
}
|
|
67
|
-
/**
|
|
68
|
-
* Get default from name
|
|
69
|
-
*/ getDefaultFromName() {
|
|
52
|
+
// ─── Config Getters ─────────────────────────────────────────────────────────
|
|
53
|
+
getDefaultFromName() {
|
|
70
54
|
return _emailconstants.DEFAULT_FROM_NAME;
|
|
71
55
|
}
|
|
72
|
-
|
|
73
|
-
* Get default database config
|
|
74
|
-
*/ getDefaultDatabaseConfig() {
|
|
75
|
-
return this.options.config?.defaultDatabaseConfig;
|
|
76
|
-
}
|
|
77
|
-
/**
|
|
78
|
-
* Get all options
|
|
79
|
-
*/ getOptions() {
|
|
56
|
+
getOptions() {
|
|
80
57
|
return this.options;
|
|
81
58
|
}
|
|
82
59
|
constructor(options){
|