@bernierllc/email-sendgrid-plugin 1.4.5 → 1.6.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 (43) hide show
  1. package/dist/api/errors.js +5 -0
  2. package/dist/api/rate-limiter.js +3 -1
  3. package/dist/api/sendgrid-client.js +4 -1
  4. package/dist/converters/template-converter.js +1 -0
  5. package/dist/errors.d.ts +11 -0
  6. package/dist/errors.js +24 -0
  7. package/dist/index.d.ts +1 -0
  8. package/dist/index.js +4 -1
  9. package/dist/plugin/sender-manager.js +6 -1
  10. package/dist/plugin/sendgrid-plugin.js +15 -4
  11. package/dist/plugin/sync-manager.js +4 -0
  12. package/dist/plugin/template-manager.js +5 -0
  13. package/dist/safety/ownership-validator.js +3 -1
  14. package/dist/safety/prefix-enforcer.js +1 -0
  15. package/package.json +8 -4
  16. package/.eslintrc.js +0 -27
  17. package/CHANGELOG.md +0 -117
  18. package/__tests__/api/rate-limiter.test.ts +0 -125
  19. package/__tests__/converters/attachment-header-mapping.test.ts +0 -337
  20. package/__tests__/converters/sender-converter.test.ts +0 -148
  21. package/__tests__/converters/template-converter.test.ts +0 -230
  22. package/__tests__/plugin/sender-manager.test.ts +0 -135
  23. package/__tests__/plugin/sendgrid-plugin.test.ts +0 -195
  24. package/__tests__/plugin/template-manager.test.ts +0 -110
  25. package/__tests__/safety/ownership-validator.test.ts +0 -130
  26. package/__tests__/safety/prefix-enforcer.test.ts +0 -136
  27. package/jest.config.js +0 -26
  28. package/src/api/errors.ts +0 -49
  29. package/src/api/rate-limiter.ts +0 -79
  30. package/src/api/sendgrid-client.ts +0 -86
  31. package/src/converters/sender-converter.ts +0 -138
  32. package/src/converters/template-converter.ts +0 -97
  33. package/src/index.ts +0 -61
  34. package/src/plugin/sender-manager.ts +0 -166
  35. package/src/plugin/sendgrid-plugin.ts +0 -316
  36. package/src/plugin/sync-manager.ts +0 -115
  37. package/src/plugin/template-manager.ts +0 -240
  38. package/src/safety/ownership-validator.ts +0 -55
  39. package/src/safety/prefix-enforcer.ts +0 -37
  40. package/src/types/config-types.ts +0 -34
  41. package/src/types/plugin-types.ts +0 -97
  42. package/src/types/sendgrid-types.ts +0 -98
  43. package/tsconfig.json +0 -18
@@ -1,97 +0,0 @@
1
- /*
2
- Copyright (c) 2025 Bernier LLC
3
-
4
- This file is licensed to the client under a limited-use license.
5
- The client may use and modify this code *only within the scope of the project it was delivered for*.
6
- Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
7
- */
8
-
9
- import {
10
- SendGridTemplateData,
11
- SendGridTemplateVersion
12
- } from '../types/sendgrid-types';
13
- import { ProviderTemplate, Template } from '../types/plugin-types';
14
- import { PrefixEnforcer } from '../safety/prefix-enforcer';
15
-
16
- export class TemplateConverter {
17
- private readonly prefixEnforcer: PrefixEnforcer;
18
-
19
- constructor(prefixEnforcer: PrefixEnforcer) {
20
- this.prefixEnforcer = prefixEnforcer;
21
- }
22
-
23
- convertToProviderTemplate(
24
- sendgridData: SendGridTemplateData,
25
- activeVersion?: SendGridTemplateVersion
26
- ): ProviderTemplate {
27
- // Find active version if not provided
28
- const version = activeVersion || this.findActiveVersion(sendgridData.versions || []);
29
-
30
- return {
31
- id: sendgridData.id,
32
- name: this.prefixEnforcer.stripPrefix(sendgridData.name),
33
- subject: version?.subject || '',
34
- htmlContent: version?.html_content || '',
35
- textContent: version?.plain_content,
36
- version: version?.name || '1.0.0',
37
- active: version?.active === 1,
38
- createdAt: new Date(sendgridData.updated_at),
39
- updatedAt: new Date(sendgridData.updated_at)
40
- };
41
- }
42
-
43
- convertToLocalTemplate(
44
- sendgridData: SendGridTemplateData,
45
- localId: string,
46
- activeVersion?: SendGridTemplateVersion
47
- ): Template {
48
- const providerTemplate = this.convertToProviderTemplate(sendgridData, activeVersion);
49
-
50
- return {
51
- id: localId,
52
- name: providerTemplate.name,
53
- subject: providerTemplate.subject,
54
- htmlContent: providerTemplate.htmlContent,
55
- textContent: providerTemplate.textContent,
56
- category: 'synced',
57
- tags: ['sendgrid', 'synced'],
58
- version: providerTemplate.version,
59
- isActive: providerTemplate.active,
60
- requiredVariables: [],
61
- providerTemplateIds: {
62
- sendgrid: sendgridData.id
63
- },
64
- lastSyncedAt: {
65
- sendgrid: new Date()
66
- },
67
- syncStatus: {
68
- sendgrid: 'success'
69
- },
70
- createdAt: providerTemplate.createdAt,
71
- updatedAt: providerTemplate.updatedAt,
72
- createdBy: 'sendgrid-sync',
73
- lastModifiedBy: 'sendgrid-sync'
74
- };
75
- }
76
-
77
- extractVariables(content: string): string[] {
78
- // Extract Handlebars-style variables: {{variableName}}
79
- const regex = /\{\{([^}]+)\}\}/g;
80
- const matches = content.matchAll(regex);
81
- const variables = new Set<string>();
82
-
83
- for (const match of matches) {
84
- const variable = match[1].trim();
85
- // Filter out Handlebars helpers
86
- if (!variable.startsWith('#') && !variable.startsWith('/')) {
87
- variables.add(variable);
88
- }
89
- }
90
-
91
- return Array.from(variables);
92
- }
93
-
94
- private findActiveVersion(versions: SendGridTemplateVersion[]): SendGridTemplateVersion | undefined {
95
- return versions.find(v => v.active === 1) || versions[0];
96
- }
97
- }
package/src/index.ts DELETED
@@ -1,61 +0,0 @@
1
- /*
2
- Copyright (c) 2025 Bernier LLC
3
-
4
- This file is licensed to the client under a limited-use license.
5
- The client may use and modify this code *only within the scope of the project it was delivered for*.
6
- Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
7
- */
8
-
9
- // Main plugin class
10
- export { SendGridTemplatePlugin, TemplateProviderPlugin } from './plugin/sendgrid-plugin';
11
-
12
- // Plugin components
13
- export { TemplateManager } from './plugin/template-manager';
14
- export { SenderManager } from './plugin/sender-manager';
15
- export { SyncManager } from './plugin/sync-manager';
16
-
17
- // API components
18
- export { SendGridClient } from './api/sendgrid-client';
19
- export { SimpleRateLimiter } from './api/rate-limiter';
20
-
21
- // Safety components
22
- export { OwnershipValidator, SafetyMode } from './safety/ownership-validator';
23
- export { PrefixEnforcer } from './safety/prefix-enforcer';
24
-
25
- // Converters
26
- export { TemplateConverter } from './converters/template-converter';
27
- export { SenderConverter } from './converters/sender-converter';
28
-
29
- // Errors
30
- export {
31
- SendGridApiError,
32
- TemplateNotFoundError,
33
- UnauthorizedTemplateAccessError,
34
- TemplateConflictError,
35
- RateLimitError
36
- } from './api/errors';
37
-
38
- // Types
39
- export type { SendGridPluginConfig, ApiRequestOptions, ApiResponse } from './types/config-types';
40
- export type {
41
- PluginConfig,
42
- ProviderCapability,
43
- Template,
44
- ProviderTemplate,
45
- TemplateVersion,
46
- SyncResult,
47
- ValidationResult,
48
- DateRange,
49
- TemplateAnalytics,
50
- FeatureConfig
51
- } from './types/plugin-types';
52
- export type {
53
- VerifiedSender,
54
- SenderValidationResult,
55
- SendGridTemplateData,
56
- SendGridTemplateVersion,
57
- SendGridVerifiedSenderData,
58
- EmailAttachment,
59
- SendGridAttachment,
60
- SendGridMailPayload
61
- } from './types/sendgrid-types';
@@ -1,166 +0,0 @@
1
- /*
2
- Copyright (c) 2025 Bernier LLC
3
-
4
- This file is licensed to the client under a limited-use license.
5
- The client may use and modify this code *only within the scope of the project it was delivered for*.
6
- Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
7
- */
8
-
9
- import { SendGridClient } from '../api/sendgrid-client';
10
- import { SendGridApiError } from '../api/errors';
11
- import { SenderConverter } from '../converters/sender-converter';
12
- import {
13
- VerifiedSender,
14
- SenderValidationResult,
15
- SendGridVerifiedSenderData
16
- } from '../types/sendgrid-types';
17
-
18
- export class SenderManager {
19
- private sendersCache?: VerifiedSender[];
20
- private cacheTimestamp?: number;
21
- private readonly cacheTTL = 300000; // 5 minutes
22
-
23
- constructor(
24
- private readonly client: SendGridClient,
25
- private readonly converter: SenderConverter,
26
- private readonly logger?: {
27
- info: (message: string) => void;
28
- debug: (message: string) => void;
29
- }
30
- ) {}
31
-
32
- async getVerifiedSenders(useCache = true): Promise<VerifiedSender[]> {
33
- // Return cached data if valid
34
- if (
35
- useCache &&
36
- this.sendersCache &&
37
- this.cacheTimestamp &&
38
- Date.now() - this.cacheTimestamp < this.cacheTTL
39
- ) {
40
- this.logger?.debug('Returning cached verified senders');
41
- return this.sendersCache;
42
- }
43
-
44
- const response = await this.client.request('/verified_senders', {
45
- method: 'GET'
46
- });
47
-
48
- if (!this.client.isSuccessResponse(response)) {
49
- throw new SendGridApiError(
50
- 'Failed to fetch verified senders',
51
- response.statusCode,
52
- response.body
53
- );
54
- }
55
-
56
- const data = this.client.parseResponseBody<{ results: SendGridVerifiedSenderData[] }>(response);
57
- const senders = data.results.map(sender => this.converter.convertToVerifiedSender(sender));
58
-
59
- // Update cache
60
- this.sendersCache = senders;
61
- this.cacheTimestamp = Date.now();
62
-
63
- this.logger?.info(`Fetched ${senders.length} verified senders from SendGrid`);
64
-
65
- return senders;
66
- }
67
-
68
- async validateSender(email: string): Promise<SenderValidationResult> {
69
- const verifiedSenders = await this.getVerifiedSenders();
70
- const emailDomain = this.converter.extractDomain(email);
71
-
72
- // Check for exact match
73
- const exactMatch = verifiedSenders.find(
74
- sender => sender.email.toLowerCase() === email.toLowerCase() && sender.isVerified
75
- );
76
-
77
- if (exactMatch) {
78
- return {
79
- isValid: true,
80
- verifiedSender: exactMatch,
81
- domain: emailDomain,
82
- domainMatches: [exactMatch],
83
- errors: [],
84
- suggestions: []
85
- };
86
- }
87
-
88
- // Check for domain matches
89
- const domainMatches = verifiedSenders.filter(sender => {
90
- const senderDomain = this.converter.extractDomain(sender.email);
91
- return this.converter.isDomainMatch(emailDomain, senderDomain) && sender.isVerified;
92
- });
93
-
94
- if (domainMatches.length > 0) {
95
- return {
96
- isValid: true,
97
- domain: emailDomain,
98
- domainMatches,
99
- errors: [],
100
- suggestions: domainMatches.map(sender => sender.email)
101
- };
102
- }
103
-
104
- // No matches found
105
- return {
106
- isValid: false,
107
- domain: emailDomain,
108
- domainMatches: [],
109
- errors: [`No verified sender found for email ${email} or domain ${emailDomain}`],
110
- suggestions: this.getSenderSuggestions(email, verifiedSenders)
111
- };
112
- }
113
-
114
- async selectBestSender(fromEmail: string): Promise<VerifiedSender | null> {
115
- const validation = await this.validateSender(fromEmail);
116
-
117
- if (!validation.isValid || validation.domainMatches.length === 0) {
118
- return null;
119
- }
120
-
121
- // Prefer exact match
122
- if (validation.verifiedSender) {
123
- return validation.verifiedSender;
124
- }
125
-
126
- // Fall back to best domain match
127
- // Priority: most recently verified, then alphabetical
128
- return validation.domainMatches.sort((a, b) => {
129
- if (a.lastVerifiedAt && b.lastVerifiedAt) {
130
- return b.lastVerifiedAt.getTime() - a.lastVerifiedAt.getTime();
131
- }
132
- if (a.lastVerifiedAt && !b.lastVerifiedAt) return -1;
133
- if (!a.lastVerifiedAt && b.lastVerifiedAt) return 1;
134
- return a.email.localeCompare(b.email);
135
- })[0];
136
- }
137
-
138
- clearCache(): void {
139
- this.sendersCache = undefined;
140
- this.cacheTimestamp = undefined;
141
- this.logger?.debug('Cleared verified senders cache');
142
- }
143
-
144
- private getSenderSuggestions(email: string, senders: VerifiedSender[]): string[] {
145
- const emailDomain = this.converter.extractDomain(email);
146
-
147
- // Find senders with similar domains
148
- const suggestions: string[] = [];
149
-
150
- for (const sender of senders) {
151
- if (!sender.isVerified) continue;
152
-
153
- const senderDomain = this.converter.extractDomain(sender.email);
154
-
155
- // Check for partial domain matches
156
- if (
157
- emailDomain.includes(senderDomain) ||
158
- senderDomain.includes(emailDomain)
159
- ) {
160
- suggestions.push(sender.email);
161
- }
162
- }
163
-
164
- return suggestions.slice(0, 5); // Limit to 5 suggestions
165
- }
166
- }
@@ -1,316 +0,0 @@
1
- /*
2
- Copyright (c) 2025 Bernier LLC
3
-
4
- This file is licensed to the client under a limited-use license.
5
- The client may use and modify this code *only within the scope of the project it was delivered for*.
6
- Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
7
- */
8
-
9
- import { SendGridPluginConfig } from '../types/config-types';
10
- import {
11
- PluginConfig,
12
- ProviderCapability,
13
- Template,
14
- ProviderTemplate,
15
- TemplateVersion,
16
- SyncResult,
17
- ValidationResult,
18
- DateRange,
19
- TemplateAnalytics,
20
- FeatureConfig
21
- } from '../types/plugin-types';
22
- import { VerifiedSender, SenderValidationResult } from '../types/sendgrid-types';
23
- import { SendGridClient } from '../api/sendgrid-client';
24
- import { SimpleRateLimiter } from '../api/rate-limiter';
25
- import { OwnershipValidator } from '../safety/ownership-validator';
26
- import { PrefixEnforcer } from '../safety/prefix-enforcer';
27
- import { TemplateConverter } from '../converters/template-converter';
28
- import { SenderConverter } from '../converters/sender-converter';
29
- import { TemplateManager } from './template-manager';
30
- import { SenderManager } from './sender-manager';
31
- import { SyncManager } from './sync-manager';
32
-
33
- export interface TemplateProviderPlugin {
34
- readonly name: string;
35
- readonly version: string;
36
- readonly providerId: string;
37
-
38
- initialize(config: PluginConfig): Promise<void>;
39
- destroy(): Promise<void>;
40
-
41
- // Template operations
42
- createTemplate(template: Template): Promise<string>;
43
- updateTemplate(providerTemplateId: string, template: Template): Promise<void>;
44
- deleteTemplate(providerTemplateId: string): Promise<void>;
45
- getTemplate(providerTemplateId: string): Promise<ProviderTemplate>;
46
- listTemplates(): Promise<ProviderTemplate[]>;
47
-
48
- // Synchronization
49
- syncToProvider(template: Template): Promise<SyncResult>;
50
- syncFromProvider(providerTemplateId: string): Promise<Template>;
51
-
52
- // Capabilities
53
- getCapabilities(): ProviderCapability[];
54
- supportsFeature(feature: string): boolean;
55
- getFeatureConfig(feature: string): FeatureConfig | null;
56
- }
57
-
58
- export class SendGridTemplatePlugin implements TemplateProviderPlugin {
59
- readonly name = 'sendgrid-templates';
60
- readonly version = '1.0.0';
61
- readonly providerId = 'sendgrid';
62
-
63
- private client!: SendGridClient;
64
- private rateLimiter?: SimpleRateLimiter;
65
- private ownershipValidator!: OwnershipValidator;
66
- private prefixEnforcer!: PrefixEnforcer;
67
- private templateConverter!: TemplateConverter;
68
- private senderConverter!: SenderConverter;
69
- private templateManager!: TemplateManager;
70
- private senderManager!: SenderManager;
71
- private syncManager!: SyncManager;
72
-
73
- private readonly config: SendGridPluginConfig;
74
- private readonly logger?: {
75
- info: (message: string) => void;
76
- debug: (message: string) => void;
77
- error: (message: string) => void;
78
- };
79
-
80
- private initialized = false;
81
-
82
- constructor(config: SendGridPluginConfig) {
83
- this.config = config;
84
- }
85
-
86
- async initialize(__pluginConfig: PluginConfig): Promise<void> {
87
- if (this.initialized) {
88
- return;
89
- }
90
-
91
- // Initialize rate limiter if configured
92
- if (this.config.rateLimits) {
93
- this.rateLimiter = new SimpleRateLimiter({
94
- maxRequestsPerSecond: this.config.rateLimits.apiCallsPerSecond,
95
- maxRequestsPerMinute: this.config.rateLimits.templatesPerMinute
96
- });
97
- }
98
-
99
- // Initialize SendGrid client
100
- this.client = new SendGridClient({
101
- apiKey: this.config.apiKey,
102
- rateLimiter: this.rateLimiter,
103
- logger: this.logger
104
- });
105
-
106
- // Initialize safety components
107
- this.ownershipValidator = new OwnershipValidator(
108
- this.config.safetyMode,
109
- this.logger
110
- );
111
- this.prefixEnforcer = new PrefixEnforcer(this.config.templatePrefix);
112
-
113
- // Initialize converters
114
- this.templateConverter = new TemplateConverter(this.prefixEnforcer);
115
- this.senderConverter = new SenderConverter();
116
-
117
- // Initialize managers
118
- this.templateManager = new TemplateManager(
119
- this.client,
120
- this.ownershipValidator,
121
- this.prefixEnforcer,
122
- this.templateConverter,
123
- this.logger
124
- );
125
-
126
- this.senderManager = new SenderManager(
127
- this.client,
128
- this.senderConverter,
129
- this.logger
130
- );
131
-
132
- this.syncManager = new SyncManager(
133
- this.templateManager,
134
- this.templateConverter,
135
- this.client,
136
- this.logger
137
- );
138
-
139
- // Load existing templates
140
- await this.loadExistingTemplates();
141
-
142
- this.initialized = true;
143
- this.logger?.info('SendGrid plugin initialized successfully');
144
- }
145
-
146
- async destroy(): Promise<void> {
147
- if (!this.initialized) {
148
- return;
149
- }
150
-
151
- this.ownershipValidator.clearRegistrations();
152
- this.senderManager.clearCache();
153
-
154
- this.initialized = false;
155
- this.logger?.info('SendGrid plugin destroyed');
156
- }
157
-
158
- // Template operations
159
- async createTemplate(template: Template): Promise<string> {
160
- this.ensureInitialized();
161
- return this.templateManager.createTemplate(template);
162
- }
163
-
164
- async updateTemplate(providerTemplateId: string, template: Template): Promise<void> {
165
- this.ensureInitialized();
166
- return this.templateManager.updateTemplate(providerTemplateId, template);
167
- }
168
-
169
- async deleteTemplate(providerTemplateId: string): Promise<void> {
170
- this.ensureInitialized();
171
- return this.templateManager.deleteTemplate(providerTemplateId);
172
- }
173
-
174
- async getTemplate(providerTemplateId: string): Promise<ProviderTemplate> {
175
- this.ensureInitialized();
176
- return this.templateManager.getTemplate(providerTemplateId);
177
- }
178
-
179
- async listTemplates(): Promise<ProviderTemplate[]> {
180
- this.ensureInitialized();
181
- return this.templateManager.listTemplates();
182
- }
183
-
184
- // Synchronization
185
- async syncToProvider(template: Template): Promise<SyncResult> {
186
- this.ensureInitialized();
187
- return this.syncManager.syncToProvider(template);
188
- }
189
-
190
- async syncFromProvider(providerTemplateId: string): Promise<Template> {
191
- this.ensureInitialized();
192
- return this.syncManager.syncFromProvider(providerTemplateId);
193
- }
194
-
195
- // SendGrid-specific features
196
- async getVerifiedSenders(): Promise<VerifiedSender[]> {
197
- this.ensureInitialized();
198
- return this.senderManager.getVerifiedSenders();
199
- }
200
-
201
- async validateSender(email: string): Promise<SenderValidationResult> {
202
- this.ensureInitialized();
203
- return this.senderManager.validateSender(email);
204
- }
205
-
206
- async selectBestSender(email: string): Promise<VerifiedSender | null> {
207
- this.ensureInitialized();
208
- return this.senderManager.selectBestSender(email);
209
- }
210
-
211
- async createTemplateVersion(
212
- templateId: string,
213
- version: TemplateVersion
214
- ): Promise<string> {
215
- this.ensureInitialized();
216
- return this.templateManager.createTemplateVersion(templateId, version);
217
- }
218
-
219
- async activateTemplateVersion(templateId: string, versionId: string): Promise<void> {
220
- this.ensureInitialized();
221
- return this.templateManager.activateTemplateVersion(templateId, versionId);
222
- }
223
-
224
- async getTemplateAnalytics(
225
- __templateId: string,
226
- __dateRange: DateRange
227
- ): Promise<TemplateAnalytics> {
228
- this.ensureInitialized();
229
- // TODO: Implement analytics fetching from SendGrid
230
- throw new Error('Template analytics not yet implemented');
231
- }
232
-
233
- async batchSyncTemplates(templates: Template[]): Promise<SyncResult[]> {
234
- this.ensureInitialized();
235
- return this.syncManager.batchSync(templates);
236
- }
237
-
238
- // Capabilities
239
- getCapabilities(): ProviderCapability[] {
240
- return [
241
- {
242
- feature: 'template-management',
243
- description: 'Full CRUD operations for SendGrid templates',
244
- methods: ['createTemplate', 'updateTemplate', 'deleteTemplate', 'getTemplate', 'listTemplates'],
245
- config: {
246
- supportsVersioning: true,
247
- supportsActivation: true,
248
- maxNameLength: 100,
249
- templatePrefix: this.config.templatePrefix
250
- }
251
- },
252
- {
253
- feature: 'sender-verification',
254
- description: 'SendGrid verified sender management',
255
- methods: ['getVerifiedSenders', 'validateSender', 'selectBestSender'],
256
- config: {
257
- supportsDomainMatching: true,
258
- supportsAutoSelection: true
259
- }
260
- },
261
- {
262
- feature: 'template-analytics',
263
- description: 'SendGrid template usage and performance analytics',
264
- methods: ['getTemplateAnalytics'],
265
- config: {
266
- supportsRealTime: false,
267
- historicalDataDays: 30,
268
- metricsAvailable: ['opens', 'clicks', 'bounces', 'deliveries']
269
- }
270
- },
271
- {
272
- feature: 'batch-operations',
273
- description: 'Efficient bulk template operations',
274
- methods: ['batchSyncTemplates'],
275
- config: {
276
- maxBatchSize: 50,
277
- supportsTransactions: false
278
- }
279
- }
280
- ];
281
- }
282
-
283
- supportsFeature(feature: string): boolean {
284
- return this.getCapabilities().some(cap => cap.feature === feature);
285
- }
286
-
287
- getFeatureConfig(feature: string): FeatureConfig | null {
288
- const capability = this.getCapabilities().find(cap => cap.feature === feature);
289
- return capability?.config || null;
290
- }
291
-
292
- validateRoute(__route: unknown): ValidationResult {
293
- // Placeholder validation
294
- return {
295
- isValid: true,
296
- errors: []
297
- };
298
- }
299
-
300
- // Private methods
301
- private async loadExistingTemplates(): Promise<void> {
302
- try {
303
- const templates = await this.templateManager.listTemplates();
304
- this.logger?.info(`Loaded ${templates.length} existing SendGrid templates`);
305
- } catch (error) {
306
- const errorMessage = error instanceof Error ? error.message : 'Unknown error';
307
- this.logger?.error(`Failed to load existing templates: ${errorMessage}`);
308
- }
309
- }
310
-
311
- private ensureInitialized(): void {
312
- if (!this.initialized) {
313
- throw new Error('SendGrid plugin not initialized. Call initialize() first.');
314
- }
315
- }
316
- }