@flusys/ng-email 1.1.0-beta

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 ADDED
@@ -0,0 +1,660 @@
1
+ # Email Package Guide
2
+
3
+ > **Package:** `@flusys/ng-email`
4
+ > **Type:** Email management with configurations, templates, and sending
5
+
6
+ ---
7
+
8
+ ## Overview
9
+
10
+ `@flusys/ng-email` provides complete email management:
11
+
12
+ - **Email Configurations** - CRUD for SMTP, SendGrid, Mailgun providers
13
+ - **Email Templates** - Visual template management with variable interpolation
14
+ - **HTML/Plain Text** - Toggle between content types via `isHtml` field
15
+ - **Test Sending** - Test configurations and templates with dynamic variables
16
+ - **Company Scoping** - Automatic company-scoped queries (when enabled)
17
+ - **Lazy Loading** - All page components are lazy-loaded via routes
18
+
19
+ ### Package Hierarchy
20
+
21
+ ```
22
+ @flusys/ng-core <- Foundation (BaseApiService, APP_CONFIG)
23
+ |
24
+ @flusys/ng-shared <- Shared utilities (ApiResourceService, IBaseEntity)
25
+ |
26
+ @flusys/ng-layout <- Layout (LAYOUT_AUTH_STATE for company context)
27
+ |
28
+ @flusys/ng-email <- Email management (THIS PACKAGE)
29
+ ```
30
+
31
+ ### Company Context
32
+
33
+ Email components use `LAYOUT_AUTH_STATE` from `@flusys/ng-layout` for company context. This follows the Provider Interface Pattern - ng-email depends on ng-layout's abstraction, not ng-auth directly.
34
+
35
+ ```typescript
36
+ import { LAYOUT_AUTH_STATE } from '@flusys/ng-layout';
37
+
38
+ private readonly companyContext = inject(LAYOUT_AUTH_STATE);
39
+
40
+ readonly currentCompanyName = computed(
41
+ () => this.companyContext.currentCompanyInfo()?.name ?? DEFAULT_APP_NAME,
42
+ );
43
+ ```
44
+
45
+ ---
46
+
47
+ ## Package Architecture
48
+
49
+ ```
50
+ ng-email/
51
+ ├── interfaces/
52
+ │ ├── email-config.interface.ts # IEmailConfig, ICreateEmailConfigDto
53
+ │ ├── email-template.interface.ts # IEmailTemplate, ICreateEmailTemplateDto
54
+ │ ├── email-send.interface.ts # ISendEmailDto, ISendTemplateEmailDto
55
+ │ └── public-api.ts
56
+ ├── services/
57
+ │ ├── email-config-api.service.ts # Config CRUD
58
+ │ ├── email-template-api.service.ts # Template CRUD
59
+ │ ├── email-send.service.ts # Email sending operations
60
+ │ └── public-api.ts
61
+ ├── pages/
62
+ │ ├── config/
63
+ │ │ ├── email-config-list.component.ts
64
+ │ │ └── email-config-form.component.ts
65
+ │ └── template/
66
+ │ ├── template-list.component.ts
67
+ │ └── template-form.component.ts
68
+ ├── routes/
69
+ │ └── email.routes.ts
70
+ └── public-api.ts
71
+ ```
72
+
73
+ ---
74
+
75
+ ## Route Integration
76
+
77
+ ### Adding Email Routes
78
+
79
+ ```typescript
80
+ // app.routes.ts
81
+ import { EMAIL_ROUTES } from '@flusys/ng-email';
82
+
83
+ export const routes: Routes = [
84
+ {
85
+ path: 'email',
86
+ children: EMAIL_ROUTES,
87
+ },
88
+ ];
89
+ ```
90
+
91
+ ### Route Structure
92
+
93
+ | Path | Component | Description |
94
+ |------|-----------|-------------|
95
+ | `/email/configs` | EmailConfigListComponent | List email configurations |
96
+ | `/email/configs/new` | EmailConfigFormComponent | Create new config |
97
+ | `/email/configs/:id` | EmailConfigFormComponent | Edit config |
98
+ | `/email/templates` | TemplateListComponent | List email templates |
99
+ | `/email/templates/new` | TemplateFormComponent | Create new template |
100
+ | `/email/templates/:id` | TemplateFormComponent | Edit template |
101
+
102
+ ---
103
+
104
+ ## Interfaces
105
+
106
+ ### Email Configuration
107
+
108
+ ```typescript
109
+ interface IEmailConfig extends IBaseEntity {
110
+ name: string;
111
+ provider: 'smtp' | 'sendgrid' | 'mailgun';
112
+ config: Record<string, any>; // Provider-specific config
113
+ fromEmail: string;
114
+ fromName: string | null;
115
+ replyTo: string | null;
116
+ isActive: boolean;
117
+ isDefault: boolean; // Set as default configuration
118
+ companyId?: string | null;
119
+ }
120
+
121
+ interface ICreateEmailConfigDto {
122
+ name: string;
123
+ provider: 'smtp' | 'sendgrid' | 'mailgun';
124
+ config: Record<string, any>;
125
+ fromEmail: string;
126
+ fromName?: string;
127
+ replyTo?: string;
128
+ isActive?: boolean;
129
+ isDefault?: boolean;
130
+ }
131
+
132
+ interface IUpdateEmailConfigDto {
133
+ id: string;
134
+ name?: string;
135
+ provider?: 'smtp' | 'sendgrid' | 'mailgun';
136
+ config?: Record<string, any>;
137
+ fromEmail?: string;
138
+ fromName?: string;
139
+ replyTo?: string;
140
+ isActive?: boolean;
141
+ isDefault?: boolean;
142
+ }
143
+ ```
144
+
145
+ ### Provider-Specific Configs
146
+
147
+ ```typescript
148
+ // SMTP
149
+ interface ISmtpConfig {
150
+ host: string;
151
+ port: number;
152
+ secure: boolean;
153
+ auth: {
154
+ user: string;
155
+ pass: string;
156
+ };
157
+ }
158
+
159
+ // SendGrid
160
+ interface ISendGridConfig {
161
+ apiKey: string;
162
+ }
163
+
164
+ // Mailgun
165
+ interface IMailgunConfig {
166
+ apiKey: string;
167
+ domain: string;
168
+ region?: 'us' | 'eu';
169
+ }
170
+ ```
171
+
172
+ ### Email Template
173
+
174
+ ```typescript
175
+ interface IEmailTemplate extends IBaseEntity {
176
+ name: string;
177
+ slug: string;
178
+ description: string | null;
179
+ subject: string;
180
+ schema: Record<string, unknown>;
181
+ htmlContent: string;
182
+ textContent: string | null;
183
+ schemaVersion: number;
184
+ isActive: boolean;
185
+ isHtml: boolean; // true=HTML mode, false=plain text
186
+ metadata: Record<string, unknown> | null;
187
+ companyId?: string | null;
188
+ }
189
+
190
+ interface ICreateEmailTemplateDto {
191
+ name: string;
192
+ slug: string;
193
+ description?: string;
194
+ subject: string;
195
+ schema?: Record<string, unknown>;
196
+ htmlContent: string;
197
+ textContent?: string;
198
+ isActive?: boolean;
199
+ isHtml?: boolean;
200
+ }
201
+
202
+ interface IUpdateEmailTemplateDto {
203
+ id: string;
204
+ name?: string;
205
+ slug?: string;
206
+ description?: string;
207
+ subject?: string;
208
+ schema?: Record<string, unknown>;
209
+ htmlContent?: string;
210
+ textContent?: string;
211
+ isActive?: boolean;
212
+ isHtml?: boolean;
213
+ }
214
+ ```
215
+
216
+ ### Email Sending
217
+
218
+ ```typescript
219
+ // Direct email
220
+ interface ISendEmailDto {
221
+ to: string | string[];
222
+ cc?: string | string[];
223
+ bcc?: string | string[];
224
+ subject: string;
225
+ html: string;
226
+ text?: string;
227
+ from?: string;
228
+ fromName?: string;
229
+ replyTo?: string;
230
+ emailConfigId?: string;
231
+ attachments?: IEmailAttachmentDto[];
232
+ }
233
+
234
+ // Template email
235
+ interface ISendTemplateEmailDto {
236
+ templateId?: string;
237
+ templateSlug?: string;
238
+ to: string | string[];
239
+ cc?: string | string[];
240
+ bcc?: string | string[];
241
+ variables?: Record<string, any>;
242
+ from?: string;
243
+ fromName?: string;
244
+ replyTo?: string;
245
+ emailConfigId?: string;
246
+ attachments?: IEmailAttachmentDto[];
247
+ }
248
+
249
+ // Test email
250
+ interface ITestEmailDto {
251
+ emailConfigId: string;
252
+ recipient: string;
253
+ }
254
+
255
+ // Attachment
256
+ interface IEmailAttachmentDto {
257
+ filename: string;
258
+ content: string; // Base64 encoded
259
+ contentType?: string;
260
+ }
261
+
262
+ // Result
263
+ interface IEmailSendResult {
264
+ success: boolean;
265
+ messageId?: string;
266
+ error?: string;
267
+ }
268
+ ```
269
+
270
+ ---
271
+
272
+ ## Services
273
+
274
+ All services are `providedIn: 'root'`. Company scoping is automatic when the company feature is enabled.
275
+
276
+ ### EmailConfigApiService
277
+
278
+ Email configuration CRUD. Extends `ApiResourceService<IUpdateEmailConfigDto, IEmailConfig>`.
279
+
280
+ **Inherited CRUD methods** (from ApiResourceService): `insert`, `update`, `findById`, `getAll`, `delete` and their `*Async` variants.
281
+
282
+ ### EmailTemplateApiService
283
+
284
+ Email template CRUD. Extends `ApiResourceService<IUpdateEmailTemplateDto, IEmailTemplate>`.
285
+
286
+ **Inherited CRUD methods** (from ApiResourceService): `insert`, `update`, `findById`, `getAll`, `delete` and their `*Async` variants.
287
+
288
+ ### EmailSendService
289
+
290
+ Handles email sending operations. Extends `BaseApiService`.
291
+
292
+ | Method | Endpoint | Description |
293
+ |--------|----------|-------------|
294
+ | `sendDirect(dto)` | `POST /email/send/direct` | Send email directly |
295
+ | `sendTemplate(dto)` | `POST /email/send/template` | Send using template |
296
+ | `testConfiguration(dto)` | `POST /email/send/test` | Test email configuration |
297
+
298
+ ---
299
+
300
+ ## Components
301
+
302
+ All components are standalone, use Angular 21 signals, and lazy-load via routes.
303
+
304
+ ### EmailConfigListComponent
305
+
306
+ Lists email configurations with actions.
307
+
308
+ **Features:**
309
+ - Lazy pagination via `p-datatable`
310
+ - Provider type tags (SMTP, SendGrid, Mailgun)
311
+ - Active/Inactive status badges
312
+ - Test button with dialog for recipient input
313
+ - Edit and delete actions
314
+ - Company info display when enabled
315
+
316
+ **Test Dialog:**
317
+ Opens a PrimeNG dialog to enter test recipient email instead of browser `prompt()`.
318
+
319
+ ```typescript
320
+ readonly showTestDialog = signal(false);
321
+ readonly selectedConfig = signal<IEmailConfig | null>(null);
322
+ testRecipient = '';
323
+
324
+ onTest(config: IEmailConfig): void {
325
+ this.selectedConfig.set(config);
326
+ this.testRecipient = '';
327
+ this.showTestDialog.set(true);
328
+ }
329
+
330
+ async sendTestEmail(): Promise<void> {
331
+ const config = this.selectedConfig();
332
+ if (!config || !this.testRecipient) return;
333
+
334
+ const response = await firstValueFrom(
335
+ this.emailSendService.testConfiguration({
336
+ emailConfigId: config.id,
337
+ recipient: this.testRecipient,
338
+ })
339
+ );
340
+
341
+ if (response.data?.success) {
342
+ this.messageService.add({
343
+ severity: 'success',
344
+ summary: 'Success',
345
+ detail: `Test email sent! Message ID: ${response.data.messageId}`,
346
+ });
347
+ this.showTestDialog.set(false);
348
+ }
349
+ }
350
+ ```
351
+
352
+ ### EmailConfigFormComponent
353
+
354
+ Create/edit email configuration with dynamic provider fields.
355
+
356
+ **Features:**
357
+ - Dynamic form fields based on selected provider
358
+ - SMTP: host, port, secure, username, password
359
+ - SendGrid: apiKey
360
+ - Mailgun: apiKey, domain, region
361
+ - Active toggle
362
+ - **Set as Default toggle** - marks configuration as the default for sending emails
363
+ - Create/Edit mode auto-detection
364
+
365
+ **Default Configuration:**
366
+ When `isDefault: true`, this configuration will be used automatically when no `emailConfigId` is provided in send requests.
367
+
368
+ ### TemplateListComponent
369
+
370
+ Lists email templates with test send functionality.
371
+
372
+ **Features:**
373
+ - Lazy pagination via `p-datatable`
374
+ - HTML/Text type badges (based on `isHtml`)
375
+ - Active/Inactive status badges
376
+ - Test send button with dialog
377
+ - Edit and delete actions
378
+
379
+ **Test Send Dialog:**
380
+ - Select email configuration
381
+ - Enter recipient email
382
+ - **Dynamic variable inputs** extracted from template content
383
+
384
+ ```typescript
385
+ /** Extract variables from template content */
386
+ readonly templateVariables = computed(() => {
387
+ const template = this.selectedTemplate();
388
+ if (!template) return [];
389
+
390
+ // Combine all content sources to extract variables
391
+ const content = [
392
+ template.subject,
393
+ template.htmlContent,
394
+ template.textContent || '',
395
+ ].join(' ');
396
+
397
+ // Find all {{variableName}} patterns
398
+ const matches = content.matchAll(/\{\{(\w+)\}\}/g);
399
+ const variables = new Set<string>();
400
+
401
+ for (const match of matches) {
402
+ variables.add(match[1]);
403
+ }
404
+
405
+ return Array.from(variables).sort();
406
+ });
407
+
408
+ // Variable values bound to form inputs
409
+ variableValues: Record<string, string> = {};
410
+ ```
411
+
412
+ ### TemplateFormComponent
413
+
414
+ Create/edit email template with HTML/Plain Text toggle.
415
+
416
+ **Features:**
417
+ - Name, slug, description fields
418
+ - Subject line with variable support
419
+ - **Editor Mode Toggle** (HTML/Plain Text)
420
+ - HTML content editor (when `isHtml: true`)
421
+ - Plain text content editor (editable, not read-only)
422
+ - Content sync between modes when switching
423
+ - Preview with variable highlighting
424
+ - Active toggle
425
+
426
+ **Editor Mode Switching:**
427
+
428
+ ```typescript
429
+ setEditorMode(mode: 'html' | 'text'): void {
430
+ const currentMode = this.editorMode();
431
+
432
+ // Sync content when switching modes
433
+ if (currentMode === 'text' && mode === 'html') {
434
+ if (!this.formModel.htmlContent && this.formModel.textContent) {
435
+ this.formModel.htmlContent = this.textToHtml(this.formModel.textContent);
436
+ }
437
+ } else if (currentMode === 'html' && mode === 'text') {
438
+ if (!this.formModel.textContent && this.formModel.htmlContent) {
439
+ this.formModel.textContent = this.htmlToPlainText(this.formModel.htmlContent);
440
+ }
441
+ }
442
+
443
+ this.editorMode.set(mode);
444
+ this.formModel.isHtml = mode === 'html';
445
+ }
446
+ ```
447
+
448
+ ---
449
+
450
+ ## Usage Examples
451
+
452
+ ### Send Direct Email
453
+
454
+ ```typescript
455
+ import { EmailSendService } from '@flusys/ng-email';
456
+
457
+ private readonly emailSendService = inject(EmailSendService);
458
+
459
+ async sendEmail(): Promise<void> {
460
+ const response = await firstValueFrom(
461
+ this.emailSendService.sendDirect({
462
+ to: 'user@example.com',
463
+ subject: 'Hello!',
464
+ html: '<h1>Hello World</h1>',
465
+ text: 'Hello World',
466
+ emailConfigId: 'config-id',
467
+ })
468
+ );
469
+
470
+ if (response.data?.success) {
471
+ console.log('Sent! Message ID:', response.data.messageId);
472
+ }
473
+ }
474
+ ```
475
+
476
+ ### Send Template Email
477
+
478
+ ```typescript
479
+ async sendTemplateEmail(): Promise<void> {
480
+ const response = await firstValueFrom(
481
+ this.emailSendService.sendTemplate({
482
+ templateId: 'template-id',
483
+ to: 'user@example.com',
484
+ emailConfigId: 'config-id',
485
+ variables: {
486
+ userName: 'John',
487
+ appName: 'My App',
488
+ verificationLink: 'https://example.com/verify/abc123',
489
+ },
490
+ })
491
+ );
492
+
493
+ if (response.data?.success) {
494
+ console.log('Sent! Message ID:', response.data.messageId);
495
+ }
496
+ }
497
+ ```
498
+
499
+ ### Test Configuration
500
+
501
+ ```typescript
502
+ async testConfig(configId: string, recipient: string): Promise<boolean> {
503
+ const response = await firstValueFrom(
504
+ this.emailSendService.testConfiguration({
505
+ emailConfigId: configId,
506
+ recipient: recipient,
507
+ })
508
+ );
509
+
510
+ return response.data?.success ?? false;
511
+ }
512
+ ```
513
+
514
+ ### Create Email Template
515
+
516
+ ```typescript
517
+ import { EmailTemplateApiService } from '@flusys/ng-email';
518
+
519
+ private readonly templateService = inject(EmailTemplateApiService);
520
+
521
+ async createTemplate(): Promise<void> {
522
+ const response = await firstValueFrom(
523
+ this.templateService.insert({
524
+ name: 'Welcome Email',
525
+ slug: 'welcome-email',
526
+ description: 'Sent to new users',
527
+ subject: 'Welcome {{userName}}!',
528
+ isHtml: true,
529
+ htmlContent: '<h1>Welcome, {{userName}}!</h1><p>Thank you for joining.</p>',
530
+ textContent: 'Welcome, {{userName}}! Thank you for joining.',
531
+ isActive: true,
532
+ })
533
+ );
534
+
535
+ if (response.success) {
536
+ console.log('Template created:', response.data?.id);
537
+ }
538
+ }
539
+ ```
540
+
541
+ ---
542
+
543
+ ## Backend Integration
544
+
545
+ All endpoints use **POST-only RPC** style (not REST).
546
+
547
+ | Service | Endpoint | Description |
548
+ |---------|----------|-------------|
549
+ | Config | `POST /email/config/insert` | Create config |
550
+ | Config | `POST /email/config/get/:id` | Get config by ID |
551
+ | Config | `POST /email/config/get-all` | List configs (paginated) |
552
+ | Config | `POST /email/config/update` | Update config |
553
+ | Config | `POST /email/config/delete` | Delete config |
554
+ | Template | `POST /email/template/insert` | Create template |
555
+ | Template | `POST /email/template/get/:id` | Get template by ID |
556
+ | Template | `POST /email/template/get-all` | List templates (paginated) |
557
+ | Template | `POST /email/template/update` | Update template |
558
+ | Template | `POST /email/template/delete` | Delete template |
559
+ | Send | `POST /email/send/direct` | Send email directly |
560
+ | Send | `POST /email/send/template` | Send using template |
561
+ | Send | `POST /email/send/test` | Test configuration |
562
+
563
+ ---
564
+
565
+ ## Best Practices
566
+
567
+ ### 1. Use Template Slugs for Code References
568
+
569
+ ```typescript
570
+ // ✅ Use slug for programmatic access
571
+ await this.emailSendService.sendTemplate({
572
+ templateSlug: 'welcome-email', // Stable identifier
573
+ ...
574
+ });
575
+
576
+ // ❌ Don't hardcode template IDs
577
+ await this.emailSendService.sendTemplate({
578
+ templateId: 'f47ac10b-58cc-4372-a567-0e02b2c3d479', // May change
579
+ ...
580
+ });
581
+ ```
582
+
583
+ ### 2. Provide Both HTML and Text Content
584
+
585
+ ```typescript
586
+ // ✅ Provide both for maximum compatibility
587
+ {
588
+ isHtml: true,
589
+ htmlContent: '<h1>Hello!</h1>',
590
+ textContent: 'Hello!', // Fallback for text-only clients
591
+ }
592
+ ```
593
+
594
+ ### 3. Test Configurations Before Production
595
+
596
+ Always use the test send feature to verify configurations work before using them in production.
597
+
598
+ ### 4. Use Meaningful Variable Names
599
+
600
+ ```typescript
601
+ // ✅ Clear, descriptive variable names
602
+ "Hello {{userName}}, your order #{{orderNumber}} shipped on {{shipDate}}."
603
+
604
+ // ❌ Vague or abbreviated names
605
+ "Hello {{u}}, order {{o}} shipped {{d}}."
606
+ ```
607
+
608
+ ### 5. Handle Send Failures Gracefully
609
+
610
+ ```typescript
611
+ async sendEmail(): Promise<void> {
612
+ const response = await firstValueFrom(
613
+ this.emailSendService.sendTemplate({ ... })
614
+ );
615
+
616
+ if (response.data?.success) {
617
+ this.messageService.add({
618
+ severity: 'success',
619
+ summary: 'Email Sent',
620
+ detail: `Message ID: ${response.data.messageId}`,
621
+ });
622
+ } else {
623
+ this.messageService.add({
624
+ severity: 'error',
625
+ summary: 'Send Failed',
626
+ detail: response.data?.error || 'Unknown error occurred',
627
+ });
628
+ }
629
+ }
630
+ ```
631
+
632
+ ---
633
+
634
+ ## Public API Exports
635
+
636
+ ```typescript
637
+ // Interfaces
638
+ export {
639
+ IEmailConfig, ICreateEmailConfigDto, IUpdateEmailConfigDto,
640
+ ISmtpConfig, ISendGridConfig, IMailgunConfig,
641
+ IEmailTemplate, ICreateEmailTemplateDto, IUpdateEmailTemplateDto,
642
+ ISendEmailDto, ISendTemplateEmailDto, ITestEmailDto,
643
+ IEmailAttachmentDto, IEmailSendResult,
644
+ } from '@flusys/ng-email';
645
+
646
+ // Services
647
+ export {
648
+ EmailConfigApiService,
649
+ EmailTemplateApiService,
650
+ EmailSendService,
651
+ } from '@flusys/ng-email';
652
+
653
+ // Routes
654
+ export { EMAIL_ROUTES } from '@flusys/ng-email';
655
+ ```
656
+
657
+ ---
658
+
659
+ **Last Updated:** 2026-02-17
660
+ **Angular Version:** 21