@bernierllc/email-sender-manager 1.0.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.
Files changed (55) hide show
  1. package/LICENSE +5 -0
  2. package/README.md +466 -0
  3. package/dist/__tests__/manager.test.d.ts +2 -0
  4. package/dist/__tests__/manager.test.d.ts.map +1 -0
  5. package/dist/__tests__/manager.test.js +344 -0
  6. package/dist/__tests__/manager.test.js.map +1 -0
  7. package/dist/__tests__/mocks/mock-database.d.ts +15 -0
  8. package/dist/__tests__/mocks/mock-database.d.ts.map +1 -0
  9. package/dist/__tests__/mocks/mock-database.js +219 -0
  10. package/dist/__tests__/mocks/mock-database.js.map +1 -0
  11. package/dist/__tests__/selection/selector.test.d.ts +2 -0
  12. package/dist/__tests__/selection/selector.test.d.ts.map +1 -0
  13. package/dist/__tests__/selection/selector.test.js +351 -0
  14. package/dist/__tests__/selection/selector.test.js.map +1 -0
  15. package/dist/__tests__/utils/domain-utils.test.d.ts +2 -0
  16. package/dist/__tests__/utils/domain-utils.test.d.ts.map +1 -0
  17. package/dist/__tests__/utils/domain-utils.test.js +91 -0
  18. package/dist/__tests__/utils/domain-utils.test.js.map +1 -0
  19. package/dist/__tests__/utils/validation-utils.test.d.ts +2 -0
  20. package/dist/__tests__/utils/validation-utils.test.d.ts.map +1 -0
  21. package/dist/__tests__/utils/validation-utils.test.js +117 -0
  22. package/dist/__tests__/utils/validation-utils.test.js.map +1 -0
  23. package/dist/database/bootstrap.d.ts +35 -0
  24. package/dist/database/bootstrap.d.ts.map +1 -0
  25. package/dist/database/bootstrap.js +183 -0
  26. package/dist/database/bootstrap.js.map +1 -0
  27. package/dist/database/schema.d.ts +13 -0
  28. package/dist/database/schema.d.ts.map +1 -0
  29. package/dist/database/schema.js +72 -0
  30. package/dist/database/schema.js.map +1 -0
  31. package/dist/index.d.ts +8 -0
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/index.js +18 -0
  34. package/dist/index.js.map +1 -0
  35. package/dist/manager.d.ts +85 -0
  36. package/dist/manager.d.ts.map +1 -0
  37. package/dist/manager.js +469 -0
  38. package/dist/manager.js.map +1 -0
  39. package/dist/selection/selector.d.ts +27 -0
  40. package/dist/selection/selector.d.ts.map +1 -0
  41. package/dist/selection/selector.js +107 -0
  42. package/dist/selection/selector.js.map +1 -0
  43. package/dist/types.d.ts +257 -0
  44. package/dist/types.d.ts.map +1 -0
  45. package/dist/types.js +9 -0
  46. package/dist/types.js.map +1 -0
  47. package/dist/utils/domain-utils.d.ts +21 -0
  48. package/dist/utils/domain-utils.d.ts.map +1 -0
  49. package/dist/utils/domain-utils.js +65 -0
  50. package/dist/utils/domain-utils.js.map +1 -0
  51. package/dist/utils/validation-utils.d.ts +23 -0
  52. package/dist/utils/validation-utils.d.ts.map +1 -0
  53. package/dist/utils/validation-utils.js +148 -0
  54. package/dist/utils/validation-utils.js.map +1 -0
  55. package/package.json +49 -0
package/LICENSE ADDED
@@ -0,0 +1,5 @@
1
+ Copyright (c) 2025 Bernier LLC
2
+
3
+ This file is licensed to the client under a limited-use license.
4
+ The client may use and modify this code *only within the scope of the project it was delivered for*.
5
+ Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
package/README.md ADDED
@@ -0,0 +1,466 @@
1
+ # @bernierllc/email-sender-manager
2
+
3
+ Database-backed email sender configuration management with provider verification integration. Manage, verify, and intelligently select email sender configurations for your applications.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @bernierllc/email-sender-manager
9
+ ```
10
+
11
+ ## Features
12
+
13
+ - **Database-Backed Storage**: Persistent sender configurations with transaction support
14
+ - **Provider Integration**: Verify sender configurations with email providers (SendGrid, Mailgun, etc.)
15
+ - **Smart Sender Selection**: Intelligent sender selection based on domain matching, priority, or round-robin strategies
16
+ - **Bootstrap System**: Automatic database schema creation and default sender seeding
17
+ - **Full TypeScript Support**: Complete type definitions with strict mode compliance
18
+ - **Comprehensive Validation**: Email format validation, domain restrictions, and configuration checks
19
+
20
+ ## Quick Start
21
+
22
+ ```typescript
23
+ import { EmailSenderManager } from '@bernierllc/email-sender-manager';
24
+ import type { DatabaseAdapter } from '@bernierllc/email-sender-manager';
25
+
26
+ // Implement your database adapter
27
+ const databaseAdapter: DatabaseAdapter = {
28
+ async execute(query: string, params?: unknown[]): Promise<void> {
29
+ // Your database execute logic
30
+ },
31
+ async query<T = unknown>(query: string, params?: unknown[]): Promise<T[]> {
32
+ // Your database query logic
33
+ },
34
+ async queryOne<T = unknown>(query: string, params?: unknown[]): Promise<T | null> {
35
+ // Your database queryOne logic
36
+ },
37
+ async transaction<T>(callback: (adapter: DatabaseAdapter) => Promise<T>): Promise<T> {
38
+ // Your transaction logic
39
+ },
40
+ };
41
+
42
+ // Initialize manager
43
+ const manager = new EmailSenderManager({
44
+ database: databaseAdapter,
45
+ bootstrap: {
46
+ enabled: true,
47
+ createTables: true,
48
+ seedDefaultSenders: true,
49
+ defaultSenders: [
50
+ {
51
+ name: 'Default Sender',
52
+ fromEmail: 'noreply@example.com',
53
+ fromName: 'My App',
54
+ provider: 'sendgrid',
55
+ priority: 1,
56
+ },
57
+ ],
58
+ environment: 'production',
59
+ },
60
+ selection: {
61
+ strategy: 'domain_match',
62
+ fallbackToDefault: true,
63
+ domainMatchingRules: [],
64
+ },
65
+ });
66
+
67
+ // Bootstrap database
68
+ await manager.bootstrapDatabase();
69
+ ```
70
+
71
+ ## Core Concepts
72
+
73
+ ### Sender Configuration
74
+
75
+ A sender configuration represents an email address that can send emails through your application:
76
+
77
+ ```typescript
78
+ interface SenderConfiguration {
79
+ id: string;
80
+ name: string;
81
+ description?: string;
82
+ fromEmail: string;
83
+ fromName: string;
84
+ replyToEmail?: string;
85
+ replyToName?: string;
86
+ provider: string;
87
+ providerSenderId?: string;
88
+ providerMetadata?: Record<string, unknown>;
89
+ isVerified: boolean;
90
+ verificationStatus: 'pending' | 'verified' | 'failed' | 'expired';
91
+ lastVerifiedAt?: Date;
92
+ isDefault: boolean;
93
+ isActive: boolean;
94
+ priority: number; // Lower = higher priority
95
+ allowedDomains?: string[];
96
+ domain: string;
97
+ createdAt: Date;
98
+ updatedAt: Date;
99
+ createdBy: string;
100
+ lastModifiedBy: string;
101
+ }
102
+ ```
103
+
104
+ ### Provider Integration
105
+
106
+ Providers verify sender configurations with email service providers:
107
+
108
+ ```typescript
109
+ interface SenderProviderPlugin {
110
+ readonly providerId: string;
111
+ readonly name: string;
112
+ validateSender(email: string): Promise<ProviderValidationResult>;
113
+ getVerifiedSenders(): Promise<ProviderSender[]>;
114
+ syncSender(sender: SenderConfiguration): Promise<SyncResult>;
115
+ getCapabilities(): string[];
116
+ }
117
+ ```
118
+
119
+ ## Usage Examples
120
+
121
+ ### Creating Senders
122
+
123
+ ```typescript
124
+ // Create a new sender
125
+ const sender = await manager.createSender({
126
+ name: 'Marketing Team',
127
+ fromEmail: 'marketing@example.com',
128
+ fromName: 'Marketing Department',
129
+ replyToEmail: 'support@example.com',
130
+ provider: 'sendgrid',
131
+ priority: 10,
132
+ allowedDomains: ['example.com', '*.example.com'],
133
+ createdBy: 'admin-user',
134
+ });
135
+
136
+ console.log(`Created sender: ${sender.id}`);
137
+ ```
138
+
139
+ ### Updating Senders
140
+
141
+ ```typescript
142
+ // Update sender configuration
143
+ const updated = await manager.updateSender(sender.id, {
144
+ name: 'Updated Name',
145
+ priority: 5,
146
+ isActive: true,
147
+ lastModifiedBy: 'admin-user',
148
+ });
149
+ ```
150
+
151
+ ### Listing Senders
152
+
153
+ ```typescript
154
+ // List all active senders
155
+ const activeSenders = await manager.listSenders({
156
+ isActive: true,
157
+ orderBy: 'priority',
158
+ orderDirection: 'asc',
159
+ });
160
+
161
+ // List verified senders for a specific provider
162
+ const verifiedSenders = await manager.listSenders({
163
+ provider: 'sendgrid',
164
+ isVerified: true,
165
+ limit: 10,
166
+ offset: 0,
167
+ });
168
+
169
+ // Paginate through results
170
+ console.log(`Total: ${activeSenders.total}, Showing: ${activeSenders.items.length}`);
171
+ console.log(`Has more: ${activeSenders.hasMore}`);
172
+ ```
173
+
174
+ ### Sender Selection
175
+
176
+ ```typescript
177
+ // Select best sender for an email
178
+ const bestSender = await manager.selectBestSender('user@example.com', {
179
+ strategy: 'domain_match', // or 'priority' or 'round_robin'
180
+ allowUnverified: false,
181
+ });
182
+
183
+ if (bestSender) {
184
+ console.log(`Using sender: ${bestSender.fromEmail} (priority: ${bestSender.priority})`);
185
+ }
186
+
187
+ // Select sender for specific provider
188
+ const sendgridSender = await manager.selectBestSender('user@example.com', {
189
+ provider: 'sendgrid',
190
+ strategy: 'priority',
191
+ });
192
+ ```
193
+
194
+ ### Provider Verification
195
+
196
+ ```typescript
197
+ import type { SenderProviderPlugin } from '@bernierllc/email-sender-manager';
198
+
199
+ // Implement a provider plugin
200
+ class SendGridProvider implements SenderProviderPlugin {
201
+ readonly providerId = 'sendgrid';
202
+ readonly name = 'SendGrid';
203
+
204
+ constructor(private apiKey: string) {}
205
+
206
+ async validateSender(email: string): Promise<ProviderValidationResult> {
207
+ // Call SendGrid API to verify sender
208
+ const verifiedSenders = await this.getVerifiedSenders();
209
+ const match = verifiedSenders.find((s) => s.email === email);
210
+
211
+ return {
212
+ isVerified: Boolean(match?.isVerified),
213
+ verificationStatus: match?.verificationStatus || 'not_found',
214
+ lastChecked: new Date(),
215
+ metadata: { providerId: match?.id },
216
+ };
217
+ }
218
+
219
+ async getVerifiedSenders(): Promise<ProviderSender[]> {
220
+ // Fetch from SendGrid API
221
+ return [];
222
+ }
223
+
224
+ async syncSender(sender: SenderConfiguration): Promise<SyncResult> {
225
+ // Sync with SendGrid
226
+ return { success: true, senderId: sender.id, synced: true };
227
+ }
228
+
229
+ getCapabilities(): string[] {
230
+ return ['verification', 'sync'];
231
+ }
232
+ }
233
+
234
+ // Register provider
235
+ const provider = new SendGridProvider(process.env.SENDGRID_API_KEY!);
236
+ await manager.registerProvider(provider);
237
+
238
+ // Verify a sender
239
+ const verification = await manager.verifySender(sender.id);
240
+ console.log(`Verified: ${verification.isVerified}`);
241
+ ```
242
+
243
+ ### Bootstrap Configuration
244
+
245
+ ```typescript
246
+ const manager = new EmailSenderManager({
247
+ database: databaseAdapter,
248
+ bootstrap: {
249
+ enabled: true,
250
+ createTables: true,
251
+ seedDefaultSenders: true,
252
+ defaultSenders: [
253
+ {
254
+ name: 'Default Sender',
255
+ fromEmail: 'noreply@example.com',
256
+ fromName: 'My Application',
257
+ provider: 'sendgrid',
258
+ priority: 1,
259
+ },
260
+ {
261
+ name: 'Marketing Sender',
262
+ fromEmail: 'marketing@example.com',
263
+ fromName: 'Marketing Team',
264
+ provider: 'sendgrid',
265
+ priority: 10,
266
+ allowedDomains: ['example.com'],
267
+ },
268
+ ],
269
+ environment: 'production',
270
+ },
271
+ });
272
+
273
+ // Run bootstrap
274
+ const result = await manager.bootstrapDatabase();
275
+ console.log(result.message);
276
+ // Output: "Bootstrap completed successfully. Tables created: true, Senders seeded: 2"
277
+ ```
278
+
279
+ ## Selection Strategies
280
+
281
+ ### Domain Matching
282
+
283
+ Selects senders based on domain relationships:
284
+
285
+ 1. **Exact domain match**: `example.com` sender for `user@example.com`
286
+ 2. **Subdomain match**: `example.com` sender for `user@app.example.com`
287
+ 3. **Allowed domains**: Senders with `allowedDomains` configuration
288
+ 4. **Fallback to priority**: If no domain match found
289
+
290
+ ```typescript
291
+ const sender = await manager.selectBestSender('user@app.example.com', {
292
+ strategy: 'domain_match',
293
+ });
294
+ ```
295
+
296
+ ### Priority-Based
297
+
298
+ Selects sender with lowest priority number (higher priority):
299
+
300
+ ```typescript
301
+ const sender = await manager.selectBestSender('user@example.com', {
302
+ strategy: 'priority',
303
+ });
304
+ ```
305
+
306
+ ### Round-Robin
307
+
308
+ Distributes selections evenly across available senders:
309
+
310
+ ```typescript
311
+ const sender = await manager.selectBestSender('user@example.com', {
312
+ strategy: 'round_robin',
313
+ });
314
+ ```
315
+
316
+ ## Domain Utilities
317
+
318
+ ```typescript
319
+ import {
320
+ extractDomain,
321
+ isDomainMatch,
322
+ isValidEmail,
323
+ getParentDomain,
324
+ isSubdomainOf,
325
+ } from '@bernierllc/email-sender-manager';
326
+
327
+ // Extract domain from email
328
+ const domain = extractDomain('user@example.com'); // "example.com"
329
+
330
+ // Check domain matching with wildcards
331
+ const matches = isDomainMatch('app.example.com', '*.example.com'); // true
332
+
333
+ // Validate email format
334
+ const valid = isValidEmail('user@example.com'); // true
335
+
336
+ // Get parent domain
337
+ const parent = getParentDomain('app.example.com'); // "example.com"
338
+
339
+ // Check subdomain relationship
340
+ const isSub = isSubdomainOf('app.example.com', 'example.com'); // true
341
+ ```
342
+
343
+ ## Validation
344
+
345
+ ```typescript
346
+ import { validateCreateSenderRequest } from '@bernierllc/email-sender-manager';
347
+
348
+ // Validate sender creation request
349
+ const validation = validateCreateSenderRequest({
350
+ name: 'Test Sender',
351
+ fromEmail: 'test@example.com',
352
+ fromName: 'Test',
353
+ provider: 'sendgrid',
354
+ createdBy: 'admin',
355
+ });
356
+
357
+ if (validation.errors.length > 0) {
358
+ console.error('Validation errors:', validation.errors);
359
+ }
360
+
361
+ if (validation.warnings.length > 0) {
362
+ console.warn('Validation warnings:', validation.warnings);
363
+ }
364
+ ```
365
+
366
+ ## Database Schema
367
+
368
+ The package automatically creates the following database schema:
369
+
370
+ ```sql
371
+ CREATE TABLE IF NOT EXISTS email_senders (
372
+ id VARCHAR(255) PRIMARY KEY,
373
+ name VARCHAR(255) NOT NULL,
374
+ description TEXT,
375
+ from_email VARCHAR(255) NOT NULL UNIQUE,
376
+ from_name VARCHAR(255) NOT NULL,
377
+ reply_to_email VARCHAR(255),
378
+ reply_to_name VARCHAR(255),
379
+ provider VARCHAR(100) NOT NULL,
380
+ provider_sender_id VARCHAR(255),
381
+ provider_metadata JSON,
382
+ is_verified BOOLEAN DEFAULT FALSE,
383
+ verification_status VARCHAR(50) DEFAULT 'pending',
384
+ last_verified_at TIMESTAMP NULL,
385
+ verification_error TEXT,
386
+ is_default BOOLEAN DEFAULT FALSE,
387
+ is_active BOOLEAN DEFAULT TRUE,
388
+ priority INTEGER DEFAULT 100,
389
+ allowed_domains JSON,
390
+ domain VARCHAR(255) NOT NULL,
391
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
392
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
393
+ created_by VARCHAR(255),
394
+ last_modified_by VARCHAR(255),
395
+ INDEX idx_email_senders_from_email (from_email),
396
+ INDEX idx_email_senders_domain (domain),
397
+ INDEX idx_email_senders_provider (provider),
398
+ INDEX idx_email_senders_active (is_active),
399
+ INDEX idx_email_senders_default (is_default),
400
+ INDEX idx_email_senders_priority (priority)
401
+ );
402
+ ```
403
+
404
+ ## API Reference
405
+
406
+ ### EmailSenderManager
407
+
408
+ Main class for managing sender configurations.
409
+
410
+ #### Constructor
411
+
412
+ ```typescript
413
+ constructor(config: SenderManagerConfig)
414
+ ```
415
+
416
+ #### Methods
417
+
418
+ - `bootstrapDatabase(): Promise<BootstrapResult>` - Initialize database and seed default senders
419
+ - `createSender(request: CreateSenderRequest): Promise<SenderConfiguration>` - Create new sender
420
+ - `updateSender(id: string, updates: UpdateSenderRequest): Promise<SenderConfiguration>` - Update existing sender
421
+ - `deleteSender(id: string): Promise<void>` - Delete sender
422
+ - `getSender(id: string): Promise<SenderConfiguration | null>` - Get sender by ID
423
+ - `listSenders(options?: ListSendersOptions): Promise<PaginatedResult<SenderConfiguration>>` - List senders with filters
424
+ - `validateSender(id: string): Promise<SenderValidationResult>` - Validate sender configuration
425
+ - `verifySender(id: string, provider?: string): Promise<VerificationResult>` - Verify sender with provider
426
+ - `selectBestSender(fromEmail: string, options?: SelectionOptions): Promise<SenderConfiguration | null>` - Select optimal sender
427
+ - `getDefaultSender(provider?: string): Promise<SenderConfiguration | null>` - Get default sender
428
+ - `getSendersByDomain(domain: string): Promise<SenderConfiguration[]>` - Get senders for domain
429
+ - `registerProvider(provider: SenderProviderPlugin): Promise<void>` - Register provider plugin
430
+ - `syncWithProviders(): Promise<SyncResult[]>` - Sync all senders with providers
431
+ - `cleanupInactiveSenders(): Promise<CleanupResult>` - Remove inactive senders
432
+
433
+ ## Integration Status
434
+
435
+ - **Logger**: Not applicable - Core utility package with database-backed state (logging handled at service layer)
436
+ - **Docs-Suite**: Ready - Comprehensive API documentation with TypeDoc comments exported
437
+ - **NeverHub**: Not applicable - Core utility package focused on email sender configuration management. NeverHub integration is appropriate at the service layer where sender selection events and metrics would be published to the event bus. This package provides the foundational primitives for sender management without environment-dependent behavior.
438
+
439
+ ## Testing
440
+
441
+ The package includes comprehensive tests with 90%+ coverage:
442
+
443
+ ```bash
444
+ # Run tests
445
+ npm test
446
+
447
+ # Run tests with coverage
448
+ npm run test:coverage
449
+
450
+ # Run tests once
451
+ npm run test:run
452
+ ```
453
+
454
+ ## License
455
+
456
+ Copyright (c) 2025 Bernier LLC
457
+
458
+ This file is licensed to the client under a limited-use license.
459
+ The client may use and modify this code *only within the scope of the project it was delivered for*.
460
+ Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
461
+
462
+ ## See Also
463
+
464
+ - [@bernierllc/email-sender](https://www.npmjs.com/package/@bernierllc/email-sender) - Email sending abstraction
465
+ - [@bernierllc/logger](https://www.npmjs.com/package/@bernierllc/logger) - Logging utilities
466
+ - [@bernierllc/retry-policy](https://www.npmjs.com/package/@bernierllc/retry-policy) - Retry logic utilities
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=manager.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manager.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/manager.test.ts"],"names":[],"mappings":""}