@flusys/ng-email 1.0.0-rc

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,872 @@
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 with live preview
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
+ - **Responsive Design** - Mobile-first with horizontal scroll tables
19
+ - **Dark Mode** - Full dark/light mode support
20
+
21
+ ### Package Hierarchy
22
+
23
+ ```
24
+ @flusys/ng-core <- Foundation (APP_CONFIG, DEFAULT_APP_NAME)
25
+ |
26
+ @flusys/ng-shared <- Shared utilities (ApiResourceService, IBaseEntity)
27
+ |
28
+ @flusys/ng-layout <- Layout (LAYOUT_AUTH_STATE for company context)
29
+ |
30
+ @flusys/ng-email <- Email management (THIS PACKAGE)
31
+ ```
32
+
33
+ ### Company Context
34
+
35
+ 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.
36
+
37
+ ```typescript
38
+ import { LAYOUT_AUTH_STATE } from '@flusys/ng-layout';
39
+ import { APP_CONFIG, DEFAULT_APP_NAME } from '@flusys/ng-core';
40
+
41
+ // Optional injection - works with or without company feature
42
+ private readonly companyContext = inject(LAYOUT_AUTH_STATE, { optional: true });
43
+
44
+ readonly showCompanyInfo = computed(
45
+ () => this.appConfig.enableCompanyFeature && !!this.companyContext,
46
+ );
47
+
48
+ readonly currentCompanyName = computed(
49
+ () => this.companyContext?.currentCompanyInfo()?.name ?? DEFAULT_APP_NAME,
50
+ );
51
+ ```
52
+
53
+ ---
54
+
55
+ ## Package Architecture
56
+
57
+ ```
58
+ ng-email/
59
+ ├── enums/
60
+ │ ├── email-provider.enum.ts # EmailProviderEnum (smtp, sendgrid, mailgun)
61
+ │ ├── email-block-type.enum.ts # EmailBlockType (text, image, button, etc.)
62
+ │ └── public-api.ts
63
+ ├── interfaces/
64
+ │ ├── email-config.interface.ts # IEmailConfig, ICreateEmailConfigDto
65
+ │ ├── email-template.interface.ts # IEmailTemplate, ICreateEmailTemplateDto
66
+ │ ├── email-schema.interface.ts # IEmailSchema, IEmailSection, IEmailBlock
67
+ │ ├── email-send.interface.ts # ISendEmailDto, ISendTemplateEmailDto
68
+ │ └── public-api.ts
69
+ ├── services/
70
+ │ ├── email-config-api.service.ts # Config CRUD + test sending
71
+ │ ├── email-template-api.service.ts # Template CRUD + getBySlug
72
+ │ ├── email-send.service.ts # Email sending operations
73
+ │ ├── email-builder-state.service.ts # Template builder state management
74
+ │ └── public-api.ts
75
+ ├── pages/
76
+ │ ├── email-container/
77
+ │ │ └── email-container.component.ts # Main container with tabs
78
+ │ ├── config/
79
+ │ │ ├── email-config-list.component.ts
80
+ │ │ └── email-config-form.component.ts
81
+ │ └── template/
82
+ │ ├── template-list.component.ts
83
+ │ └── template-form.component.ts
84
+ ├── routes/
85
+ │ └── email.routes.ts
86
+ └── public-api.ts
87
+ ```
88
+
89
+ ---
90
+
91
+ ## Route Integration
92
+
93
+ ### Adding Email Routes
94
+
95
+ ```typescript
96
+ // app.routes.ts
97
+ import { EMAIL_ROUTES } from '@flusys/ng-email';
98
+
99
+ export const routes: Routes = [
100
+ {
101
+ path: 'email',
102
+ children: EMAIL_ROUTES,
103
+ },
104
+ ];
105
+ ```
106
+
107
+ ### Route Structure
108
+
109
+ | Path | Component | Description |
110
+ |------|-----------|-------------|
111
+ | `/email` | EmailContainerComponent | Main container with tab navigation |
112
+ | `/email/templates` | TemplateListComponent | List email templates |
113
+ | `/email/templates/new` | TemplateFormComponent | Create new template |
114
+ | `/email/templates/:id` | TemplateFormComponent | Edit template |
115
+ | `/email/configs` | EmailConfigListComponent | List email configurations |
116
+ | `/email/configs/new` | EmailConfigFormComponent | Create new config |
117
+ | `/email/configs/:id` | EmailConfigFormComponent | Edit config |
118
+
119
+ ---
120
+
121
+ ## Enums
122
+
123
+ ### EmailProviderEnum
124
+
125
+ ```typescript
126
+ export enum EmailProviderEnum {
127
+ SMTP = 'smtp',
128
+ SENDGRID = 'sendgrid',
129
+ MAILGUN = 'mailgun',
130
+ }
131
+ ```
132
+
133
+ ### EmailBlockType
134
+
135
+ ```typescript
136
+ export enum EmailBlockType {
137
+ TEXT = 'text',
138
+ IMAGE = 'image',
139
+ BUTTON = 'button',
140
+ DIVIDER = 'divider',
141
+ HTML = 'html',
142
+ }
143
+ ```
144
+
145
+ ---
146
+
147
+ ## Interfaces
148
+
149
+ ### Email Configuration
150
+
151
+ ```typescript
152
+ interface IEmailConfig extends IBaseEntity {
153
+ name: string;
154
+ provider: EmailProviderEnum;
155
+ config: Record<string, any>; // Provider-specific config
156
+ fromEmail: string | null;
157
+ fromName: string | null;
158
+ isActive: boolean;
159
+ isDefault: boolean; // Set as default configuration
160
+ companyId?: string | null;
161
+ }
162
+
163
+ interface ICreateEmailConfigDto {
164
+ name: string;
165
+ provider: EmailProviderEnum;
166
+ config: Record<string, any>;
167
+ fromEmail?: string;
168
+ fromName?: string;
169
+ isActive?: boolean;
170
+ isDefault?: boolean;
171
+ }
172
+
173
+ interface IUpdateEmailConfigDto extends Partial<ICreateEmailConfigDto> {
174
+ id: string;
175
+ }
176
+ ```
177
+
178
+ ### Provider-Specific Configs
179
+
180
+ ```typescript
181
+ // SMTP
182
+ interface ISmtpConfig {
183
+ host: string;
184
+ port: number;
185
+ secure?: boolean;
186
+ username: string;
187
+ password: string;
188
+ }
189
+
190
+ // SendGrid
191
+ interface ISendGridConfig {
192
+ apiKey: string;
193
+ }
194
+
195
+ // Mailgun
196
+ interface IMailgunConfig {
197
+ apiKey: string;
198
+ domain: string;
199
+ region?: 'us' | 'eu';
200
+ }
201
+ ```
202
+
203
+ ### Email Template
204
+
205
+ ```typescript
206
+ interface IEmailTemplate extends IBaseEntity {
207
+ name: string;
208
+ slug: string;
209
+ description: string | null;
210
+ subject: string;
211
+ schema: IEmailSchema;
212
+ htmlContent: string;
213
+ textContent: string | null;
214
+ schemaVersion: number;
215
+ isActive: boolean;
216
+ isHtml: boolean; // true=HTML mode, false=plain text
217
+ metadata: Record<string, unknown> | null;
218
+ companyId?: string | null;
219
+ }
220
+
221
+ interface ICreateEmailTemplateDto {
222
+ name: string;
223
+ slug: string;
224
+ description?: string;
225
+ subject: string;
226
+ schema: IEmailSchema;
227
+ htmlContent: string;
228
+ textContent?: string;
229
+ isActive?: boolean;
230
+ isHtml?: boolean;
231
+ metadata?: Record<string, unknown>;
232
+ }
233
+
234
+ interface IUpdateEmailTemplateDto extends Partial<ICreateEmailTemplateDto> {
235
+ id: string;
236
+ }
237
+ ```
238
+
239
+ ### Email Schema
240
+
241
+ ```typescript
242
+ interface IEmailSchema {
243
+ id: string;
244
+ version: string;
245
+ name: string;
246
+ subject: string;
247
+ preheader?: string;
248
+ sections: IEmailSection[];
249
+ variables?: IEmailTemplateVariable[];
250
+ settings?: IEmailSettings;
251
+ }
252
+
253
+ interface IEmailSection {
254
+ id: string;
255
+ type: 'header' | 'body' | 'footer';
256
+ name: string;
257
+ blocks: IEmailBlock[];
258
+ order: number;
259
+ style?: IEmailSectionStyle;
260
+ }
261
+
262
+ interface IEmailBlock {
263
+ id: string;
264
+ type: EmailBlockType;
265
+ order: number;
266
+ content: ITextBlockContent | IImageBlockContent | IButtonBlockContent | IDividerBlockContent | IHtmlBlockContent;
267
+ style?: IEmailBlockStyle;
268
+ }
269
+
270
+ interface IEmailTemplateVariable {
271
+ name: string;
272
+ type: 'string' | 'number' | 'boolean' | 'date';
273
+ required?: boolean;
274
+ defaultValue?: string;
275
+ description?: string;
276
+ }
277
+ ```
278
+
279
+ ### Email Sending
280
+
281
+ ```typescript
282
+ // Test send result
283
+ interface ITestSendResult {
284
+ success: boolean;
285
+ messageId?: string;
286
+ error?: string;
287
+ }
288
+
289
+ // Direct email
290
+ interface ISendEmailDto {
291
+ to: string | string[];
292
+ cc?: string | string[];
293
+ bcc?: string | string[];
294
+ subject: string;
295
+ html: string;
296
+ text?: string;
297
+ from?: string;
298
+ fromName?: string;
299
+ replyTo?: string;
300
+ emailConfigId?: string;
301
+ }
302
+
303
+ // Template email
304
+ interface ISendTemplateEmailDto {
305
+ templateId?: string;
306
+ templateSlug?: string;
307
+ to: string | string[];
308
+ cc?: string | string[];
309
+ bcc?: string | string[];
310
+ variables?: Record<string, any>;
311
+ from?: string;
312
+ fromName?: string;
313
+ replyTo?: string;
314
+ emailConfigId?: string;
315
+ }
316
+ ```
317
+
318
+ ---
319
+
320
+ ## Services
321
+
322
+ All services are `providedIn: 'root'`. Company scoping is automatic when the company feature is enabled.
323
+
324
+ ### EmailConfigApiService
325
+
326
+ Email configuration CRUD. Extends `ApiResourceService<IUpdateEmailConfigDto, IEmailConfig>`.
327
+
328
+ ```typescript
329
+ @Injectable({ providedIn: 'root' })
330
+ export class EmailConfigApiService extends ApiResourceService<
331
+ IUpdateEmailConfigDto,
332
+ IEmailConfig
333
+ > {
334
+ constructor() {
335
+ const http = inject(HttpClient);
336
+ super('email/email-config', http);
337
+ }
338
+
339
+ /** Send test email to verify configuration */
340
+ sendTest(configId: string, recipient: string): Observable<ISingleResponse<ITestSendResult>> {
341
+ return this.http.post<ISingleResponse<ITestSendResult>>(
342
+ `${this.baseUrl.replace('/email-config', '')}/send/test`,
343
+ { emailConfigId: configId, recipient },
344
+ );
345
+ }
346
+ }
347
+ ```
348
+
349
+ ### EmailTemplateApiService
350
+
351
+ Email template CRUD. Extends `ApiResourceService<IUpdateEmailTemplateDto, IEmailTemplate>`.
352
+
353
+ ```typescript
354
+ @Injectable({ providedIn: 'root' })
355
+ export class EmailTemplateApiService extends ApiResourceService<
356
+ IUpdateEmailTemplateDto,
357
+ IEmailTemplate
358
+ > {
359
+ constructor() {
360
+ const http = inject(HttpClient);
361
+ super('email/email-template', http);
362
+ }
363
+
364
+ /** Get template by slug (POST-only RPC pattern) */
365
+ getBySlug(slug: string): Observable<ISingleResponse<IEmailTemplate | null>> {
366
+ return this.http.post<ISingleResponse<IEmailTemplate | null>>(
367
+ `${this.baseUrl}/get-by-slug`,
368
+ { slug },
369
+ );
370
+ }
371
+ }
372
+ ```
373
+
374
+ ### EmailSendService
375
+
376
+ Handles email sending operations.
377
+
378
+ ```typescript
379
+ @Injectable({ providedIn: 'root' })
380
+ export class EmailSendService {
381
+ private readonly http = inject(HttpClient);
382
+ private readonly baseUrl = `${inject(APP_CONFIG).apiBaseUrl}/email/send`;
383
+
384
+ /** Send email directly (without template) */
385
+ sendDirect(dto: ISendEmailDto): Observable<ISingleResponse<ITestSendResult>>;
386
+
387
+ /** Send email using template */
388
+ sendTemplate(dto: ISendTemplateEmailDto): Observable<ISingleResponse<ITestSendResult>>;
389
+ }
390
+ ```
391
+
392
+ ### EmailBuilderStateService
393
+
394
+ Manages email builder UI state. Should be provided at component level, not root.
395
+
396
+ ```typescript
397
+ @Injectable()
398
+ export class EmailBuilderStateService {
399
+ // Schema state
400
+ readonly schema: Signal<IEmailSchema>;
401
+ readonly isDirty: Signal<boolean>;
402
+
403
+ // Selection state
404
+ readonly selectedSectionId: Signal<string | null>;
405
+ readonly selectedBlockId: Signal<string | null>;
406
+
407
+ // Computed signals
408
+ readonly sections: Signal<IEmailSection[]>;
409
+ readonly headerSection: Signal<IEmailSection | null>;
410
+ readonly bodySection: Signal<IEmailSection | null>;
411
+ readonly footerSection: Signal<IEmailSection | null>;
412
+
413
+ // Methods
414
+ loadSchema(schema: IEmailSchema): void;
415
+ createNewSchema(name?: string): void;
416
+ addBlock(sectionId: string, blockType: EmailBlockType): string | null;
417
+ updateBlock(sectionId: string, blockId: string, updates: Partial<IEmailBlock>): void;
418
+ deleteBlock(sectionId: string, blockId: string): void;
419
+ }
420
+ ```
421
+
422
+ ---
423
+
424
+ ## Components
425
+
426
+ All components are standalone, use Angular 21 signals, OnPush change detection, and lazy-load via routes.
427
+
428
+ ### EmailContainerComponent
429
+
430
+ Main container with tab navigation between Templates and Configurations.
431
+
432
+ **Features:**
433
+ - Responsive page title (`text-xl sm:text-2xl`)
434
+ - Tab navigation with horizontal scroll on mobile
435
+ - Router outlet for child routes
436
+
437
+ ```html
438
+ <div class="p-2 sm:p-4">
439
+ <h1 class="text-xl sm:text-2xl font-bold m-0">Email</h1>
440
+ <p class="text-sm text-muted-color mt-1">Manage email templates...</p>
441
+
442
+ <div class="scrollbar-hide flex gap-1 mb-4 border-b border-surface overflow-x-auto">
443
+ @for (tab of tabs; track tab.id) {
444
+ <a [routerLink]="tab.route" routerLinkActive="tab-active">...</a>
445
+ }
446
+ </div>
447
+
448
+ <router-outlet />
449
+ </div>
450
+ ```
451
+
452
+ ### EmailConfigListComponent
453
+
454
+ Lists email configurations with actions.
455
+
456
+ **Features:**
457
+ - Lazy pagination via `p-table` with `[paginator]="totalRecords() > 0"`
458
+ - Horizontal scroll on mobile (`overflow-x-auto -mx-4 sm:mx-0`)
459
+ - Provider type tags (SMTP, SendGrid, Mailgun)
460
+ - Active/Inactive status badges
461
+ - Default configuration badge
462
+ - Test button with dialog for recipient input
463
+ - Edit and delete actions
464
+ - Company info display when enabled
465
+
466
+ **Test Dialog:**
467
+
468
+ ```typescript
469
+ showTestDialog = false; // Plain property for ngModel
470
+ readonly selectedConfig = signal<IEmailConfig | null>(null);
471
+ readonly isSendingTest = signal(false);
472
+ testRecipient = '';
473
+
474
+ onTest(config: IEmailConfig): void {
475
+ this.selectedConfig.set(config);
476
+ this.testRecipient = '';
477
+ this.showTestDialog = true;
478
+ }
479
+
480
+ async sendTestEmail(): Promise<void> {
481
+ const config = this.selectedConfig();
482
+ if (!config || !this.testRecipient) return;
483
+
484
+ const response = await firstValueFrom(
485
+ this.configService.sendTest(config.id, this.testRecipient)
486
+ );
487
+
488
+ if (response.data?.success) {
489
+ this.messageService.add({
490
+ severity: 'success',
491
+ summary: 'Success',
492
+ detail: `Test email sent! Message ID: ${response.data.messageId}`,
493
+ });
494
+ this.showTestDialog = false;
495
+ }
496
+ }
497
+ ```
498
+
499
+ ### EmailConfigFormComponent
500
+
501
+ Create/edit email configuration with dynamic provider fields.
502
+
503
+ **Features:**
504
+ - Responsive grid layout (`grid-cols-1 md:grid-cols-2`)
505
+ - Dynamic form fields based on selected provider
506
+ - SMTP: host, port, secure toggle, username, password
507
+ - SendGrid: apiKey (masked input)
508
+ - Mailgun: apiKey, domain, region
509
+ - Active toggle and Set as Default toggle
510
+ - Form actions right-aligned at bottom
511
+ - Create/Edit mode auto-detection
512
+
513
+ ### TemplateListComponent
514
+
515
+ Lists email templates with test send functionality.
516
+
517
+ **Features:**
518
+ - Lazy pagination with conditional display
519
+ - Responsive table with hidden columns on mobile
520
+ - HTML/Text type badges (based on `isHtml`)
521
+ - Active/Inactive status badges
522
+ - Test send with dynamic variables
523
+ - Edit and delete actions
524
+
525
+ **Test Send Dialog with Variables:**
526
+
527
+ ```typescript
528
+ /** Extract variables from template content */
529
+ readonly templateVariables = computed(() => {
530
+ const template = this.selectedTemplate();
531
+ if (!template) return [];
532
+
533
+ const content = [
534
+ template.subject,
535
+ template.htmlContent,
536
+ template.textContent || '',
537
+ ].join(' ');
538
+
539
+ const matches = content.matchAll(/\{\{(\w+)\}\}/g);
540
+ const variables = new Set<string>();
541
+
542
+ for (const match of matches) {
543
+ variables.add(match[1]);
544
+ }
545
+
546
+ return Array.from(variables).sort();
547
+ });
548
+
549
+ variableValues: Record<string, string> = {};
550
+ ```
551
+
552
+ ### TemplateFormComponent
553
+
554
+ Create/edit email template with HTML/Plain Text toggle and live preview.
555
+
556
+ **Features:**
557
+ - Responsive form grid (`grid-cols-1 md:grid-cols-2`)
558
+ - Name, slug, subject, description fields
559
+ - Subject line with variable support hint
560
+ - Editor Mode Toggle (HTML/Plain Text buttons)
561
+ - Same height content editor and preview (`h-[400px]`)
562
+ - Horizontal scroll for code (`wrap="off"`)
563
+ - Live HTML preview in iframe with white background
564
+ - Content sync between modes when switching
565
+ - Active toggle
566
+ - Form actions right-aligned at bottom
567
+
568
+ **Preview with Light Background:**
569
+
570
+ ```typescript
571
+ getPreviewHtml(): string {
572
+ const baseStyles = `
573
+ <style>
574
+ body {
575
+ font-family: Arial, sans-serif;
576
+ margin: 16px;
577
+ background-color: #ffffff;
578
+ color: #333333;
579
+ }
580
+ img { max-width: 100%; height: auto; }
581
+ </style>
582
+ `;
583
+ return baseStyles + (this.formModel.htmlContent || '');
584
+ }
585
+ ```
586
+
587
+ ---
588
+
589
+ ## Responsive Design Patterns
590
+
591
+ ### Tables
592
+
593
+ ```html
594
+ <!-- Horizontal scroll wrapper -->
595
+ <div class="overflow-x-auto -mx-4 sm:mx-0">
596
+ <p-table
597
+ [paginator]="totalRecords() > 0"
598
+ styleClass="p-datatable-sm"
599
+ [tableStyle]="{ 'min-width': '50rem' }"
600
+ >
601
+ <!-- Hidden columns on smaller screens -->
602
+ <th class="hidden md:table-cell">Slug</th>
603
+ <th class="hidden lg:table-cell">Subject</th>
604
+ <th class="hidden xl:table-cell">Created</th>
605
+ </p-table>
606
+ </div>
607
+ ```
608
+
609
+ ### Forms
610
+
611
+ ```html
612
+ <!-- Responsive grid -->
613
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
614
+ <div class="field">...</div>
615
+ <div class="field md:col-span-2">...</div> <!-- Full width on all screens -->
616
+ </div>
617
+
618
+ <!-- Form actions -->
619
+ <div class="flex justify-end gap-2 mt-6 pt-4 border-t border-surface">
620
+ <p-button label="Cancel" severity="secondary" [outlined]="true" routerLink="../" />
621
+ <p-button [label]="isEditMode() ? 'Update' : 'Create'" icon="pi pi-save" />
622
+ </div>
623
+ ```
624
+
625
+ ### Headers
626
+
627
+ ```html
628
+ <div class="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-3 mb-4">
629
+ <div>
630
+ <h3 class="text-lg sm:text-xl font-semibold m-0">Title</h3>
631
+ @if (showCompanyInfo()) {
632
+ <p class="text-sm text-muted-color mt-1">Company: {{ currentCompanyName() }}</p>
633
+ }
634
+ </div>
635
+ <p-button label="Action" styleClass="w-full sm:w-auto" />
636
+ </div>
637
+ ```
638
+
639
+ ---
640
+
641
+ ## Dark Mode Support
642
+
643
+ All components use PrimeNG/Tailwind dark mode classes:
644
+
645
+ | Element | Classes |
646
+ |---------|---------|
647
+ | Borders | `border-surface` |
648
+ | Text | `text-muted-color` |
649
+ | Backgrounds | `bg-surface-0 dark:bg-surface-900`, `bg-surface-100 dark:bg-surface-700` |
650
+ | Code blocks | `bg-surface-100 dark:bg-surface-700` |
651
+ | Hover states | `hover:bg-surface-hover` |
652
+
653
+ ---
654
+
655
+ ## Usage Examples
656
+
657
+ ### Send Direct Email
658
+
659
+ ```typescript
660
+ import { EmailSendService } from '@flusys/ng-email';
661
+
662
+ private readonly emailSendService = inject(EmailSendService);
663
+
664
+ async sendEmail(): Promise<void> {
665
+ const response = await firstValueFrom(
666
+ this.emailSendService.sendDirect({
667
+ to: 'user@example.com',
668
+ subject: 'Hello!',
669
+ html: '<h1>Hello World</h1>',
670
+ text: 'Hello World',
671
+ emailConfigId: 'config-id',
672
+ })
673
+ );
674
+
675
+ if (response.data?.success) {
676
+ console.log('Sent! Message ID:', response.data.messageId);
677
+ }
678
+ }
679
+ ```
680
+
681
+ ### Send Template Email
682
+
683
+ ```typescript
684
+ async sendTemplateEmail(): Promise<void> {
685
+ const response = await firstValueFrom(
686
+ this.emailSendService.sendTemplate({
687
+ templateId: 'template-id',
688
+ to: 'user@example.com',
689
+ emailConfigId: 'config-id',
690
+ variables: {
691
+ userName: 'John',
692
+ appName: 'My App',
693
+ verificationLink: 'https://example.com/verify/abc123',
694
+ },
695
+ })
696
+ );
697
+
698
+ if (response.data?.success) {
699
+ console.log('Sent! Message ID:', response.data.messageId);
700
+ }
701
+ }
702
+ ```
703
+
704
+ ### Test Configuration
705
+
706
+ ```typescript
707
+ import { EmailConfigApiService } from '@flusys/ng-email';
708
+
709
+ private readonly configService = inject(EmailConfigApiService);
710
+
711
+ async testConfig(configId: string, recipient: string): Promise<boolean> {
712
+ const response = await firstValueFrom(
713
+ this.configService.sendTest(configId, recipient)
714
+ );
715
+
716
+ return response.data?.success ?? false;
717
+ }
718
+ ```
719
+
720
+ ### Get Template by Slug
721
+
722
+ ```typescript
723
+ import { EmailTemplateApiService } from '@flusys/ng-email';
724
+
725
+ private readonly templateService = inject(EmailTemplateApiService);
726
+
727
+ async getTemplate(slug: string): Promise<IEmailTemplate | null> {
728
+ const response = await firstValueFrom(
729
+ this.templateService.getBySlug(slug)
730
+ );
731
+
732
+ return response.data ?? null;
733
+ }
734
+ ```
735
+
736
+ ---
737
+
738
+ ## Backend Integration
739
+
740
+ All endpoints use **POST-only RPC** style (not REST).
741
+
742
+ | Service | Endpoint | Description |
743
+ |---------|----------|-------------|
744
+ | Config | `POST /email/email-config/insert` | Create config |
745
+ | Config | `POST /email/email-config/get/:id` | Get config by ID |
746
+ | Config | `POST /email/email-config/get-all` | List configs (paginated) |
747
+ | Config | `POST /email/email-config/update` | Update config |
748
+ | Config | `POST /email/email-config/delete` | Delete config |
749
+ | Template | `POST /email/email-template/insert` | Create template |
750
+ | Template | `POST /email/email-template/get/:id` | Get template by ID |
751
+ | Template | `POST /email/email-template/get-all` | List templates (paginated) |
752
+ | Template | `POST /email/email-template/get-by-slug` | Get template by slug |
753
+ | Template | `POST /email/email-template/update` | Update template |
754
+ | Template | `POST /email/email-template/delete` | Delete template |
755
+ | Send | `POST /email/send/direct` | Send email directly |
756
+ | Send | `POST /email/send/template` | Send using template |
757
+ | Send | `POST /email/send/test` | Test configuration |
758
+
759
+ ---
760
+
761
+ ## Best Practices
762
+
763
+ ### 1. Use Template Slugs for Code References
764
+
765
+ ```typescript
766
+ // ✅ Use slug for programmatic access
767
+ await this.emailSendService.sendTemplate({
768
+ templateSlug: 'welcome-email', // Stable identifier
769
+ ...
770
+ });
771
+
772
+ // ❌ Don't hardcode template IDs
773
+ await this.emailSendService.sendTemplate({
774
+ templateId: 'f47ac10b-58cc-4372-a567-0e02b2c3d479', // May change
775
+ ...
776
+ });
777
+ ```
778
+
779
+ ### 2. Provide Both HTML and Text Content
780
+
781
+ ```typescript
782
+ // ✅ Provide both for maximum compatibility
783
+ {
784
+ isHtml: true,
785
+ htmlContent: '<h1>Hello!</h1>',
786
+ textContent: 'Hello!', // Fallback for text-only clients
787
+ }
788
+ ```
789
+
790
+ ### 3. Test Configurations Before Production
791
+
792
+ Always use the test send feature to verify configurations work before using them in production.
793
+
794
+ ### 4. Use Meaningful Variable Names
795
+
796
+ ```typescript
797
+ // ✅ Clear, descriptive variable names
798
+ "Hello {{userName}}, your order #{{orderNumber}} shipped on {{shipDate}}."
799
+
800
+ // ❌ Vague or abbreviated names
801
+ "Hello {{u}}, order {{o}} shipped {{d}}."
802
+ ```
803
+
804
+ ### 5. Handle Send Failures Gracefully
805
+
806
+ ```typescript
807
+ async sendEmail(): Promise<void> {
808
+ try {
809
+ const response = await firstValueFrom(
810
+ this.emailSendService.sendTemplate({ ... })
811
+ );
812
+
813
+ if (response.data?.success) {
814
+ this.messageService.add({
815
+ severity: 'success',
816
+ summary: 'Email Sent',
817
+ detail: `Message ID: ${response.data.messageId}`,
818
+ });
819
+ } else {
820
+ this.messageService.add({
821
+ severity: 'error',
822
+ summary: 'Send Failed',
823
+ detail: response.data?.error || 'Unknown error occurred',
824
+ });
825
+ }
826
+ } catch (error: any) {
827
+ this.messageService.add({
828
+ severity: 'error',
829
+ summary: 'Error',
830
+ detail: error?.message || 'Failed to send email.',
831
+ });
832
+ }
833
+ }
834
+ ```
835
+
836
+ ---
837
+
838
+ ## Public API Exports
839
+
840
+ ```typescript
841
+ // Enums
842
+ export { EmailProviderEnum } from '@flusys/ng-email';
843
+ export { EmailBlockType } from '@flusys/ng-email';
844
+
845
+ // Interfaces
846
+ export {
847
+ IEmailConfig, ICreateEmailConfigDto, IUpdateEmailConfigDto,
848
+ ISmtpConfig, ISendGridConfig, IMailgunConfig,
849
+ IEmailTemplate, ICreateEmailTemplateDto, IUpdateEmailTemplateDto,
850
+ IEmailSchema, IEmailSection, IEmailBlock, IEmailTemplateVariable,
851
+ ISendEmailDto, ISendTemplateEmailDto, ITestSendResult,
852
+ } from '@flusys/ng-email';
853
+
854
+ // Services
855
+ export {
856
+ EmailConfigApiService,
857
+ EmailTemplateApiService,
858
+ EmailSendService,
859
+ EmailBuilderStateService,
860
+ } from '@flusys/ng-email';
861
+
862
+ // Components
863
+ export { EmailContainerComponent } from '@flusys/ng-email';
864
+
865
+ // Routes
866
+ export { EMAIL_ROUTES } from '@flusys/ng-email';
867
+ ```
868
+
869
+ ---
870
+
871
+ **Last Updated:** 2026-02-18
872
+ **Angular Version:** 21