@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.
- package/dist/api/errors.js +5 -0
- package/dist/api/rate-limiter.js +3 -1
- package/dist/api/sendgrid-client.js +4 -1
- package/dist/converters/template-converter.js +1 -0
- package/dist/errors.d.ts +11 -0
- package/dist/errors.js +24 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +4 -1
- package/dist/plugin/sender-manager.js +6 -1
- package/dist/plugin/sendgrid-plugin.js +15 -4
- package/dist/plugin/sync-manager.js +4 -0
- package/dist/plugin/template-manager.js +5 -0
- package/dist/safety/ownership-validator.js +3 -1
- package/dist/safety/prefix-enforcer.js +1 -0
- package/package.json +8 -4
- package/.eslintrc.js +0 -27
- package/CHANGELOG.md +0 -117
- package/__tests__/api/rate-limiter.test.ts +0 -125
- package/__tests__/converters/attachment-header-mapping.test.ts +0 -337
- package/__tests__/converters/sender-converter.test.ts +0 -148
- package/__tests__/converters/template-converter.test.ts +0 -230
- package/__tests__/plugin/sender-manager.test.ts +0 -135
- package/__tests__/plugin/sendgrid-plugin.test.ts +0 -195
- package/__tests__/plugin/template-manager.test.ts +0 -110
- package/__tests__/safety/ownership-validator.test.ts +0 -130
- package/__tests__/safety/prefix-enforcer.test.ts +0 -136
- package/jest.config.js +0 -26
- package/src/api/errors.ts +0 -49
- package/src/api/rate-limiter.ts +0 -79
- package/src/api/sendgrid-client.ts +0 -86
- package/src/converters/sender-converter.ts +0 -138
- package/src/converters/template-converter.ts +0 -97
- package/src/index.ts +0 -61
- package/src/plugin/sender-manager.ts +0 -166
- package/src/plugin/sendgrid-plugin.ts +0 -316
- package/src/plugin/sync-manager.ts +0 -115
- package/src/plugin/template-manager.ts +0 -240
- package/src/safety/ownership-validator.ts +0 -55
- package/src/safety/prefix-enforcer.ts +0 -37
- package/src/types/config-types.ts +0 -34
- package/src/types/plugin-types.ts +0 -97
- package/src/types/sendgrid-types.ts +0 -98
- 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
|
-
}
|