@bernierllc/email-template-service 1.0.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/LICENSE +21 -0
- package/README.md +449 -0
- package/dist/__tests__/email-template-service.test.d.ts +2 -0
- package/dist/__tests__/email-template-service.test.d.ts.map +1 -0
- package/dist/__tests__/email-template-service.test.js +583 -0
- package/dist/__tests__/email-template-service.test.js.map +1 -0
- package/dist/database-adapter.d.ts +59 -0
- package/dist/database-adapter.d.ts.map +1 -0
- package/dist/database-adapter.js +134 -0
- package/dist/database-adapter.js.map +1 -0
- package/dist/email-template-service.d.ts +131 -0
- package/dist/email-template-service.d.ts.map +1 -0
- package/dist/email-template-service.js +574 -0
- package/dist/email-template-service.js.map +1 -0
- package/dist/errors.d.ts +69 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +128 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +35 -0
- package/dist/index.js.map +1 -0
- package/dist/provider-plugin.d.ts +91 -0
- package/dist/provider-plugin.d.ts.map +1 -0
- package/dist/provider-plugin.js +166 -0
- package/dist/provider-plugin.js.map +1 -0
- package/dist/types.d.ts +322 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +10 -0
- package/dist/types.js.map +1 -0
- package/package.json +55 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Bernier LLC
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
# @bernierllc/email-template-service
|
|
2
|
+
|
|
3
|
+
Email template management service with provider synchronization, variable context validation, and base template support.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- Complete template CRUD operations with validation
|
|
8
|
+
- Provider plugin system for email service integration (SendGrid, Mailgun, etc.)
|
|
9
|
+
- Bidirectional synchronization between local database and provider services
|
|
10
|
+
- Variable context registration and validation
|
|
11
|
+
- Base template support for consistent branding
|
|
12
|
+
- Conflict detection and resolution strategies
|
|
13
|
+
- Batch synchronization with error tracking
|
|
14
|
+
- TypeScript-first with strict type safety
|
|
15
|
+
- Extensible database adapter interface
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install @bernierllc/email-template-service
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Quick Start
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
import {
|
|
27
|
+
EmailTemplateService,
|
|
28
|
+
InMemoryDatabaseAdapter,
|
|
29
|
+
MockProviderPlugin
|
|
30
|
+
} from '@bernierllc/email-template-service';
|
|
31
|
+
|
|
32
|
+
// Initialize service
|
|
33
|
+
const service = new EmailTemplateService({
|
|
34
|
+
database: new InMemoryDatabaseAdapter(),
|
|
35
|
+
templateEngine: myTemplateEngine,
|
|
36
|
+
variableRegistry: myVariableRegistry,
|
|
37
|
+
providers: [new MockProviderPlugin()],
|
|
38
|
+
syncConfig: {
|
|
39
|
+
enabled: true,
|
|
40
|
+
providers: {
|
|
41
|
+
sendgrid: {
|
|
42
|
+
enabled: true,
|
|
43
|
+
strategy: 'auto',
|
|
44
|
+
direction: 'bidirectional'
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
defaultStrategy: 'auto',
|
|
48
|
+
conflictResolution: 'local_wins'
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// Create a template
|
|
53
|
+
const template = await service.createTemplate({
|
|
54
|
+
name: 'Welcome Email',
|
|
55
|
+
subject: 'Welcome to our platform, {{firstName}}!',
|
|
56
|
+
htmlContent: '<html><body><h1>Welcome {{firstName}} {{lastName}}!</h1></body></html>',
|
|
57
|
+
textContent: 'Welcome {{firstName}} {{lastName}}!',
|
|
58
|
+
tags: ['welcome', 'onboarding'],
|
|
59
|
+
createdBy: 'admin'
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// Sync to provider
|
|
63
|
+
await service.syncToProvider(template.id, 'sendgrid');
|
|
64
|
+
|
|
65
|
+
// List templates
|
|
66
|
+
const result = await service.listTemplates({
|
|
67
|
+
category: 'transactional',
|
|
68
|
+
page: 1,
|
|
69
|
+
limit: 10
|
|
70
|
+
});
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Core Concepts
|
|
74
|
+
|
|
75
|
+
### Templates
|
|
76
|
+
|
|
77
|
+
Templates are email definitions with support for:
|
|
78
|
+
- HTML and text content
|
|
79
|
+
- Variable placeholders (e.g., `{{firstName}}`)
|
|
80
|
+
- Subject lines with variables
|
|
81
|
+
- Categorization and tagging
|
|
82
|
+
- Versioning
|
|
83
|
+
- Provider synchronization status
|
|
84
|
+
|
|
85
|
+
### Provider Plugins
|
|
86
|
+
|
|
87
|
+
Provider plugins enable integration with email service providers:
|
|
88
|
+
- **SendGrid**: Template management and synchronization
|
|
89
|
+
- **Mailgun**: Template operations and tracking
|
|
90
|
+
- **Custom**: Implement `TemplateProviderPlugin` interface
|
|
91
|
+
|
|
92
|
+
Plugins support:
|
|
93
|
+
- Template CRUD operations in provider systems
|
|
94
|
+
- Bidirectional synchronization
|
|
95
|
+
- Feature capability detection
|
|
96
|
+
- Health monitoring and status reporting
|
|
97
|
+
|
|
98
|
+
### Variable Contexts
|
|
99
|
+
|
|
100
|
+
Variable contexts define available variables for templates:
|
|
101
|
+
- Type-safe variable definitions
|
|
102
|
+
- Required vs. optional variables
|
|
103
|
+
- Context inheritance
|
|
104
|
+
- Template validation against contexts
|
|
105
|
+
- Auto-completion suggestions
|
|
106
|
+
|
|
107
|
+
### Base Templates
|
|
108
|
+
|
|
109
|
+
Base templates provide consistent branding:
|
|
110
|
+
- Reusable header/footer HTML and CSS
|
|
111
|
+
- Theme management
|
|
112
|
+
- Branding elements (logos, colors, fonts)
|
|
113
|
+
- Dynamic content insertion
|
|
114
|
+
- Template building from base + content
|
|
115
|
+
|
|
116
|
+
## API Reference
|
|
117
|
+
|
|
118
|
+
### EmailTemplateService
|
|
119
|
+
|
|
120
|
+
#### Template Operations
|
|
121
|
+
|
|
122
|
+
##### createTemplate(definition: TemplateDefinition): Promise<Template>
|
|
123
|
+
|
|
124
|
+
Create a new email template.
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
const template = await service.createTemplate({
|
|
128
|
+
name: 'Password Reset',
|
|
129
|
+
subject: 'Reset your password',
|
|
130
|
+
htmlContent: '<html><body>Click here to reset: {{resetLink}}</body></html>',
|
|
131
|
+
context: 'auth',
|
|
132
|
+
createdBy: 'admin'
|
|
133
|
+
});
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
##### updateTemplate(id: string, updates: TemplateUpdate): Promise<Template>
|
|
137
|
+
|
|
138
|
+
Update an existing template.
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
const updated = await service.updateTemplate(template.id, {
|
|
142
|
+
subject: 'Reset your password - Updated',
|
|
143
|
+
lastModifiedBy: 'admin'
|
|
144
|
+
});
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
##### deleteTemplate(id: string): Promise<void>
|
|
148
|
+
|
|
149
|
+
Delete a template and its provider copies.
|
|
150
|
+
|
|
151
|
+
```typescript
|
|
152
|
+
await service.deleteTemplate(template.id);
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
##### getTemplate(id: string): Promise<Template | null>
|
|
156
|
+
|
|
157
|
+
Retrieve a template by ID.
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
const template = await service.getTemplate('tpl_123');
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
##### listTemplates(options: ListOptions): Promise<PaginatedResult<Template>>
|
|
164
|
+
|
|
165
|
+
List templates with filtering and pagination.
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
const result = await service.listTemplates({
|
|
169
|
+
category: 'marketing',
|
|
170
|
+
tags: ['promo'],
|
|
171
|
+
isActive: true,
|
|
172
|
+
search: 'holiday',
|
|
173
|
+
page: 1,
|
|
174
|
+
limit: 20,
|
|
175
|
+
sortBy: 'createdAt',
|
|
176
|
+
sortOrder: 'desc'
|
|
177
|
+
});
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
#### Provider Synchronization
|
|
181
|
+
|
|
182
|
+
##### syncToProvider(templateId: string, providerId: string): Promise<SyncResult>
|
|
183
|
+
|
|
184
|
+
Sync a template to a specific provider.
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
const result = await service.syncToProvider('tpl_123', 'sendgrid');
|
|
188
|
+
console.log(result.action); // 'created' or 'updated'
|
|
189
|
+
console.log(result.providerTemplateId);
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
##### syncFromProvider(providerTemplateId: string, providerId: string): Promise<Template>
|
|
193
|
+
|
|
194
|
+
Import a template from a provider.
|
|
195
|
+
|
|
196
|
+
```typescript
|
|
197
|
+
const template = await service.syncFromProvider('sg_template_456', 'sendgrid');
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
##### syncAllTemplates(providerId: string): Promise<BatchSyncResult>
|
|
201
|
+
|
|
202
|
+
Sync all local templates to a provider.
|
|
203
|
+
|
|
204
|
+
```typescript
|
|
205
|
+
const result = await service.syncAllTemplates('sendgrid');
|
|
206
|
+
console.log(`Synced: ${result.successful}/${result.total}`);
|
|
207
|
+
console.log(`Failed: ${result.failed}`);
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
#### Base Template Operations
|
|
211
|
+
|
|
212
|
+
##### createBaseTemplate(definition: BaseTemplateDefinition): Promise<BaseTemplate>
|
|
213
|
+
|
|
214
|
+
Create a reusable base template.
|
|
215
|
+
|
|
216
|
+
```typescript
|
|
217
|
+
const baseTemplate = await service.createBaseTemplate({
|
|
218
|
+
name: 'Corporate Email Template',
|
|
219
|
+
headerHtml: '<header><img src="{{logo}}" /></header>',
|
|
220
|
+
footerHtml: '<footer>© 2025 Company Name</footer>',
|
|
221
|
+
theme: 'corporate'
|
|
222
|
+
});
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
##### buildTemplate(baseTemplateId: string, content: string, variables?: Record<string, unknown>): Promise<BuiltTemplate>
|
|
226
|
+
|
|
227
|
+
Build a complete template from base + content.
|
|
228
|
+
|
|
229
|
+
```typescript
|
|
230
|
+
const built = await service.buildTemplate(
|
|
231
|
+
baseTemplate.id,
|
|
232
|
+
'<main><h1>{{title}}</h1><p>{{content}}</p></main>',
|
|
233
|
+
{
|
|
234
|
+
logo: 'https://example.com/logo.png',
|
|
235
|
+
title: 'Important Update',
|
|
236
|
+
content: 'This is the email body'
|
|
237
|
+
}
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
console.log(built.html); // Complete HTML with header, content, footer
|
|
241
|
+
console.log(built.text); // Plain text version
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
#### Variable Context Management
|
|
245
|
+
|
|
246
|
+
##### registerContext(name: string, context: VariableContext): Promise<void>
|
|
247
|
+
|
|
248
|
+
Register a variable context for templates.
|
|
249
|
+
|
|
250
|
+
```typescript
|
|
251
|
+
await service.registerContext('user', {
|
|
252
|
+
description: 'User-related variables',
|
|
253
|
+
variables: {
|
|
254
|
+
firstName: { type: 'string', required: true, description: 'User first name' },
|
|
255
|
+
lastName: { type: 'string', required: true, description: 'User last name' },
|
|
256
|
+
email: { type: 'string', required: true, description: 'User email address' },
|
|
257
|
+
accountAge: { type: 'number', required: false, description: 'Days since signup' }
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
##### validateTemplate(templateId: string, contextOverride?: string): Promise<ValidationResult>
|
|
263
|
+
|
|
264
|
+
Validate a template against its variable context.
|
|
265
|
+
|
|
266
|
+
```typescript
|
|
267
|
+
const validation = await service.validateTemplate('tpl_123');
|
|
268
|
+
|
|
269
|
+
if (!validation.isValid) {
|
|
270
|
+
console.log('Errors:', validation.errors);
|
|
271
|
+
console.log('Undefined variables:', validation.undefinedVariables);
|
|
272
|
+
}
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
##### getVariableSuggestions(context: string, prefix?: string): Promise<VariableSuggestion[]>
|
|
276
|
+
|
|
277
|
+
Get variable suggestions for auto-completion.
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
const suggestions = await service.getVariableSuggestions('user', 'first');
|
|
281
|
+
// Returns: [{ name: 'firstName', type: 'string', ... }]
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
## Database Adapters
|
|
285
|
+
|
|
286
|
+
The service supports any database through the `DatabaseAdapter` interface.
|
|
287
|
+
|
|
288
|
+
### In-Memory Adapter (Testing/Development)
|
|
289
|
+
|
|
290
|
+
```typescript
|
|
291
|
+
import { InMemoryDatabaseAdapter } from '@bernierllc/email-template-service';
|
|
292
|
+
|
|
293
|
+
const adapter = new InMemoryDatabaseAdapter();
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### Custom Adapter
|
|
297
|
+
|
|
298
|
+
Implement the `DatabaseAdapter` interface for your database:
|
|
299
|
+
|
|
300
|
+
```typescript
|
|
301
|
+
import { DatabaseAdapter, Template, BaseTemplate } from '@bernierllc/email-template-service';
|
|
302
|
+
|
|
303
|
+
class PostgresDatabaseAdapter implements DatabaseAdapter {
|
|
304
|
+
async createTemplate(template: Template): Promise<Template> {
|
|
305
|
+
// Insert into PostgreSQL
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
async updateTemplate(id: string, updates: Partial<Template>): Promise<Template> {
|
|
309
|
+
// Update PostgreSQL row
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Implement other required methods...
|
|
313
|
+
}
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
## Provider Plugins
|
|
317
|
+
|
|
318
|
+
### Creating a Provider Plugin
|
|
319
|
+
|
|
320
|
+
```typescript
|
|
321
|
+
import {
|
|
322
|
+
BaseProviderPlugin,
|
|
323
|
+
Template,
|
|
324
|
+
ProviderTemplate,
|
|
325
|
+
SyncResult
|
|
326
|
+
} from '@bernierllc/email-template-service';
|
|
327
|
+
|
|
328
|
+
class SendGridTemplatePlugin extends BaseProviderPlugin {
|
|
329
|
+
readonly name = 'SendGrid Template Plugin';
|
|
330
|
+
readonly version = '1.0.0';
|
|
331
|
+
readonly providerId = 'sendgrid';
|
|
332
|
+
readonly capabilities = [
|
|
333
|
+
{
|
|
334
|
+
feature: 'template-versioning',
|
|
335
|
+
description: 'Supports template versions',
|
|
336
|
+
methods: ['createTemplate', 'updateTemplate']
|
|
337
|
+
}
|
|
338
|
+
];
|
|
339
|
+
|
|
340
|
+
async initialize(config: PluginConfig): Promise<void> {
|
|
341
|
+
await super.initialize(config);
|
|
342
|
+
this.client = new SendGridClient(config.apiKey);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
async createTemplate(template: Template): Promise<string> {
|
|
346
|
+
this.ensureInitialized();
|
|
347
|
+
const result = await this.client.createTemplate({
|
|
348
|
+
name: template.name,
|
|
349
|
+
generation: 'dynamic',
|
|
350
|
+
subject: template.subject,
|
|
351
|
+
html_content: template.htmlContent
|
|
352
|
+
});
|
|
353
|
+
return result.id;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Implement other required methods...
|
|
357
|
+
}
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
## Configuration
|
|
361
|
+
|
|
362
|
+
### Sync Configuration
|
|
363
|
+
|
|
364
|
+
```typescript
|
|
365
|
+
{
|
|
366
|
+
enabled: true, // Enable synchronization
|
|
367
|
+
providers: {
|
|
368
|
+
sendgrid: {
|
|
369
|
+
enabled: true,
|
|
370
|
+
strategy: 'auto', // 'auto', 'manual', or 'scheduled'
|
|
371
|
+
direction: 'bidirectional', // 'bidirectional', 'push_only', 'pull_only'
|
|
372
|
+
schedule: '0 */6 * * *', // Cron expression for scheduled sync
|
|
373
|
+
batchSize: 50 // Batch size for bulk operations
|
|
374
|
+
}
|
|
375
|
+
},
|
|
376
|
+
defaultStrategy: 'manual', // Default sync strategy
|
|
377
|
+
conflictResolution: 'local_wins' // 'local_wins', 'remote_wins', 'timestamp', 'manual'
|
|
378
|
+
}
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
## Error Handling
|
|
382
|
+
|
|
383
|
+
The service provides specific error types:
|
|
384
|
+
|
|
385
|
+
```typescript
|
|
386
|
+
import {
|
|
387
|
+
TemplateNotFoundError,
|
|
388
|
+
TemplateValidationError,
|
|
389
|
+
ProviderNotFoundError,
|
|
390
|
+
SyncError,
|
|
391
|
+
SyncConflictError
|
|
392
|
+
} from '@bernierllc/email-template-service';
|
|
393
|
+
|
|
394
|
+
try {
|
|
395
|
+
await service.updateTemplate('invalid-id', { name: 'New Name' });
|
|
396
|
+
} catch (error) {
|
|
397
|
+
if (error instanceof TemplateNotFoundError) {
|
|
398
|
+
console.error('Template not found');
|
|
399
|
+
} else if (error instanceof TemplateValidationError) {
|
|
400
|
+
console.error('Validation errors:', error.errors);
|
|
401
|
+
} else if (error instanceof SyncError) {
|
|
402
|
+
console.error('Sync failed:', error.message);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
## Testing
|
|
408
|
+
|
|
409
|
+
The package includes comprehensive tests using real data:
|
|
410
|
+
|
|
411
|
+
```bash
|
|
412
|
+
# Run tests
|
|
413
|
+
npm test
|
|
414
|
+
|
|
415
|
+
# Run tests with coverage
|
|
416
|
+
npm run test:coverage
|
|
417
|
+
|
|
418
|
+
# Run tests once (CI)
|
|
419
|
+
npm run test:run
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
## Dependencies
|
|
423
|
+
|
|
424
|
+
### Required
|
|
425
|
+
- `@bernierllc/template-variable-registry` - Variable context management
|
|
426
|
+
- `@bernierllc/email-template-engine` - Template processing
|
|
427
|
+
- `@bernierllc/email-sender` - Email sending abstraction
|
|
428
|
+
- `@bernierllc/logger` - Service logging
|
|
429
|
+
|
|
430
|
+
### Optional
|
|
431
|
+
- `@bernierllc/cache-manager` - Template caching
|
|
432
|
+
- `@bernierllc/retry-policy` - Provider operation retries
|
|
433
|
+
|
|
434
|
+
## Integration Status
|
|
435
|
+
|
|
436
|
+
- **Logger**: Integrated - All service operations and provider sync events logged
|
|
437
|
+
- **Docs-Suite**: Ready - Complete API documentation with examples
|
|
438
|
+
- **NeverHub**: Required - Template events, sync status, and provider health monitoring
|
|
439
|
+
|
|
440
|
+
## License
|
|
441
|
+
|
|
442
|
+
Copyright (c) 2025 Bernier LLC. All rights reserved.
|
|
443
|
+
|
|
444
|
+
## See Also
|
|
445
|
+
|
|
446
|
+
- [@bernierllc/email-template-engine](../email-template-engine) - Template processing and rendering
|
|
447
|
+
- [@bernierllc/template-variable-registry](../template-variable-registry) - Variable context management
|
|
448
|
+
- [@bernierllc/email-sender](../email-sender) - Email sending service
|
|
449
|
+
- [@bernierllc/email-sendgrid-plugin](../email-sendgrid-plugin) - SendGrid provider plugin
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"email-template-service.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/email-template-service.test.ts"],"names":[],"mappings":""}
|