@bernierllc/email 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/README.md +76 -217
  2. package/dist/index.d.ts +3 -0
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +28 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/simple-email-service.d.ts +58 -0
  7. package/dist/simple-email-service.d.ts.map +1 -0
  8. package/dist/simple-email-service.js +416 -0
  9. package/dist/simple-email-service.js.map +1 -0
  10. package/dist/types.d.ts +311 -0
  11. package/dist/types.d.ts.map +1 -0
  12. package/dist/types.js +33 -0
  13. package/dist/types.js.map +1 -0
  14. package/package.json +53 -22
  15. package/.eslintrc.json +0 -112
  16. package/.flake8 +0 -18
  17. package/.github/workflows/ci.yml +0 -300
  18. package/EXTRACTION_SUMMARY.md +0 -265
  19. package/IMPLEMENTATION_STATUS.md +0 -159
  20. package/OPEN_SOURCE_SETUP.md +0 -420
  21. package/PACKAGE_USAGE.md +0 -471
  22. package/examples/fastapi-example/main.py +0 -257
  23. package/examples/nextjs-example/next-env.d.ts +0 -13
  24. package/examples/nextjs-example/package.json +0 -26
  25. package/examples/nextjs-example/pages/admin/templates.tsx +0 -157
  26. package/examples/nextjs-example/tsconfig.json +0 -28
  27. package/packages/core/package.json +0 -70
  28. package/packages/core/rollup.config.js +0 -37
  29. package/packages/core/specification.md +0 -416
  30. package/packages/core/src/adapters/supabase.ts +0 -291
  31. package/packages/core/src/core/scheduler.ts +0 -356
  32. package/packages/core/src/core/template-manager.ts +0 -388
  33. package/packages/core/src/index.ts +0 -30
  34. package/packages/core/src/providers/base.ts +0 -104
  35. package/packages/core/src/providers/sendgrid.ts +0 -368
  36. package/packages/core/src/types/provider.ts +0 -91
  37. package/packages/core/src/types/scheduled.ts +0 -78
  38. package/packages/core/src/types/template.ts +0 -97
  39. package/packages/core/tsconfig.json +0 -23
  40. package/packages/python/README.md +0 -106
  41. package/packages/python/email_template_manager/__init__.py +0 -66
  42. package/packages/python/email_template_manager/config.py +0 -98
  43. package/packages/python/email_template_manager/core/magic_links.py +0 -245
  44. package/packages/python/email_template_manager/core/manager.py +0 -344
  45. package/packages/python/email_template_manager/core/scheduler.py +0 -473
  46. package/packages/python/email_template_manager/exceptions.py +0 -67
  47. package/packages/python/email_template_manager/models/magic_link.py +0 -59
  48. package/packages/python/email_template_manager/models/scheduled.py +0 -78
  49. package/packages/python/email_template_manager/models/template.py +0 -90
  50. package/packages/python/email_template_manager/providers/aws_ses.py +0 -44
  51. package/packages/python/email_template_manager/providers/base.py +0 -94
  52. package/packages/python/email_template_manager/providers/sendgrid.py +0 -325
  53. package/packages/python/email_template_manager/providers/smtp.py +0 -44
  54. package/packages/python/pyproject.toml +0 -133
  55. package/packages/python/setup.py +0 -93
  56. package/packages/python/specification.md +0 -930
  57. package/packages/react/README.md +0 -13
  58. package/packages/react/package.json +0 -105
  59. package/packages/react/rollup.config.js +0 -37
  60. package/packages/react/specification.md +0 -569
  61. package/packages/react/src/index.ts +0 -20
  62. package/packages/react/tsconfig.json +0 -24
  63. package/src/index.js +0 -1
  64. package/test_package.py +0 -125
@@ -1,368 +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
- * SendGrid email provider implementation
11
- */
12
-
13
- import { EmailProvider, EmailMessage, SendResult, DeliveryStatus, WebhookEvent } from './base';
14
-
15
- export interface SendGridConfig {
16
- apiKey: string;
17
- fromEmail: string;
18
- fromName?: string;
19
- webhookUrl?: string;
20
- webhookSecret?: string;
21
- sandbox?: boolean;
22
- }
23
-
24
- export class SendGridProvider extends EmailProvider {
25
- private apiKey: string;
26
- private fromEmail: string;
27
- private fromName?: string;
28
- private webhookUrl?: string;
29
- private webhookSecret?: string;
30
- private sandbox: boolean;
31
-
32
- constructor(config: SendGridConfig) {
33
- super(config);
34
-
35
- if (!config.apiKey) {
36
- throw new Error('SendGrid API key is required');
37
- }
38
-
39
- this.apiKey = config.apiKey;
40
- this.fromEmail = config.fromEmail;
41
- this.fromName = config.fromName;
42
- this.webhookUrl = config.webhookUrl;
43
- this.webhookSecret = config.webhookSecret;
44
- this.sandbox = config.sandbox || false;
45
- }
46
-
47
- async sendEmail(email: EmailMessage): Promise<SendResult> {
48
- try {
49
- const payload = {
50
- personalizations: [{
51
- to: [{
52
- email: email.toEmail,
53
- name: email.toName
54
- }],
55
- subject: email.subject,
56
- custom_args: email.metadata || {}
57
- }],
58
- from: {
59
- email: email.fromEmail || this.fromEmail,
60
- name: email.fromName || this.fromName
61
- },
62
- content: this.buildContent(email),
63
- headers: email.headers || {},
64
- mail_settings: {
65
- sandbox_mode: {
66
- enable: this.sandbox
67
- }
68
- }
69
- };
70
-
71
- // Add reply-to if specified
72
- if (email.replyTo) {
73
- (payload as any).reply_to = { email: email.replyTo };
74
- }
75
-
76
- // Convert attachments to SendGrid format
77
- if (email.attachments && email.attachments.length > 0) {
78
- (payload as any).attachments = email.attachments.map((att: any) => ({
79
- filename: att.filename,
80
- content: att.content,
81
- type: att.contentType,
82
- disposition: 'attachment'
83
- }));
84
- }
85
-
86
- const response = await this.makeRequest('/mail/send', 'POST', payload);
87
-
88
- // SendGrid returns 202 for successful sends
89
- if (response.status === 202) {
90
- return {
91
- success: true,
92
- messageId: response.headers?.['x-message-id'] || `sg-${Date.now()}`,
93
- providerResponse: response.data,
94
- metadata: {
95
- provider: 'sendgrid',
96
- statusCode: response.status
97
- }
98
- };
99
- }
100
-
101
- return {
102
- success: false,
103
- errorMessage: `SendGrid API returned status ${response.status}`,
104
- metadata: {
105
- provider: 'sendgrid',
106
- statusCode: response.status,
107
- response: response.data
108
- }
109
- };
110
-
111
- } catch (error) {
112
- return {
113
- success: false,
114
- errorMessage: error instanceof Error ? error.message : 'Unknown error',
115
- metadata: {
116
- provider: 'sendgrid'
117
- }
118
- };
119
- }
120
- }
121
-
122
- async sendBatch(emails: EmailMessage[]): Promise<SendResult[]> {
123
- // SendGrid doesn't have a true batch API, so we send individually
124
- // but we can optimize by sending them concurrently
125
- const promises = emails.map(email => this.sendEmail(email));
126
- return Promise.all(promises);
127
- }
128
-
129
- async getDeliveryStatus(messageId: string): Promise<DeliveryStatus | null> {
130
- try {
131
- // Use SendGrid's Event API
132
- const response = await this.makeRequest(`/messages/${messageId}`, 'GET');
133
-
134
- if (response.status === 200 && response.data) {
135
- return {
136
- messageId,
137
- status: this.mapSendGridStatus(response.data.status),
138
- deliveredAt: response.data.delivered_at ? new Date(response.data.delivered_at) : undefined,
139
- openedAt: response.data.opened_at ? new Date(response.data.opened_at) : undefined,
140
- clickedAt: response.data.clicked_at ? new Date(response.data.clicked_at) : undefined,
141
- metadata: response.data
142
- };
143
- }
144
-
145
- return null;
146
- } catch (error) {
147
- console.error('Failed to get delivery status:', error);
148
- return null;
149
- }
150
- }
151
-
152
- async processWebhook(payload: any, signature: string): Promise<WebhookEvent[]> {
153
- // Verify webhook signature if configured
154
- if (this.webhookSecret && !this.verifyWebhookSignature(payload, signature)) {
155
- throw new Error('Invalid webhook signature');
156
- }
157
-
158
- const events: WebhookEvent[] = [];
159
-
160
- // SendGrid sends events as an array
161
- if (Array.isArray(payload)) {
162
- for (const eventData of payload) {
163
- events.push({
164
- messageId: eventData.sg_message_id,
165
- eventType: this.mapSendGridEvent(eventData.event),
166
- timestamp: new Date(eventData.timestamp * 1000),
167
- email: eventData.email,
168
- reason: eventData.reason,
169
- response: eventData.response,
170
- metadata: eventData
171
- });
172
- }
173
- }
174
-
175
- return events;
176
- }
177
-
178
- async createTemplate(templateData: {
179
- name: string;
180
- subject: string;
181
- htmlContent: string;
182
- textContent?: string;
183
- }): Promise<string> {
184
- try {
185
- // Create the template
186
- const templatePayload = {
187
- name: templateData.name,
188
- generation: 'dynamic'
189
- };
190
-
191
- const templateResponse = await this.makeRequest('/templates', 'POST', templatePayload);
192
-
193
- if (templateResponse.status !== 201) {
194
- throw new Error(`Failed to create template: ${templateResponse.status}`);
195
- }
196
-
197
- const templateId = templateResponse.data.id;
198
-
199
- // Create a version for the template
200
- const versionPayload = {
201
- template_id: templateId,
202
- subject: templateData.subject,
203
- html_content: templateData.htmlContent,
204
- plain_content: templateData.textContent || '',
205
- active: 1
206
- };
207
-
208
- const versionResponse = await this.makeRequest(
209
- `/templates/${templateId}/versions`,
210
- 'POST',
211
- versionPayload
212
- );
213
-
214
- if (versionResponse.status !== 201) {
215
- throw new Error(`Failed to create template version: ${versionResponse.status}`);
216
- }
217
-
218
- return templateId;
219
- } catch (error) {
220
- throw new Error(`Template creation failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
221
- }
222
- }
223
-
224
- async sendTemplateEmail(
225
- templateId: string,
226
- toEmail: string,
227
- templateData: Record<string, any>,
228
- toName?: string
229
- ): Promise<SendResult> {
230
- try {
231
- const payload = {
232
- personalizations: [{
233
- to: [{
234
- email: toEmail,
235
- name: toName
236
- }],
237
- dynamic_template_data: templateData
238
- }],
239
- from: {
240
- email: this.fromEmail,
241
- name: this.fromName
242
- },
243
- template_id: templateId,
244
- mail_settings: {
245
- sandbox_mode: {
246
- enable: this.sandbox
247
- }
248
- }
249
- };
250
-
251
- const response = await this.makeRequest('/mail/send', 'POST', payload);
252
-
253
- if (response.status === 202) {
254
- return {
255
- success: true,
256
- messageId: response.headers?.['x-message-id'] || `sg-tpl-${Date.now()}`,
257
- providerResponse: response.data,
258
- metadata: {
259
- provider: 'sendgrid',
260
- templateId,
261
- statusCode: response.status
262
- }
263
- };
264
- }
265
-
266
- return {
267
- success: false,
268
- errorMessage: `SendGrid API returned status ${response.status}`,
269
- metadata: {
270
- provider: 'sendgrid',
271
- templateId,
272
- statusCode: response.status
273
- }
274
- };
275
-
276
- } catch (error) {
277
- return {
278
- success: false,
279
- errorMessage: error instanceof Error ? error.message : 'Unknown error',
280
- metadata: {
281
- provider: 'sendgrid',
282
- templateId
283
- }
284
- };
285
- }
286
- }
287
-
288
- private buildContent(email: EmailMessage): Array<{ type: string; value: string }> {
289
- const content: Array<{ type: string; value: string }> = [];
290
-
291
- if (email.textContent) {
292
- content.push({
293
- type: 'text/plain',
294
- value: email.textContent
295
- });
296
- }
297
-
298
- if (email.htmlContent) {
299
- content.push({
300
- type: 'text/html',
301
- value: email.htmlContent
302
- });
303
- }
304
-
305
- return content;
306
- }
307
-
308
- private async makeRequest(endpoint: string, method: string, requestData?: any): Promise<any> {
309
- const url = `https://api.sendgrid.com/v3${endpoint}`;
310
-
311
- const response = await fetch(url, {
312
- method,
313
- headers: {
314
- 'Authorization': `Bearer ${this.apiKey}`,
315
- 'Content-Type': 'application/json'
316
- },
317
- body: requestData ? JSON.stringify(requestData) : undefined
318
- });
319
-
320
- const responseText = await response.text();
321
- let data;
322
- try {
323
- data = JSON.parse(responseText);
324
- } catch {
325
- data = responseText;
326
- }
327
-
328
- return {
329
- status: response.status,
330
- headers: {},
331
- data: data
332
- };
333
- }
334
-
335
- private mapSendGridStatus(status: string): 'delivered' | 'bounced' | 'failed' | 'pending' | 'sent' | 'opened' | 'clicked' | 'spam' | 'unsubscribed' {
336
- const statusMap: Record<string, 'delivered' | 'bounced' | 'failed' | 'pending' | 'sent' | 'opened' | 'clicked' | 'spam' | 'unsubscribed'> = {
337
- 'delivered': 'delivered',
338
- 'processed': 'sent',
339
- 'bounce': 'bounced',
340
- 'dropped': 'failed',
341
- 'deferred': 'pending',
342
- 'blocked': 'failed'
343
- };
344
-
345
- return statusMap[status] || 'failed';
346
- }
347
-
348
- private mapSendGridEvent(event: string): string {
349
- const eventMap: Record<string, string> = {
350
- 'delivered': 'delivered',
351
- 'processed': 'sent',
352
- 'open': 'opened',
353
- 'click': 'clicked',
354
- 'bounce': 'bounced',
355
- 'dropped': 'failed',
356
- 'spam_report': 'spam',
357
- 'unsubscribe': 'unsubscribed'
358
- };
359
-
360
- return eventMap[event] || event;
361
- }
362
-
363
- private verifyWebhookSignature(payload: any, signature: string): boolean {
364
- // Implement SendGrid webhook signature verification
365
- // This is a simplified version - you'd need to implement the actual verification
366
- return true;
367
- }
368
- }
@@ -1,91 +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 provider type definitions
11
- */
12
-
13
- export interface EmailMessage {
14
- toEmail: string;
15
- toName?: string;
16
- fromEmail?: string;
17
- fromName?: string;
18
- subject: string;
19
- htmlContent?: string;
20
- textContent?: string;
21
- replyTo?: string;
22
- headers?: Record<string, string>;
23
- attachments?: EmailAttachment[];
24
- metadata?: Record<string, any>;
25
- }
26
-
27
- export interface EmailAttachment {
28
- filename: string;
29
- content: string; // Base64 encoded
30
- contentType?: string;
31
- disposition?: 'attachment' | 'inline';
32
- }
33
-
34
- export interface SendResult {
35
- success: boolean;
36
- messageId?: string;
37
- errorMessage?: string;
38
- providerResponse?: any;
39
- metadata?: Record<string, any>;
40
- }
41
-
42
- export interface DeliveryStatus {
43
- messageId: string;
44
- status: 'delivered' | 'bounced' | 'failed' | 'pending' | 'sent' | 'opened' | 'clicked' | 'spam' | 'unsubscribed';
45
- deliveredAt?: Date;
46
- openedAt?: Date;
47
- clickedAt?: Date;
48
- bouncedAt?: Date;
49
- reason?: string;
50
- metadata?: Record<string, any>;
51
- }
52
-
53
- export interface WebhookEvent {
54
- messageId: string;
55
- eventType: string;
56
- timestamp: Date;
57
- email: string;
58
- reason?: string;
59
- response?: string;
60
- metadata?: Record<string, any>;
61
- }
62
-
63
- export interface ProviderConfig {
64
- provider: 'sendgrid' | 'ses' | 'mailgun' | 'postmark' | 'smtp';
65
- apiKey?: string;
66
- region?: string;
67
- endpoint?: string;
68
- fromEmail: string;
69
- fromName?: string;
70
- replyTo?: string;
71
- webhookUrl?: string;
72
- webhookSecret?: string;
73
- sandbox?: boolean;
74
- }
75
-
76
- export interface BatchEmailOptions {
77
- templateId?: string;
78
- variables?: Record<string, any>;
79
- scheduledFor?: Date;
80
- metadata?: Record<string, any>;
81
- }
82
-
83
- export interface EmailProviderCapabilities {
84
- supportsBatch: boolean;
85
- supportsTemplates: boolean;
86
- supportsWebhooks: boolean;
87
- supportsAttachments: boolean;
88
- supportsDeliveryTracking: boolean;
89
- maxBatchSize: number;
90
- maxAttachmentSize: number;
91
- }
@@ -1,78 +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
- * Scheduled email type definitions
11
- */
12
-
13
- export type TriggerType = 'immediate' | 'relative' | 'absolute';
14
- export type EmailStatus = 'pending' | 'sent' | 'failed' | 'cancelled' | 'paused';
15
-
16
- export interface ScheduledEmail {
17
- id?: string;
18
- templateId: string;
19
- recipientEmail: string;
20
- recipientName?: string;
21
- scheduledFor: Date;
22
- triggerType: TriggerType;
23
- triggerOffset?: number; // days before/after reference date
24
- referenceDate?: Date;
25
- variables: Record<string, any>;
26
- status: EmailStatus;
27
- sentAt?: Date;
28
- errorMessage?: string;
29
- retryCount: number;
30
- maxRetries: number;
31
- metadata: Record<string, any>;
32
- createdAt?: Date;
33
- updatedAt?: Date;
34
- }
35
-
36
- export interface SchedulingFilters {
37
- templateId?: string;
38
- recipientEmail?: string;
39
- status?: EmailStatus;
40
- scheduledAfter?: Date;
41
- scheduledBefore?: Date;
42
- createdAfter?: Date;
43
- createdBefore?: Date;
44
- limit?: number;
45
- offset?: number;
46
- }
47
-
48
- export interface ProcessingResult {
49
- processedCount: number;
50
- successCount: number;
51
- failedCount: number;
52
- errors: Array<{
53
- emailId: string;
54
- error: string;
55
- }>;
56
- processingTimeSeconds: number;
57
- }
58
-
59
- export interface EmailBatch {
60
- templateId: string;
61
- recipients: Array<{
62
- email: string;
63
- name?: string;
64
- variables: Record<string, any>;
65
- }>;
66
- scheduledFor?: Date;
67
- triggerType?: TriggerType;
68
- triggerOffset?: number;
69
- referenceDate?: Date;
70
- metadata?: Record<string, any>;
71
- }
72
-
73
- export interface SchedulingConfig {
74
- batchSize: number;
75
- retryDelays: number[]; // minutes
76
- maxConcurrency: number;
77
- processingInterval: number; // minutes
78
- }
@@ -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
- /**
10
- * Email template type definitions
11
- */
12
-
13
- export type TemplateVariableType = 'text' | 'number' | 'date' | 'boolean' | 'url' | 'email';
14
-
15
- export interface TemplateVariable {
16
- name: string;
17
- type: TemplateVariableType;
18
- description?: string;
19
- defaultValue?: any;
20
- required: boolean;
21
- validation?: {
22
- pattern?: string;
23
- minLength?: number;
24
- maxLength?: number;
25
- min?: number;
26
- max?: number;
27
- };
28
- }
29
-
30
- export interface EmailTemplate {
31
- id?: string;
32
- name: string;
33
- subject: string;
34
- htmlBody: string;
35
- textBody: string;
36
- variables: TemplateVariable[];
37
- categoryId?: string;
38
- tags: string[];
39
- isActive: boolean;
40
- version: number;
41
- createdAt?: Date;
42
- updatedAt?: Date;
43
- createdBy?: string;
44
- metadata?: Record<string, any>;
45
- }
46
-
47
- export interface TemplateCategory {
48
- id: string;
49
- name: string;
50
- description?: string;
51
- color?: string;
52
- isActive: boolean;
53
- }
54
-
55
- export interface RenderedTemplate {
56
- subject: string;
57
- htmlContent: string;
58
- textContent: string;
59
- variables: Record<string, any>;
60
- templateId: string;
61
- }
62
-
63
- export interface TemplateFilters {
64
- categoryId?: string;
65
- tags?: string[];
66
- isActive?: boolean;
67
- search?: string;
68
- createdBy?: string;
69
- createdAfter?: Date;
70
- createdBefore?: Date;
71
- }
72
-
73
- export interface ValidationResult {
74
- isValid: boolean;
75
- errors: ValidationError[];
76
- warnings: ValidationWarning[];
77
- }
78
-
79
- export interface ValidationError {
80
- field: string;
81
- message: string;
82
- code: string;
83
- value?: any;
84
- }
85
-
86
- export interface ValidationWarning {
87
- field: string;
88
- message: string;
89
- code: string;
90
- value?: any;
91
- }
92
-
93
- export interface TemplatePreview {
94
- template: EmailTemplate;
95
- variables: Record<string, any>;
96
- rendered: RenderedTemplate;
97
- }
@@ -1,23 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2020",
4
- "module": "ESNext",
5
- "lib": ["ES2020"],
6
- "declaration": true,
7
- "declarationMap": true,
8
- "sourceMap": true,
9
- "outDir": "./dist",
10
- "rootDir": "./src",
11
- "strict": true,
12
- "esModuleInterop": true,
13
- "skipLibCheck": true,
14
- "forceConsistentCasingInFileNames": true,
15
- "moduleResolution": "node",
16
- "allowSyntheticDefaultImports": true,
17
- "resolveJsonModule": true,
18
- "isolatedModules": true,
19
- "noEmit": false
20
- },
21
- "include": ["src/**/*"],
22
- "exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"]
23
- }