@flusys/nestjs-email 1.1.0-beta

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 (124) hide show
  1. package/cjs/config/email-config.service.js +94 -0
  2. package/cjs/config/email.constants.js +40 -0
  3. package/cjs/config/index.js +19 -0
  4. package/cjs/controllers/email-config.controller.js +59 -0
  5. package/cjs/controllers/email-send.controller.js +142 -0
  6. package/cjs/controllers/email-template.controller.js +84 -0
  7. package/cjs/controllers/index.js +20 -0
  8. package/cjs/docs/email-swagger.config.js +176 -0
  9. package/cjs/docs/index.js +11 -0
  10. package/cjs/dtos/email-config.dto.js +238 -0
  11. package/cjs/dtos/email-send.dto.js +376 -0
  12. package/cjs/dtos/email-template.dto.js +283 -0
  13. package/cjs/dtos/index.js +20 -0
  14. package/cjs/entities/email-config-base.entity.js +111 -0
  15. package/cjs/entities/email-config-with-company.entity.js +63 -0
  16. package/cjs/entities/email-config.entity.js +25 -0
  17. package/cjs/entities/email-template-base.entity.js +134 -0
  18. package/cjs/entities/email-template-with-company.entity.js +63 -0
  19. package/cjs/entities/email-template.entity.js +25 -0
  20. package/cjs/entities/index.js +41 -0
  21. package/cjs/enums/email-provider-type.enum.js +18 -0
  22. package/cjs/enums/index.js +18 -0
  23. package/cjs/index.js +27 -0
  24. package/cjs/interfaces/email-config.interface.js +4 -0
  25. package/cjs/interfaces/email-module-options.interface.js +4 -0
  26. package/cjs/interfaces/email-provider.interface.js +6 -0
  27. package/cjs/interfaces/email-template.interface.js +4 -0
  28. package/cjs/interfaces/index.js +21 -0
  29. package/cjs/modules/email.module.js +177 -0
  30. package/cjs/modules/index.js +18 -0
  31. package/cjs/providers/email-factory.service.js +160 -0
  32. package/cjs/providers/email-provider.registry.js +51 -0
  33. package/cjs/providers/index.js +22 -0
  34. package/cjs/providers/mailgun-provider.js +125 -0
  35. package/cjs/providers/sendgrid-provider.js +156 -0
  36. package/cjs/providers/smtp-provider.js +185 -0
  37. package/cjs/services/email-datasource.provider.js +215 -0
  38. package/cjs/services/email-provider-config.service.js +180 -0
  39. package/cjs/services/email-send.service.js +228 -0
  40. package/cjs/services/email-template.service.js +186 -0
  41. package/cjs/services/index.js +21 -0
  42. package/config/email-config.service.d.ts +13 -0
  43. package/config/email.constants.d.ts +11 -0
  44. package/config/index.d.ts +2 -0
  45. package/controllers/email-config.controller.d.ts +17 -0
  46. package/controllers/email-send.controller.d.ts +19 -0
  47. package/controllers/email-template.controller.d.ts +25 -0
  48. package/controllers/index.d.ts +3 -0
  49. package/docs/email-swagger.config.d.ts +3 -0
  50. package/docs/index.d.ts +1 -0
  51. package/dtos/email-config.dto.d.ts +33 -0
  52. package/dtos/email-send.dto.d.ts +45 -0
  53. package/dtos/email-template.dto.d.ts +42 -0
  54. package/dtos/index.d.ts +3 -0
  55. package/entities/email-config-base.entity.d.ts +11 -0
  56. package/entities/email-config-with-company.entity.d.ts +4 -0
  57. package/entities/email-config.entity.d.ts +3 -0
  58. package/entities/email-template-base.entity.d.ts +14 -0
  59. package/entities/email-template-with-company.entity.d.ts +4 -0
  60. package/entities/email-template.entity.d.ts +3 -0
  61. package/entities/index.d.ts +7 -0
  62. package/enums/email-provider-type.enum.d.ts +5 -0
  63. package/enums/index.d.ts +1 -0
  64. package/fesm/config/email-config.service.js +84 -0
  65. package/fesm/config/email.constants.js +13 -0
  66. package/fesm/config/index.js +2 -0
  67. package/fesm/controllers/email-config.controller.js +49 -0
  68. package/fesm/controllers/email-send.controller.js +132 -0
  69. package/fesm/controllers/email-template.controller.js +74 -0
  70. package/fesm/controllers/index.js +3 -0
  71. package/fesm/docs/email-swagger.config.js +172 -0
  72. package/fesm/docs/index.js +1 -0
  73. package/fesm/dtos/email-config.dto.js +223 -0
  74. package/fesm/dtos/email-send.dto.js +360 -0
  75. package/fesm/dtos/email-template.dto.js +268 -0
  76. package/fesm/dtos/index.js +3 -0
  77. package/fesm/entities/email-config-base.entity.js +101 -0
  78. package/fesm/entities/email-config-with-company.entity.js +53 -0
  79. package/fesm/entities/email-config.entity.js +15 -0
  80. package/fesm/entities/email-template-base.entity.js +124 -0
  81. package/fesm/entities/email-template-with-company.entity.js +53 -0
  82. package/fesm/entities/email-template.entity.js +15 -0
  83. package/fesm/entities/index.js +20 -0
  84. package/fesm/enums/email-provider-type.enum.js +8 -0
  85. package/fesm/enums/index.js +1 -0
  86. package/fesm/index.js +10 -0
  87. package/fesm/interfaces/email-config.interface.js +3 -0
  88. package/fesm/interfaces/email-module-options.interface.js +3 -0
  89. package/fesm/interfaces/email-provider.interface.js +5 -0
  90. package/fesm/interfaces/email-template.interface.js +3 -0
  91. package/fesm/interfaces/index.js +4 -0
  92. package/fesm/modules/email.module.js +167 -0
  93. package/fesm/modules/index.js +1 -0
  94. package/fesm/providers/email-factory.service.js +109 -0
  95. package/fesm/providers/email-provider.registry.js +44 -0
  96. package/fesm/providers/index.js +5 -0
  97. package/fesm/providers/mailgun-provider.js +119 -0
  98. package/fesm/providers/sendgrid-provider.js +150 -0
  99. package/fesm/providers/smtp-provider.js +137 -0
  100. package/fesm/services/email-datasource.provider.js +164 -0
  101. package/fesm/services/email-provider-config.service.js +170 -0
  102. package/fesm/services/email-send.service.js +218 -0
  103. package/fesm/services/email-template.service.js +176 -0
  104. package/fesm/services/index.js +4 -0
  105. package/index.d.ts +9 -0
  106. package/interfaces/email-config.interface.d.ts +28 -0
  107. package/interfaces/email-module-options.interface.d.ts +26 -0
  108. package/interfaces/email-provider.interface.d.ts +34 -0
  109. package/interfaces/email-template.interface.d.ts +64 -0
  110. package/interfaces/index.d.ts +4 -0
  111. package/modules/email.module.d.ts +9 -0
  112. package/modules/index.d.ts +1 -0
  113. package/package.json +105 -0
  114. package/providers/email-factory.service.d.ts +14 -0
  115. package/providers/email-provider.registry.d.ts +10 -0
  116. package/providers/index.d.ts +5 -0
  117. package/providers/mailgun-provider.d.ts +11 -0
  118. package/providers/sendgrid-provider.d.ts +11 -0
  119. package/providers/smtp-provider.d.ts +11 -0
  120. package/services/email-datasource.provider.d.ts +25 -0
  121. package/services/email-provider-config.service.d.ts +32 -0
  122. package/services/email-send.service.d.ts +20 -0
  123. package/services/email-template.service.d.ts +31 -0
  124. package/services/index.d.ts +4 -0
@@ -0,0 +1,109 @@
1
+ function _define_property(obj, key, value) {
2
+ if (key in obj) {
3
+ Object.defineProperty(obj, key, {
4
+ value: value,
5
+ enumerable: true,
6
+ configurable: true,
7
+ writable: true
8
+ });
9
+ } else {
10
+ obj[key] = value;
11
+ }
12
+ return obj;
13
+ }
14
+ function _ts_decorate(decorators, target, key, desc) {
15
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
16
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
17
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
18
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
19
+ }
20
+ function _ts_metadata(k, v) {
21
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
22
+ }
23
+ function _ts_param(paramIndex, decorator) {
24
+ return function(target, key) {
25
+ decorator(target, key, paramIndex);
26
+ };
27
+ }
28
+ import { Inject, Injectable, Logger, NotFoundException } from '@nestjs/common';
29
+ import * as crypto from 'crypto';
30
+ import { EmailConfigService } from '../config/email-config.service';
31
+ import { EmailProviderRegistry } from './email-provider.registry';
32
+ export class EmailFactoryService {
33
+ /**
34
+ * Generate a unique cache key for a provider configuration
35
+ */ generateCacheKey(config) {
36
+ const configString = JSON.stringify(config.config, Object.keys(config.config || {}).sort());
37
+ const configHash = crypto.createHash('sha256').update(configString).digest('hex').substring(0, 16);
38
+ return `${config.provider}-${configHash}`;
39
+ }
40
+ /**
41
+ * Create or retrieve a cached email provider instance
42
+ */ async createProvider(config) {
43
+ const providerKey = this.generateCacheKey(config);
44
+ // Return cached provider if exists
45
+ if (this.providerCache.has(providerKey)) {
46
+ return this.providerCache.get(providerKey);
47
+ }
48
+ // Get provider class from registry
49
+ const ProviderClass = EmailProviderRegistry.get(config.provider);
50
+ if (!ProviderClass) {
51
+ throw new NotFoundException(`Email provider '${config.provider}' is not registered. ` + `Available providers: ${EmailProviderRegistry.getAll().join(', ')}`);
52
+ }
53
+ try {
54
+ // Create new instance
55
+ const instance = new ProviderClass();
56
+ // Initialize if provider has initialize method
57
+ if ('initialize' in instance && typeof instance.initialize === 'function') {
58
+ await instance.initialize(config.config);
59
+ }
60
+ // Cache the instance
61
+ this.providerCache.set(providerKey, instance);
62
+ this.logger.log(`Created email provider: ${config.provider} (key: ${providerKey})`);
63
+ return instance;
64
+ } catch (error) {
65
+ this.logger.error(`Failed to create provider ${config.provider}:`, error);
66
+ throw new Error(`Failed to initialize email provider '${config.provider}': ${error?.message || 'Unknown error'}`);
67
+ }
68
+ }
69
+ /**
70
+ * Check if a provider is available (registered)
71
+ */ isProviderAvailable(providerName) {
72
+ return EmailProviderRegistry.has(providerName);
73
+ }
74
+ /**
75
+ * Get list of available providers
76
+ */ getAvailableProviders() {
77
+ return EmailProviderRegistry.getAll();
78
+ }
79
+ /**
80
+ * Cleanup on module destroy
81
+ */ async onModuleDestroy() {
82
+ this.logger.log('Cleaning up email provider connections...');
83
+ const closePromises = [];
84
+ for (const [key, provider] of this.providerCache.entries()){
85
+ if ('close' in provider && typeof provider.close === 'function') {
86
+ closePromises.push(provider.close().then(()=>this.logger.debug(`Closed provider: ${key}`)).catch((err)=>this.logger.warn(`Failed to close provider ${key}: ${err.message}`)));
87
+ }
88
+ }
89
+ await Promise.allSettled(closePromises);
90
+ this.providerCache.clear();
91
+ this.logger.log('Email provider cleanup complete');
92
+ }
93
+ constructor(emailConfigService){
94
+ _define_property(this, "emailConfigService", void 0);
95
+ _define_property(this, "logger", void 0);
96
+ _define_property(this, "providerCache", void 0);
97
+ this.emailConfigService = emailConfigService;
98
+ this.logger = new Logger(EmailFactoryService.name);
99
+ this.providerCache = new Map();
100
+ }
101
+ }
102
+ EmailFactoryService = _ts_decorate([
103
+ Injectable(),
104
+ _ts_param(0, Inject(EmailConfigService)),
105
+ _ts_metadata("design:type", Function),
106
+ _ts_metadata("design:paramtypes", [
107
+ typeof EmailConfigService === "undefined" ? Object : EmailConfigService
108
+ ])
109
+ ], EmailFactoryService);
@@ -0,0 +1,44 @@
1
+ function _define_property(obj, key, value) {
2
+ if (key in obj) {
3
+ Object.defineProperty(obj, key, {
4
+ value: value,
5
+ enumerable: true,
6
+ configurable: true,
7
+ writable: true
8
+ });
9
+ } else {
10
+ obj[key] = value;
11
+ }
12
+ return obj;
13
+ }
14
+ /**
15
+ * Registry for email providers
16
+ * Allows dynamic registration of providers at runtime
17
+ */ export class EmailProviderRegistry {
18
+ /**
19
+ * Register an email provider
20
+ */ static register(providerName, providerClass) {
21
+ this.providers.set(providerName.toLowerCase(), providerClass);
22
+ }
23
+ /**
24
+ * Get a registered provider class
25
+ */ static get(providerName) {
26
+ return this.providers.get(providerName.toLowerCase());
27
+ }
28
+ /**
29
+ * Check if a provider is registered
30
+ */ static has(providerName) {
31
+ return this.providers.has(providerName.toLowerCase());
32
+ }
33
+ /**
34
+ * Get all registered provider names
35
+ */ static getAll() {
36
+ return Array.from(this.providers.keys());
37
+ }
38
+ /**
39
+ * Clear all providers (useful for testing)
40
+ */ static clear() {
41
+ this.providers.clear();
42
+ }
43
+ }
44
+ _define_property(EmailProviderRegistry, "providers", new Map());
@@ -0,0 +1,5 @@
1
+ export * from './email-provider.registry';
2
+ export * from './email-factory.service';
3
+ export * from './smtp-provider';
4
+ export * from './sendgrid-provider';
5
+ export * from './mailgun-provider';
@@ -0,0 +1,119 @@
1
+ function _define_property(obj, key, value) {
2
+ if (key in obj) {
3
+ Object.defineProperty(obj, key, {
4
+ value: value,
5
+ enumerable: true,
6
+ configurable: true,
7
+ writable: true
8
+ });
9
+ } else {
10
+ obj[key] = value;
11
+ }
12
+ return obj;
13
+ }
14
+ import { Logger } from '@nestjs/common';
15
+ /**
16
+ * Mailgun Email Provider
17
+ * Uses mailgun.js package for sending emails
18
+ * Install: npm install mailgun.js form-data
19
+ */ export class MailgunProvider {
20
+ /**
21
+ * Initialize the Mailgun provider with configuration
22
+ */ async initialize(config) {
23
+ this.config = config;
24
+ try {
25
+ // Dynamic import mailgun.js and form-data - use eval to bypass TypeScript module resolution
26
+ // eslint-disable-next-line @typescript-eslint/no-implied-eval
27
+ const Mailgun = (await new Function('return import("mailgun.js")')()).default;
28
+ const FormData = (await new Function('return import("form-data")')()).default;
29
+ const mailgun = new Mailgun(FormData);
30
+ // Determine API URL based on region
31
+ const url = config.region === 'eu' ? 'https://api.eu.mailgun.net' : 'https://api.mailgun.net';
32
+ this.client = mailgun.client({
33
+ username: 'api',
34
+ key: config.apiKey,
35
+ url
36
+ });
37
+ this.logger.log(`Mailgun Provider initialized for domain: ${config.domain}`);
38
+ } catch (error) {
39
+ this.logger.error('Failed to initialize Mailgun:', error.message);
40
+ throw new Error('Mailgun initialization failed. Make sure mailgun.js and form-data are installed: npm install mailgun.js form-data');
41
+ }
42
+ }
43
+ /**
44
+ * Send a single email
45
+ */ async sendEmail(options) {
46
+ if (!this.client || !this.config) {
47
+ return {
48
+ success: false,
49
+ error: 'Mailgun provider not initialized'
50
+ };
51
+ }
52
+ try {
53
+ const to = Array.isArray(options.to) ? options.to.join(', ') : options.to;
54
+ const cc = options.cc ? Array.isArray(options.cc) ? options.cc.join(', ') : options.cc : undefined;
55
+ const bcc = options.bcc ? Array.isArray(options.bcc) ? options.bcc.join(', ') : options.bcc : undefined;
56
+ const messageData = {
57
+ from: options.fromName ? `${options.fromName} <${options.from}>` : options.from,
58
+ to,
59
+ subject: options.subject
60
+ };
61
+ // Only include html/text if they have values (required for plain text templates)
62
+ if (options.html) messageData.html = options.html;
63
+ if (options.text) messageData.text = options.text;
64
+ // Optional fields
65
+ if (cc) messageData.cc = cc;
66
+ if (bcc) messageData.bcc = bcc;
67
+ if (options.replyTo) messageData['h:Reply-To'] = options.replyTo;
68
+ // Handle attachments
69
+ if (options.attachments && options.attachments.length > 0) {
70
+ messageData.attachment = options.attachments.map((a)=>({
71
+ filename: a.filename,
72
+ data: typeof a.content === 'string' ? Buffer.from(a.content, 'base64') : a.content,
73
+ contentType: a.contentType
74
+ }));
75
+ }
76
+ const result = await this.client.messages.create(this.config.domain, messageData);
77
+ this.logger.log(`Email sent via Mailgun: ${result.id}`);
78
+ return {
79
+ success: true,
80
+ messageId: result.id
81
+ };
82
+ } catch (error) {
83
+ this.logger.error('Mailgun send failed:', error);
84
+ return {
85
+ success: false,
86
+ error: error.message
87
+ };
88
+ }
89
+ }
90
+ /**
91
+ * Send multiple emails (batch)
92
+ */ async sendBulkEmails(options) {
93
+ // Mailgun supports batch sending with recipient variables
94
+ // For simplicity, we send individually here
95
+ return Promise.all(options.map((opt)=>this.sendEmail(opt)));
96
+ }
97
+ /**
98
+ * Health check for the provider
99
+ */ async healthCheck() {
100
+ if (!this.client || !this.config) return false;
101
+ try {
102
+ // Verify domain exists
103
+ await this.client.domains.get(this.config.domain);
104
+ return true;
105
+ } catch {
106
+ return false;
107
+ }
108
+ }
109
+ /**
110
+ * Cleanup
111
+ */ async close() {
112
+ this.client = null;
113
+ }
114
+ constructor(){
115
+ _define_property(this, "logger", new Logger(MailgunProvider.name));
116
+ _define_property(this, "client", null);
117
+ _define_property(this, "config", null);
118
+ }
119
+ }
@@ -0,0 +1,150 @@
1
+ function _define_property(obj, key, value) {
2
+ if (key in obj) {
3
+ Object.defineProperty(obj, key, {
4
+ value: value,
5
+ enumerable: true,
6
+ configurable: true,
7
+ writable: true
8
+ });
9
+ } else {
10
+ obj[key] = value;
11
+ }
12
+ return obj;
13
+ }
14
+ import { Logger } from '@nestjs/common';
15
+ /**
16
+ * SendGrid Email Provider
17
+ * Uses @sendgrid/mail package for sending emails
18
+ * Install: npm install @sendgrid/mail
19
+ */ export class SendGridProvider {
20
+ /**
21
+ * Initialize the SendGrid provider with configuration
22
+ */ async initialize(config) {
23
+ this.config = config;
24
+ try {
25
+ // Dynamic import @sendgrid/mail - use eval to bypass TypeScript module resolution
26
+ // eslint-disable-next-line @typescript-eslint/no-implied-eval
27
+ const sgMailModule = await new Function('return import("@sendgrid/mail")')();
28
+ this.sgMail = sgMailModule.default || sgMailModule;
29
+ this.sgMail.setApiKey(config.apiKey);
30
+ this.logger.log('SendGrid Provider initialized');
31
+ } catch (error) {
32
+ this.logger.error('Failed to initialize SendGrid:', error.message);
33
+ throw new Error('SendGrid initialization failed. Make sure @sendgrid/mail is installed: npm install @sendgrid/mail');
34
+ }
35
+ }
36
+ /**
37
+ * Send a single email
38
+ */ async sendEmail(options) {
39
+ if (!this.sgMail) {
40
+ return {
41
+ success: false,
42
+ error: 'SendGrid provider not initialized'
43
+ };
44
+ }
45
+ try {
46
+ const msg = {
47
+ to: Array.isArray(options.to) ? options.to : [
48
+ options.to
49
+ ],
50
+ from: options.fromName ? {
51
+ email: options.from,
52
+ name: options.fromName
53
+ } : options.from,
54
+ subject: options.subject
55
+ };
56
+ // Only include html/text if they have values (required for plain text templates)
57
+ if (options.html) msg.html = options.html;
58
+ if (options.text) msg.text = options.text;
59
+ // Optional fields
60
+ if (options.cc) {
61
+ msg.cc = Array.isArray(options.cc) ? options.cc : [
62
+ options.cc
63
+ ];
64
+ }
65
+ if (options.bcc) {
66
+ msg.bcc = Array.isArray(options.bcc) ? options.bcc : [
67
+ options.bcc
68
+ ];
69
+ }
70
+ if (options.replyTo) msg.replyTo = options.replyTo;
71
+ if (options.attachments?.length) {
72
+ msg.attachments = options.attachments.map((a)=>({
73
+ filename: a.filename,
74
+ content: typeof a.content === 'string' ? a.content : a.content.toString('base64'),
75
+ type: a.contentType,
76
+ disposition: 'attachment'
77
+ }));
78
+ }
79
+ const [response] = await this.sgMail.send(msg);
80
+ const messageId = response.headers['x-message-id'] || response.headers['X-Message-Id'];
81
+ this.logger.log(`Email sent via SendGrid: ${messageId}`);
82
+ return {
83
+ success: true,
84
+ messageId
85
+ };
86
+ } catch (error) {
87
+ this.logger.error('SendGrid send failed:', error);
88
+ const errorMessage = error.response?.body?.errors?.[0]?.message || error.message;
89
+ return {
90
+ success: false,
91
+ error: errorMessage
92
+ };
93
+ }
94
+ }
95
+ /**
96
+ * Send multiple emails (batch)
97
+ */ async sendBulkEmails(options) {
98
+ if (!this.sgMail) {
99
+ return options.map(()=>({
100
+ success: false,
101
+ error: 'SendGrid provider not initialized'
102
+ }));
103
+ }
104
+ // SendGrid supports batch sending with sendMultiple
105
+ const messages = options.map((opt)=>{
106
+ const msg = {
107
+ to: Array.isArray(opt.to) ? opt.to : [
108
+ opt.to
109
+ ],
110
+ from: opt.fromName ? {
111
+ email: opt.from,
112
+ name: opt.fromName
113
+ } : opt.from,
114
+ subject: opt.subject
115
+ };
116
+ if (opt.html) msg.html = opt.html;
117
+ if (opt.text) msg.text = opt.text;
118
+ return msg;
119
+ });
120
+ try {
121
+ await this.sgMail.send(messages);
122
+ return options.map(()=>({
123
+ success: true
124
+ }));
125
+ } catch (error) {
126
+ this.logger.error('SendGrid bulk send failed:', error);
127
+ return options.map(()=>({
128
+ success: false,
129
+ error: error.message
130
+ }));
131
+ }
132
+ }
133
+ /**
134
+ * Health check for the provider
135
+ */ async healthCheck() {
136
+ // SendGrid doesn't have a direct health check API
137
+ // We verify the API key format is valid
138
+ return !!(this.sgMail && this.config?.apiKey?.startsWith('SG.'));
139
+ }
140
+ /**
141
+ * Cleanup (no-op for SendGrid)
142
+ */ async close() {
143
+ this.sgMail = null;
144
+ }
145
+ constructor(){
146
+ _define_property(this, "logger", new Logger(SendGridProvider.name));
147
+ _define_property(this, "sgMail", null);
148
+ _define_property(this, "config", null);
149
+ }
150
+ }
@@ -0,0 +1,137 @@
1
+ function _define_property(obj, key, value) {
2
+ if (key in obj) {
3
+ Object.defineProperty(obj, key, {
4
+ value: value,
5
+ enumerable: true,
6
+ configurable: true,
7
+ writable: true
8
+ });
9
+ } else {
10
+ obj[key] = value;
11
+ }
12
+ return obj;
13
+ }
14
+ import { Logger } from '@nestjs/common';
15
+ /**
16
+ * SMTP Email Provider using nodemailer
17
+ * Default provider - always available
18
+ */ export class SmtpProvider {
19
+ /**
20
+ * Initialize the SMTP provider with configuration
21
+ */ async initialize(config) {
22
+ this.config = config;
23
+ this.logger.log(`Initializing SMTP: host=${config.host}, port=${config.port}, secure=${config.secure}, user=${config.auth?.user}`);
24
+ // Dynamic import nodemailer
25
+ const nodemailer = await import('nodemailer');
26
+ // Determine secure mode: port 465 = implicit TLS, port 587 = STARTTLS
27
+ const isSecure = config.secure ?? config.port === 465;
28
+ this.transporter = nodemailer.createTransport({
29
+ host: config.host,
30
+ port: config.port,
31
+ secure: isSecure,
32
+ auth: config.auth,
33
+ // Timeout settings to prevent hanging
34
+ connectionTimeout: 10000,
35
+ greetingTimeout: 10000,
36
+ socketTimeout: 30000,
37
+ // TLS options for better compatibility
38
+ tls: {
39
+ rejectUnauthorized: false,
40
+ minVersion: 'TLSv1.2'
41
+ }
42
+ });
43
+ this.logger.log(`SMTP transporter created with secure=${isSecure}`);
44
+ // Verify connection with timeout
45
+ try {
46
+ const verifyPromise = this.transporter.verify();
47
+ const timeoutPromise = new Promise((_, reject)=>setTimeout(()=>reject(new Error('SMTP verification timeout')), 10000));
48
+ await Promise.race([
49
+ verifyPromise,
50
+ timeoutPromise
51
+ ]);
52
+ this.logger.log(`SMTP Provider verified successfully: ${config.host}:${config.port}`);
53
+ } catch (error) {
54
+ this.logger.warn(`SMTP verification failed: ${error.message}`);
55
+ // Don't throw - allow initialization even if verification fails
56
+ // Connection will be retried on send
57
+ }
58
+ }
59
+ /**
60
+ * Send a single email
61
+ */ async sendEmail(options) {
62
+ if (!this.transporter) {
63
+ return {
64
+ success: false,
65
+ error: 'SMTP provider not initialized'
66
+ };
67
+ }
68
+ const toAddress = Array.isArray(options.to) ? options.to.join(', ') : options.to;
69
+ this.logger.log(`Sending email to: ${toAddress}, subject: ${options.subject}`);
70
+ try {
71
+ const mailOptions = {
72
+ from: options.fromName ? `"${options.fromName}" <${options.from}>` : options.from,
73
+ to: toAddress,
74
+ cc: options.cc ? Array.isArray(options.cc) ? options.cc.join(', ') : options.cc : undefined,
75
+ bcc: options.bcc ? Array.isArray(options.bcc) ? options.bcc.join(', ') : options.bcc : undefined,
76
+ subject: options.subject,
77
+ text: options.text,
78
+ html: options.html,
79
+ replyTo: options.replyTo,
80
+ attachments: options.attachments?.map((a)=>({
81
+ filename: a.filename,
82
+ content: a.content,
83
+ contentType: a.contentType,
84
+ encoding: a.encoding
85
+ }))
86
+ };
87
+ this.logger.log(`Mail options: from=${mailOptions.from}, to=${mailOptions.to}`);
88
+ // Send with timeout
89
+ const sendPromise = this.transporter.sendMail(mailOptions);
90
+ const timeoutPromise = new Promise((_, reject)=>setTimeout(()=>reject(new Error('SMTP send timeout after 30 seconds')), 30000));
91
+ const info = await Promise.race([
92
+ sendPromise,
93
+ timeoutPromise
94
+ ]);
95
+ this.logger.log(`Email sent via SMTP: ${info.messageId}`);
96
+ return {
97
+ success: true,
98
+ messageId: info.messageId
99
+ };
100
+ } catch (error) {
101
+ this.logger.error(`SMTP send failed: ${error.message}`, error.stack);
102
+ return {
103
+ success: false,
104
+ error: error.message
105
+ };
106
+ }
107
+ }
108
+ /**
109
+ * Send multiple emails (batch)
110
+ */ async sendBulkEmails(options) {
111
+ return Promise.all(options.map((opt)=>this.sendEmail(opt)));
112
+ }
113
+ /**
114
+ * Health check for the provider
115
+ */ async healthCheck() {
116
+ if (!this.transporter) return false;
117
+ try {
118
+ await this.transporter.verify();
119
+ return true;
120
+ } catch {
121
+ return false;
122
+ }
123
+ }
124
+ /**
125
+ * Close the transporter
126
+ */ async close() {
127
+ if (this.transporter) {
128
+ this.transporter.close();
129
+ this.transporter = null;
130
+ }
131
+ }
132
+ constructor(){
133
+ _define_property(this, "logger", new Logger(SmtpProvider.name));
134
+ _define_property(this, "transporter", null);
135
+ _define_property(this, "config", null);
136
+ }
137
+ }