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