@bernierllc/email 1.0.1 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +76 -217
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -0
- package/dist/simple-email-service.d.ts +58 -0
- package/dist/simple-email-service.d.ts.map +1 -0
- package/dist/simple-email-service.js +416 -0
- package/dist/simple-email-service.js.map +1 -0
- package/dist/types.d.ts +311 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +33 -0
- package/dist/types.js.map +1 -0
- package/package.json +57 -22
- package/.eslintrc.json +0 -112
- package/.flake8 +0 -18
- package/.github/workflows/ci.yml +0 -300
- package/EXTRACTION_SUMMARY.md +0 -265
- package/IMPLEMENTATION_STATUS.md +0 -159
- package/OPEN_SOURCE_SETUP.md +0 -420
- package/PACKAGE_USAGE.md +0 -471
- package/examples/fastapi-example/main.py +0 -257
- package/examples/nextjs-example/next-env.d.ts +0 -13
- package/examples/nextjs-example/package.json +0 -26
- package/examples/nextjs-example/pages/admin/templates.tsx +0 -157
- package/examples/nextjs-example/tsconfig.json +0 -28
- package/packages/core/package.json +0 -70
- package/packages/core/rollup.config.js +0 -37
- package/packages/core/specification.md +0 -416
- package/packages/core/src/adapters/supabase.ts +0 -291
- package/packages/core/src/core/scheduler.ts +0 -356
- package/packages/core/src/core/template-manager.ts +0 -388
- package/packages/core/src/index.ts +0 -30
- package/packages/core/src/providers/base.ts +0 -104
- package/packages/core/src/providers/sendgrid.ts +0 -368
- package/packages/core/src/types/provider.ts +0 -91
- package/packages/core/src/types/scheduled.ts +0 -78
- package/packages/core/src/types/template.ts +0 -97
- package/packages/core/tsconfig.json +0 -23
- package/packages/python/README.md +0 -106
- package/packages/python/email_template_manager/__init__.py +0 -66
- package/packages/python/email_template_manager/config.py +0 -98
- package/packages/python/email_template_manager/core/magic_links.py +0 -245
- package/packages/python/email_template_manager/core/manager.py +0 -344
- package/packages/python/email_template_manager/core/scheduler.py +0 -473
- package/packages/python/email_template_manager/exceptions.py +0 -67
- package/packages/python/email_template_manager/models/magic_link.py +0 -59
- package/packages/python/email_template_manager/models/scheduled.py +0 -78
- package/packages/python/email_template_manager/models/template.py +0 -90
- package/packages/python/email_template_manager/providers/aws_ses.py +0 -44
- package/packages/python/email_template_manager/providers/base.py +0 -94
- package/packages/python/email_template_manager/providers/sendgrid.py +0 -325
- package/packages/python/email_template_manager/providers/smtp.py +0 -44
- package/packages/python/pyproject.toml +0 -133
- package/packages/python/setup.py +0 -93
- package/packages/python/specification.md +0 -930
- package/packages/react/README.md +0 -13
- package/packages/react/package.json +0 -105
- package/packages/react/rollup.config.js +0 -37
- package/packages/react/specification.md +0 -569
- package/packages/react/src/index.ts +0 -20
- package/packages/react/tsconfig.json +0 -24
- package/plans/email-template-manager_app-admin.md +0 -590
- package/src/index.js +0 -1
- package/test_package.py +0 -125
|
@@ -1,388 +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
|
-
/**
|
|
10
|
-
* Email Template Manager - Core template management
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import { Liquid } from 'liquidjs';
|
|
14
|
-
import { EmailTemplate, RenderedTemplate, TemplateFilters, ValidationResult } from '../types/template';
|
|
15
|
-
|
|
16
|
-
export interface DatabaseConfig {
|
|
17
|
-
type: 'memory' | 'supabase' | 'postgresql' | 'mysql';
|
|
18
|
-
connectionString?: string;
|
|
19
|
-
supabaseUrl?: string;
|
|
20
|
-
supabaseKey?: string;
|
|
21
|
-
tableName?: string;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export class EmailTemplateManager {
|
|
25
|
-
private templates: Map<string, EmailTemplate> = new Map();
|
|
26
|
-
private liquid: Liquid;
|
|
27
|
-
private dbConfig: DatabaseConfig;
|
|
28
|
-
private supabaseAdapter?: SupabaseEmailAdapter;
|
|
29
|
-
|
|
30
|
-
constructor(config: { database: DatabaseConfig }) {
|
|
31
|
-
this.dbConfig = config.database;
|
|
32
|
-
this.liquid = new Liquid({
|
|
33
|
-
strictFilters: false,
|
|
34
|
-
strictVariables: false,
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
// Initialize database connection
|
|
38
|
-
this.initializeDatabase();
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
private async initializeDatabase() {
|
|
42
|
-
if (this.dbConfig.type === 'memory') {
|
|
43
|
-
// Use in-memory storage for development/testing
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
if (this.dbConfig.type === 'supabase') {
|
|
48
|
-
if (!this.dbConfig.supabaseUrl || !this.dbConfig.supabaseKey) {
|
|
49
|
-
throw new Error('Supabase URL and key are required for Supabase database type');
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
this.supabaseAdapter = new SupabaseEmailAdapter({
|
|
53
|
-
url: this.dbConfig.supabaseUrl,
|
|
54
|
-
key: this.dbConfig.supabaseKey,
|
|
55
|
-
templatesTable: this.dbConfig.tableName,
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// For other database types, implement connection logic
|
|
60
|
-
// This will be extended based on the database type
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Create a new email template
|
|
65
|
-
*/
|
|
66
|
-
async createTemplate(template: Omit<EmailTemplate, 'id' | 'createdAt' | 'updatedAt'>): Promise<EmailTemplate> {
|
|
67
|
-
const newTemplate: EmailTemplate = {
|
|
68
|
-
...template,
|
|
69
|
-
id: this.generateId(),
|
|
70
|
-
createdAt: new Date(),
|
|
71
|
-
updatedAt: new Date(),
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
// Validate template
|
|
75
|
-
const validation = this.validateTemplate(newTemplate);
|
|
76
|
-
if (!validation.isValid) {
|
|
77
|
-
throw new Error(`Template validation failed: ${validation.errors.map(e => e.message).join(', ')}`);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Store template
|
|
81
|
-
if (this.dbConfig.type === 'memory') {
|
|
82
|
-
this.templates.set(newTemplate.id!, newTemplate);
|
|
83
|
-
} else {
|
|
84
|
-
await this.saveTemplateToDatabase(newTemplate);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
return newTemplate;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Get template by ID
|
|
92
|
-
*/
|
|
93
|
-
async getTemplate(id: string): Promise<EmailTemplate | null> {
|
|
94
|
-
if (this.dbConfig.type === 'memory') {
|
|
95
|
-
return this.templates.get(id) || null;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
return await this.loadTemplateFromDatabase(id);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Get template by name
|
|
103
|
-
*/
|
|
104
|
-
async getTemplateByName(name: string): Promise<EmailTemplate | null> {
|
|
105
|
-
if (this.dbConfig.type === 'memory') {
|
|
106
|
-
for (const template of this.templates.values()) {
|
|
107
|
-
if (template.name === name) {
|
|
108
|
-
return template;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
return null;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
return await this.loadTemplateFromDatabaseByName(name);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Update an existing template
|
|
119
|
-
*/
|
|
120
|
-
async updateTemplate(id: string, updates: Partial<EmailTemplate>): Promise<EmailTemplate> {
|
|
121
|
-
const existingTemplate = await this.getTemplate(id);
|
|
122
|
-
if (!existingTemplate) {
|
|
123
|
-
throw new Error(`Template with id ${id} not found`);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const updatedTemplate: EmailTemplate = {
|
|
127
|
-
...existingTemplate,
|
|
128
|
-
...updates,
|
|
129
|
-
id: existingTemplate.id,
|
|
130
|
-
updatedAt: new Date(),
|
|
131
|
-
};
|
|
132
|
-
|
|
133
|
-
// Validate updated template
|
|
134
|
-
const validation = this.validateTemplate(updatedTemplate);
|
|
135
|
-
if (!validation.isValid) {
|
|
136
|
-
throw new Error(`Template validation failed: ${validation.errors.map(e => e.message).join(', ')}`);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// Store updated template
|
|
140
|
-
if (this.dbConfig.type === 'memory') {
|
|
141
|
-
this.templates.set(id, updatedTemplate);
|
|
142
|
-
} else {
|
|
143
|
-
await this.saveTemplateToDatabase(updatedTemplate);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
return updatedTemplate;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Delete a template
|
|
151
|
-
*/
|
|
152
|
-
async deleteTemplate(id: string): Promise<boolean> {
|
|
153
|
-
if (this.dbConfig.type === 'memory') {
|
|
154
|
-
return this.templates.delete(id);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
return await this.deleteTemplateFromDatabase(id);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/**
|
|
161
|
-
* List templates with optional filtering
|
|
162
|
-
*/
|
|
163
|
-
async listTemplates(filters?: TemplateFilters): Promise<EmailTemplate[]> {
|
|
164
|
-
let templates: EmailTemplate[];
|
|
165
|
-
|
|
166
|
-
if (this.dbConfig.type === 'memory') {
|
|
167
|
-
templates = Array.from(this.templates.values());
|
|
168
|
-
} else {
|
|
169
|
-
templates = await this.loadTemplatesFromDatabase(filters);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// Apply filters if provided
|
|
173
|
-
if (filters) {
|
|
174
|
-
templates = this.applyFilters(templates, filters);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
return templates;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* Render a template with variables
|
|
182
|
-
*/
|
|
183
|
-
async renderTemplate(templateId: string, variables: Record<string, any>): Promise<RenderedTemplate> {
|
|
184
|
-
const template = await this.getTemplate(templateId);
|
|
185
|
-
if (!template) {
|
|
186
|
-
throw new Error(`Template with id ${templateId} not found`);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
return this.renderTemplateObject(template, variables);
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* Render a template by name with variables
|
|
194
|
-
*/
|
|
195
|
-
async renderTemplateByName(templateName: string, variables: Record<string, any>): Promise<RenderedTemplate> {
|
|
196
|
-
const template = await this.getTemplateByName(templateName);
|
|
197
|
-
if (!template) {
|
|
198
|
-
throw new Error(`Template with name ${templateName} not found`);
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
return this.renderTemplateObject(template, variables);
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* Render a template object with variables
|
|
206
|
-
*/
|
|
207
|
-
private async renderTemplateObject(template: EmailTemplate, variables: Record<string, any>): Promise<RenderedTemplate> {
|
|
208
|
-
try {
|
|
209
|
-
// Validate required variables
|
|
210
|
-
const missingVariables = template.variables
|
|
211
|
-
.filter(v => v.required && !(v.name in variables))
|
|
212
|
-
.map(v => v.name);
|
|
213
|
-
|
|
214
|
-
if (missingVariables.length > 0) {
|
|
215
|
-
throw new Error(`Missing required variables: ${missingVariables.join(', ')}`);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// Render template parts
|
|
219
|
-
const subject = await this.liquid.parseAndRender(template.subject, variables);
|
|
220
|
-
const htmlContent = await this.liquid.parseAndRender(template.htmlBody, variables);
|
|
221
|
-
const textContent = await this.liquid.parseAndRender(template.textBody, variables);
|
|
222
|
-
|
|
223
|
-
return {
|
|
224
|
-
subject,
|
|
225
|
-
htmlContent,
|
|
226
|
-
textContent,
|
|
227
|
-
variables,
|
|
228
|
-
templateId: template.id!,
|
|
229
|
-
};
|
|
230
|
-
} catch (error) {
|
|
231
|
-
throw new Error(`Template rendering failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
/**
|
|
236
|
-
* Validate a template
|
|
237
|
-
*/
|
|
238
|
-
private validateTemplate(template: EmailTemplate): ValidationResult {
|
|
239
|
-
const errors: any[] = [];
|
|
240
|
-
const warnings: any[] = [];
|
|
241
|
-
|
|
242
|
-
// Required fields
|
|
243
|
-
if (!template.name) {
|
|
244
|
-
errors.push({ field: 'name', message: 'Template name is required', code: 'REQUIRED' });
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
if (!template.subject) {
|
|
248
|
-
errors.push({ field: 'subject', message: 'Template subject is required', code: 'REQUIRED' });
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
if (!template.htmlBody && !template.textBody) {
|
|
252
|
-
errors.push({ field: 'body', message: 'Template must have either HTML or text body', code: 'REQUIRED' });
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
// Variable validation
|
|
256
|
-
template.variables.forEach((variable, index) => {
|
|
257
|
-
if (!variable.name) {
|
|
258
|
-
errors.push({
|
|
259
|
-
field: `variables[${index}].name`,
|
|
260
|
-
message: 'Variable name is required',
|
|
261
|
-
code: 'REQUIRED'
|
|
262
|
-
});
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
if (!variable.type) {
|
|
266
|
-
errors.push({
|
|
267
|
-
field: `variables[${index}].type`,
|
|
268
|
-
message: 'Variable type is required',
|
|
269
|
-
code: 'REQUIRED'
|
|
270
|
-
});
|
|
271
|
-
}
|
|
272
|
-
});
|
|
273
|
-
|
|
274
|
-
// Check for duplicate variable names
|
|
275
|
-
const variableNames = template.variables.map(v => v.name).filter(Boolean);
|
|
276
|
-
const duplicates = variableNames.filter((name, index) => variableNames.indexOf(name) !== index);
|
|
277
|
-
if (duplicates.length > 0) {
|
|
278
|
-
errors.push({
|
|
279
|
-
field: 'variables',
|
|
280
|
-
message: `Duplicate variable names: ${duplicates.join(', ')}`,
|
|
281
|
-
code: 'DUPLICATE'
|
|
282
|
-
});
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
return {
|
|
286
|
-
isValid: errors.length === 0,
|
|
287
|
-
errors,
|
|
288
|
-
warnings,
|
|
289
|
-
};
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
/**
|
|
293
|
-
* Apply filters to template list
|
|
294
|
-
*/
|
|
295
|
-
private applyFilters(templates: EmailTemplate[], filters: TemplateFilters): EmailTemplate[] {
|
|
296
|
-
return templates.filter(template => {
|
|
297
|
-
if (filters.categoryId && template.categoryId !== filters.categoryId) {
|
|
298
|
-
return false;
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
if (filters.isActive !== undefined && template.isActive !== filters.isActive) {
|
|
302
|
-
return false;
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
if (filters.tags && filters.tags.length > 0) {
|
|
306
|
-
const hasMatchingTag = filters.tags.some(tag => template.tags.includes(tag));
|
|
307
|
-
if (!hasMatchingTag) {
|
|
308
|
-
return false;
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
if (filters.search) {
|
|
313
|
-
const searchLower = filters.search.toLowerCase();
|
|
314
|
-
const matchesSearch =
|
|
315
|
-
template.name.toLowerCase().includes(searchLower) ||
|
|
316
|
-
template.subject.toLowerCase().includes(searchLower) ||
|
|
317
|
-
template.tags.some(tag => tag.toLowerCase().includes(searchLower));
|
|
318
|
-
|
|
319
|
-
if (!matchesSearch) {
|
|
320
|
-
return false;
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
if (filters.createdBy && template.createdBy !== filters.createdBy) {
|
|
325
|
-
return false;
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
if (filters.createdAfter && template.createdAt && template.createdAt < filters.createdAfter) {
|
|
329
|
-
return false;
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
if (filters.createdBefore && template.createdAt && template.createdAt > filters.createdBefore) {
|
|
333
|
-
return false;
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
return true;
|
|
337
|
-
});
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
/**
|
|
341
|
-
* Generate a unique ID
|
|
342
|
-
*/
|
|
343
|
-
private generateId(): string {
|
|
344
|
-
return `tpl_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
// Database methods (to be implemented based on database type)
|
|
348
|
-
private async saveTemplateToDatabase(template: EmailTemplate): Promise<void> {
|
|
349
|
-
if (this.dbConfig.type === 'supabase' && this.supabaseAdapter) {
|
|
350
|
-
await this.supabaseAdapter.saveTemplate(template);
|
|
351
|
-
return;
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
throw new Error(`Database operations not implemented for type: ${this.dbConfig.type}`);
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
private async loadTemplateFromDatabase(id: string): Promise<EmailTemplate | null> {
|
|
358
|
-
if (this.dbConfig.type === 'supabase' && this.supabaseAdapter) {
|
|
359
|
-
return await this.supabaseAdapter.loadTemplate(id);
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
throw new Error(`Database operations not implemented for type: ${this.dbConfig.type}`);
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
private async loadTemplateFromDatabaseByName(name: string): Promise<EmailTemplate | null> {
|
|
366
|
-
if (this.dbConfig.type === 'supabase' && this.supabaseAdapter) {
|
|
367
|
-
return await this.supabaseAdapter.loadTemplateByName(name);
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
throw new Error(`Database operations not implemented for type: ${this.dbConfig.type}`);
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
private async loadTemplatesFromDatabase(filters?: TemplateFilters): Promise<EmailTemplate[]> {
|
|
374
|
-
if (this.dbConfig.type === 'supabase' && this.supabaseAdapter) {
|
|
375
|
-
return await this.supabaseAdapter.loadTemplates(filters);
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
throw new Error(`Database operations not implemented for type: ${this.dbConfig.type}`);
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
private async deleteTemplateFromDatabase(id: string): Promise<boolean> {
|
|
382
|
-
if (this.dbConfig.type === 'supabase' && this.supabaseAdapter) {
|
|
383
|
-
return await this.supabaseAdapter.deleteTemplate(id);
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
throw new Error(`Database operations not implemented for type: ${this.dbConfig.type}`);
|
|
387
|
-
}
|
|
388
|
-
}
|
|
@@ -1,30 +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
|
-
/**
|
|
10
|
-
* Email Template Manager - Core Library
|
|
11
|
-
* Main entry point for the email template management system
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
// Core types
|
|
15
|
-
export * from './types/provider';
|
|
16
|
-
export * from './types/scheduled';
|
|
17
|
-
export * from './types/template';
|
|
18
|
-
|
|
19
|
-
// Core classes
|
|
20
|
-
export { EmailScheduler } from './core/scheduler';
|
|
21
|
-
export { EmailTemplateManager } from './core/template-manager';
|
|
22
|
-
|
|
23
|
-
// Database adapters
|
|
24
|
-
export { SupabaseEmailAdapter } from './adapters/supabase';
|
|
25
|
-
|
|
26
|
-
// Base provider class and types
|
|
27
|
-
export * from './providers/base';
|
|
28
|
-
|
|
29
|
-
// SendGrid provider
|
|
30
|
-
export { SendGridProvider } from './providers/sendgrid';
|
|
@@ -1,104 +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
|
-
/**
|
|
10
|
-
* Base EmailProvider abstract class
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import { EmailMessage, SendResult, DeliveryStatus, WebhookEvent, EmailProviderCapabilities } from '../types/provider';
|
|
14
|
-
|
|
15
|
-
// Re-export types for consumers
|
|
16
|
-
export { EmailMessage, SendResult, DeliveryStatus, WebhookEvent, EmailProviderCapabilities };
|
|
17
|
-
|
|
18
|
-
export abstract class EmailProvider {
|
|
19
|
-
protected config: Record<string, any>;
|
|
20
|
-
|
|
21
|
-
constructor(config: Record<string, any>) {
|
|
22
|
-
this.config = config;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Send a single email
|
|
27
|
-
*/
|
|
28
|
-
abstract sendEmail(email: EmailMessage): Promise<SendResult>;
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Send multiple emails
|
|
32
|
-
*/
|
|
33
|
-
abstract sendBatch(emails: EmailMessage[]): Promise<SendResult[]>;
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Get delivery status for a message
|
|
37
|
-
*/
|
|
38
|
-
abstract getDeliveryStatus(messageId: string): Promise<DeliveryStatus | null>;
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Process webhook events from the provider
|
|
42
|
-
*/
|
|
43
|
-
abstract processWebhook(payload: any, signature: string): Promise<WebhookEvent[]>;
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Validate provider configuration
|
|
47
|
-
*/
|
|
48
|
-
validateConfiguration(): boolean {
|
|
49
|
-
return true;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Get provider name
|
|
54
|
-
*/
|
|
55
|
-
getProviderName(): string {
|
|
56
|
-
return this.constructor.name.replace('Provider', '').toLowerCase();
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Get provider capabilities
|
|
61
|
-
*/
|
|
62
|
-
getCapabilities(): EmailProviderCapabilities {
|
|
63
|
-
return {
|
|
64
|
-
supportsBatch: true,
|
|
65
|
-
supportsTemplates: false,
|
|
66
|
-
supportsWebhooks: false,
|
|
67
|
-
supportsAttachments: true,
|
|
68
|
-
supportsDeliveryTracking: false,
|
|
69
|
-
maxBatchSize: 100,
|
|
70
|
-
maxAttachmentSize: 25 * 1024 * 1024 // 25MB
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Create a template in the provider (if supported)
|
|
76
|
-
*/
|
|
77
|
-
async createTemplate?(templateData: {
|
|
78
|
-
name: string;
|
|
79
|
-
subject: string;
|
|
80
|
-
htmlContent: string;
|
|
81
|
-
textContent?: string;
|
|
82
|
-
}): Promise<string>;
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Send an email using a provider template (if supported)
|
|
86
|
-
*/
|
|
87
|
-
async sendTemplateEmail?(
|
|
88
|
-
templateId: string,
|
|
89
|
-
toEmail: string,
|
|
90
|
-
templateData: Record<string, any>,
|
|
91
|
-
toName?: string
|
|
92
|
-
): Promise<SendResult>;
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Test the provider connection
|
|
96
|
-
*/
|
|
97
|
-
async testConnection(): Promise<boolean> {
|
|
98
|
-
try {
|
|
99
|
-
return this.validateConfiguration();
|
|
100
|
-
} catch {
|
|
101
|
-
return false;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|