@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.
Files changed (100) hide show
  1. package/README.md +604 -0
  2. package/cjs/config/email.constants.js +0 -18
  3. package/cjs/config/index.js +0 -1
  4. package/cjs/controllers/email-config.controller.js +46 -4
  5. package/cjs/controllers/email-send.controller.js +13 -26
  6. package/cjs/controllers/email-template.controller.js +60 -11
  7. package/cjs/docs/email-swagger.config.js +18 -80
  8. package/cjs/dtos/email-config.dto.js +6 -106
  9. package/cjs/dtos/email-send.dto.js +101 -123
  10. package/cjs/dtos/email-template.dto.js +41 -103
  11. package/cjs/entities/email-config-with-company.entity.js +2 -2
  12. package/cjs/entities/email-config.entity.js +91 -3
  13. package/cjs/entities/email-template-with-company.entity.js +5 -3
  14. package/cjs/entities/email-template.entity.js +119 -3
  15. package/cjs/entities/index.js +34 -19
  16. package/cjs/index.js +1 -0
  17. package/cjs/interfaces/email-provider.interface.js +1 -3
  18. package/cjs/modules/email.module.js +50 -104
  19. package/cjs/providers/email-factory.service.js +37 -109
  20. package/cjs/providers/email-provider.registry.js +5 -15
  21. package/cjs/providers/mailgun-provider.js +54 -58
  22. package/cjs/providers/sendgrid-provider.js +68 -92
  23. package/cjs/providers/smtp-provider.js +58 -69
  24. package/cjs/{config → services}/email-config.service.js +9 -32
  25. package/cjs/services/email-datasource.provider.js +17 -104
  26. package/cjs/services/email-provider-config.service.js +28 -58
  27. package/cjs/services/email-send.service.js +120 -125
  28. package/cjs/services/email-template.service.js +62 -85
  29. package/cjs/services/index.js +2 -1
  30. package/cjs/utils/email-templates.util.js +64 -0
  31. package/cjs/utils/index.js +18 -0
  32. package/config/email.constants.d.ts +0 -9
  33. package/config/index.d.ts +0 -1
  34. package/controllers/email-send.controller.d.ts +5 -12
  35. package/controllers/email-template.controller.d.ts +5 -7
  36. package/dtos/email-config.dto.d.ts +5 -13
  37. package/dtos/email-send.dto.d.ts +17 -21
  38. package/dtos/email-template.dto.d.ts +5 -16
  39. package/entities/email-config-with-company.entity.d.ts +2 -2
  40. package/entities/email-config.entity.d.ts +9 -2
  41. package/entities/email-template-with-company.entity.d.ts +2 -2
  42. package/entities/email-template.entity.d.ts +13 -2
  43. package/entities/index.d.ts +9 -3
  44. package/fesm/config/email.constants.js +0 -9
  45. package/fesm/config/index.js +0 -1
  46. package/fesm/controllers/email-config.controller.js +49 -7
  47. package/fesm/controllers/email-send.controller.js +13 -26
  48. package/fesm/controllers/email-template.controller.js +61 -12
  49. package/fesm/docs/email-swagger.config.js +21 -86
  50. package/fesm/dtos/email-config.dto.js +9 -115
  51. package/fesm/dtos/email-send.dto.js +103 -139
  52. package/fesm/dtos/email-template.dto.js +43 -111
  53. package/fesm/entities/email-config-with-company.entity.js +2 -2
  54. package/fesm/entities/email-config.entity.js +92 -4
  55. package/fesm/entities/email-template-with-company.entity.js +5 -3
  56. package/fesm/entities/email-template.entity.js +120 -4
  57. package/fesm/entities/index.js +22 -16
  58. package/fesm/index.js +1 -0
  59. package/fesm/interfaces/email-config.interface.js +1 -3
  60. package/fesm/interfaces/email-module-options.interface.js +1 -3
  61. package/fesm/interfaces/email-provider.interface.js +1 -5
  62. package/fesm/interfaces/email-template.interface.js +1 -3
  63. package/fesm/modules/email.module.js +52 -106
  64. package/fesm/providers/email-factory.service.js +38 -69
  65. package/fesm/providers/email-provider.registry.js +6 -19
  66. package/fesm/providers/mailgun-provider.js +55 -63
  67. package/fesm/providers/sendgrid-provider.js +69 -97
  68. package/fesm/providers/smtp-provider.js +59 -73
  69. package/fesm/{config → services}/email-config.service.js +9 -32
  70. package/fesm/services/email-datasource.provider.js +18 -64
  71. package/fesm/services/email-provider-config.service.js +26 -56
  72. package/fesm/services/email-send.service.js +118 -123
  73. package/fesm/services/email-template.service.js +60 -83
  74. package/fesm/services/index.js +2 -1
  75. package/fesm/utils/email-templates.util.js +47 -0
  76. package/fesm/utils/index.js +1 -0
  77. package/index.d.ts +1 -0
  78. package/interfaces/email-config.interface.d.ts +6 -0
  79. package/interfaces/email-module-options.interface.d.ts +0 -5
  80. package/modules/email.module.d.ts +1 -2
  81. package/package.json +9 -4
  82. package/providers/email-factory.service.d.ts +4 -7
  83. package/providers/mailgun-provider.d.ts +6 -2
  84. package/providers/sendgrid-provider.d.ts +6 -2
  85. package/providers/smtp-provider.d.ts +7 -2
  86. package/services/email-config.service.d.ts +12 -0
  87. package/services/email-datasource.provider.d.ts +3 -6
  88. package/services/email-provider-config.service.d.ts +3 -3
  89. package/services/email-send.service.d.ts +11 -3
  90. package/services/email-template.service.d.ts +5 -4
  91. package/services/index.d.ts +2 -1
  92. package/utils/email-templates.util.d.ts +2 -0
  93. package/utils/index.d.ts +1 -0
  94. package/cjs/entities/email-config-base.entity.js +0 -111
  95. package/cjs/entities/email-template-base.entity.js +0 -134
  96. package/config/email-config.service.d.ts +0 -13
  97. package/entities/email-config-base.entity.d.ts +0 -11
  98. package/entities/email-template-base.entity.d.ts +0 -14
  99. package/fesm/entities/email-config-base.entity.js +0 -101
  100. 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
- * Initialize the Mailgun provider with configuration
28
- */ async initialize(config) {
29
- this.config = config;
28
+ async initialize(config) {
29
+ this.domain = config.domain;
30
30
  try {
31
- // Dynamic import mailgun.js and form-data - use eval to bypass TypeScript module resolution
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
- // Determine API URL based on region
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.message);
46
- throw new Error('Mailgun initialization failed. Make sure mailgun.js and form-data are installed: npm install mailgun.js form-data');
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
- * Send a single email
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 to = Array.isArray(options.to) ? options.to.join(', ') : options.to;
60
- const cc = options.cc ? Array.isArray(options.cc) ? options.cc.join(', ') : options.cc : undefined;
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.logger.error('Mailgun send failed:', error);
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
- * Health check for the provider
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
- // Verify domain exists
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, "config", null);
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
- * Initialize the SendGrid provider with configuration
28
- */ async initialize(config) {
29
- this.config = config;
26
+ async initialize(config) {
27
+ this.apiKey = config.apiKey;
30
28
  try {
31
- // Dynamic import @sendgrid/mail - use eval to bypass TypeScript module resolution
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.message);
39
- throw new Error('SendGrid initialization failed. Make sure @sendgrid/mail is installed: npm install @sendgrid/mail');
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
- * Send a single email
44
- */ async sendEmail(options) {
45
- if (!this.sgMail) {
46
- return {
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.logger.error('SendGrid send failed:', error);
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
- * Send multiple emails (batch)
103
- */ async sendBulkEmails(options) {
104
- if (!this.sgMail) {
105
- return options.map(()=>({
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: error.message
73
+ error: errorMessage
136
74
  }));
137
75
  }
138
76
  }
139
- /**
140
- * Health check for the provider
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, "config", null);
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
- * Initialize the SMTP provider with configuration
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: isSecure,
75
+ secure: config.secure ?? config.port === 465,
80
76
  auth: config.auth,
81
- // Timeout settings to prevent hanging
82
- connectionTimeout: 10000,
83
- greetingTimeout: 10000,
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: false,
88
- minVersion: 'TLSv1.2'
81
+ rejectUnauthorized: config.tls?.rejectUnauthorized ?? true,
82
+ minVersion: config.tls?.minVersion ?? 'TLSv1.2'
89
83
  }
90
84
  });
91
- this.logger.log(`SMTP transporter created with secure=${isSecure}`);
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
- * Send a single email
109
- */ async sendEmail(options) {
110
- if (!this.transporter) {
111
- return {
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: options.fromName ? `"${options.fromName}" <${options.from}>` : options.from,
121
- to: toAddress,
122
- cc: options.cc ? Array.isArray(options.cc) ? options.cc.join(', ') : options.cc : undefined,
123
- bcc: options.bcc ? Array.isArray(options.bcc) ? options.bcc.join(', ') : options.bcc : undefined,
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.logger.log(`Mail options: from=${mailOptions.from}, to=${mailOptions.to}`);
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.logger.error(`SMTP send failed: ${error.message}`, error.stack);
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
- * Check if company feature is enabled
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
- * Get rate limit per minute
54
- */ getRateLimitPerMinute() {
55
- return this.options.config?.rateLimitPerMinute ?? 60;
49
+ isMultiTenant() {
50
+ return this.getDatabaseMode() === 'multi-tenant';
56
51
  }
57
- /**
58
- * Check if email logging is enabled
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){