@flusys/nestjs-email 4.0.2 → 4.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 CHANGED
@@ -1,97 +1,94 @@
1
- # Email Package Guide
1
+ # @flusys/nestjs-email
2
2
 
3
- > **Package:** `@flusys/nestjs-email`
4
- > **Version:** 4.0.2
5
- > **Type:** Email sending with templates, multiple providers, and multi-tenant support
3
+ > Production-grade email management for NestJS — multi-provider (SMTP, SendGrid, Mailgun), database-driven template engine with `{{variable}}` interpolation, company scoping, and multi-tenant support.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@flusys/nestjs-email.svg)](https://www.npmjs.com/package/@flusys/nestjs-email)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+ [![NestJS](https://img.shields.io/badge/NestJS-11.x-red.svg)](https://nestjs.com/)
8
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.x-blue.svg)](https://www.typescriptlang.org/)
9
+ [![Node.js](https://img.shields.io/badge/Node.js-%3E%3D18.x-green.svg)](https://nodejs.org/)
10
+
11
+ ---
6
12
 
7
13
  ## Table of Contents
8
14
 
9
15
  - [Overview](#overview)
16
+ - [Features](#features)
17
+ - [Compatibility](#compatibility)
10
18
  - [Installation](#installation)
11
- - [Constants](#constants)
12
- - [Module Setup](#module-setup)
19
+ - [Quick Start](#quick-start)
20
+ - [Module Registration](#module-registration)
21
+ - [forRoot (Sync)](#forroot-sync)
22
+ - [forRootAsync (Factory)](#forrootasync-factory)
23
+ - [forRootAsync (Class)](#forrootasync-class)
24
+ - [Configuration Reference](#configuration-reference)
25
+ - [Feature Toggles](#feature-toggles)
26
+ - [API Endpoints](#api-endpoints)
13
27
  - [Entities](#entities)
14
- - [Interfaces](#interfaces)
15
28
  - [Email Providers](#email-providers)
16
- - [Email Configuration](#email-configuration)
17
- - [Email Templates](#email-templates)
18
- - [Email Sending](#email-sending)
19
- - [DTOs](#dtos)
20
- - [Utility Functions](#utility-functions)
21
- - [API Endpoints](#api-endpoints)
22
- - [Swagger Documentation](#swagger-documentation)
23
- - [Multi-Tenant Support](#multi-tenant-support)
24
- - [Best Practices](#best-practices)
29
+ - [SMTP (Default)](#smtp-default)
30
+ - [SendGrid](#sendgrid)
31
+ - [Mailgun](#mailgun)
32
+ - [Custom Provider](#custom-provider)
33
+ - [Template Engine](#template-engine)
34
+ - [Exported Services](#exported-services)
35
+ - [Sending Emails Programmatically](#sending-emails-programmatically)
36
+ - [Troubleshooting](#troubleshooting)
37
+ - [License](#license)
25
38
 
26
39
  ---
27
40
 
28
41
  ## Overview
29
42
 
30
- `@flusys/nestjs-email` provides:
43
+ `@flusys/nestjs-email` provides a complete email management system. Email provider configurations and templates are stored in the database — no code changes are needed to add new email templates or switch providers. Providers are loaded dynamically, so you only install the SDK for the providers you use.
31
44
 
32
- - **Multiple Providers** - SMTP, SendGrid, Mailgun with dynamic registration
33
- - **Email Templates** - Variable interpolation with `{{variableName}}` syntax
34
- - **HTML/Plain Text** - Support via `isHtml` toggle
35
- - **Multi-Tenant Support** - Company-scoped configurations and templates
36
- - **Attachments** - File attachment support
45
+ ---
37
46
 
38
- ### Package Hierarchy
47
+ ## Features
39
48
 
40
- ```
41
- @flusys/nestjs-core ← Foundation
42
-
43
- @flusys/nestjs-shared ← Shared utilities
44
-
45
- @flusys/nestjs-email ← Email system (THIS PACKAGE)
46
- ```
49
+ - **Multi-provider** — SMTP (nodemailer), SendGrid, Mailgun with a pluggable custom provider interface
50
+ - **Database-driven templates** — Templates stored in PostgreSQL with `{{variable}}` interpolation
51
+ - **HTML XSS protection** — All variable values are HTML-escaped before interpolation
52
+ - **Provider caching** — SHA-256 config hash prevents duplicate provider instances
53
+ - **Test email** — Verify a provider configuration before using it in production
54
+ - **Company scoping** Optional `companyId` on configs and templates for multi-company setups
55
+ - **Multi-tenant** — Per-tenant DataSource isolation via the DataSource Provider pattern
56
+ - **Attachments** — Base64-encoded file attachments supported
47
57
 
48
58
  ---
49
59
 
50
- ## Installation
51
-
52
- ```bash
53
- npm install @flusys/nestjs-email
60
+ ## Compatibility
54
61
 
55
- # Provider-specific packages (install as needed)
56
- npm install nodemailer # For SMTP
57
- npm install @sendgrid/mail # For SendGrid
58
- npm install mailgun.js form-data # For Mailgun
59
- ```
62
+ | Package | Version |
63
+ |---------|---------|
64
+ | `@flusys/nestjs-core` | `^4.0.0` |
65
+ | `@flusys/nestjs-shared` | `^4.0.0` |
66
+ | `nodemailer` | `^6.0.0` |
67
+ | `@sendgrid/mail` | `^8.0.0` *(optional)* |
68
+ | `mailgun.js` | `^10.0.0` *(optional)* |
69
+ | Node.js | `>= 18.x` |
60
70
 
61
71
  ---
62
72
 
63
- ## Constants
73
+ ## Installation
64
74
 
65
- ```typescript
66
- // Injection Token
67
- export const EMAIL_MODULE_OPTIONS = 'EMAIL_MODULE_OPTIONS';
75
+ ```bash
76
+ npm install @flusys/nestjs-email @flusys/nestjs-shared @flusys/nestjs-core
68
77
 
69
- // Default Configuration
70
- export const DEFAULT_FROM_NAME = 'FLUSYS';
78
+ # Provider-specific SDKs (install only what you use)
79
+ npm install nodemailer # SMTP (recommended default)
80
+ npm install @sendgrid/mail # SendGrid
81
+ npm install mailgun.js form-data # Mailgun
71
82
  ```
72
83
 
73
84
  ---
74
85
 
75
- ## Module Setup
76
-
77
- ### Module Options Interface
86
+ ## Quick Start
78
87
 
79
- ```typescript
80
- interface IEmailModuleConfig extends IDataSourceServiceOptions {
81
- defaultProvider?: string; // Default provider type
82
- rateLimitPerMinute?: number; // Rate limiting
83
- enableLogging?: boolean; // Enable debug logging
84
- }
85
-
86
- interface EmailModuleOptions extends IDynamicModuleConfig {
87
- bootstrapAppConfig?: IBootstrapAppConfig;
88
- config?: IEmailModuleConfig;
89
- }
90
- ```
91
-
92
- ### Basic Setup
88
+ ### Minimal Setup (SMTP, Single Database)
93
89
 
94
90
  ```typescript
91
+ import { Module } from '@nestjs/common';
95
92
  import { EmailModule } from '@flusys/nestjs-email';
96
93
 
97
94
  @Module({
@@ -106,11 +103,11 @@ import { EmailModule } from '@flusys/nestjs-email';
106
103
  config: {
107
104
  defaultDatabaseConfig: {
108
105
  type: 'postgres',
109
- host: 'localhost',
110
- port: 5432,
111
- username: 'user',
112
- password: 'password',
113
- database: 'myapp',
106
+ host: process.env.DB_HOST,
107
+ port: Number(process.env.DB_PORT ?? 5432),
108
+ username: process.env.DB_USER,
109
+ password: process.env.DB_PASSWORD,
110
+ database: process.env.DB_NAME,
114
111
  },
115
112
  },
116
113
  }),
@@ -119,25 +116,36 @@ import { EmailModule } from '@flusys/nestjs-email';
119
116
  export class AppModule {}
120
117
  ```
121
118
 
122
- ### With Company Feature
119
+ After startup, create an email config via the API and then send emails using `EmailSendService`.
120
+
121
+ ---
122
+
123
+ ## Module Registration
124
+
125
+ ### forRoot (Sync)
123
126
 
124
127
  ```typescript
125
128
  EmailModule.forRoot({
126
129
  global: true,
127
130
  includeController: true,
128
131
  bootstrapAppConfig: {
129
- databaseMode: 'single',
130
- enableCompanyFeature: true,
132
+ databaseMode: 'single', // 'single' | 'multi-tenant'
133
+ enableCompanyFeature: false, // true = company-scoped templates & configs
131
134
  },
132
135
  config: {
133
- defaultDatabaseConfig: { ... },
136
+ defaultDatabaseConfig: { /* TypeORM DataSourceOptions */ },
137
+ defaultProvider: 'smtp', // Optional: default provider type
138
+ rateLimitPerMinute: 100, // Optional: rate limit on send endpoint
139
+ enableLogging: false, // Optional: debug logging
134
140
  },
135
- });
141
+ })
136
142
  ```
137
143
 
138
- ### Async Configuration
144
+ ### forRootAsync (Factory)
139
145
 
140
146
  ```typescript
147
+ import { ConfigService } from '@nestjs/config';
148
+
141
149
  EmailModule.forRootAsync({
142
150
  global: true,
143
151
  includeController: true,
@@ -146,882 +154,346 @@ EmailModule.forRootAsync({
146
154
  enableCompanyFeature: true,
147
155
  },
148
156
  imports: [ConfigModule],
149
- useFactory: async (configService: ConfigService) => ({
150
- defaultDatabaseConfig: configService.getDatabaseConfig(),
157
+ useFactory: (configService: ConfigService) => ({
158
+ defaultDatabaseConfig: {
159
+ type: 'postgres',
160
+ host: configService.get('DB_HOST'),
161
+ port: configService.get<number>('DB_PORT'),
162
+ username: configService.get('DB_USER'),
163
+ password: configService.get('DB_PASSWORD'),
164
+ database: configService.get('DB_NAME'),
165
+ },
151
166
  }),
152
167
  inject: [ConfigService],
153
- });
168
+ })
154
169
  ```
155
170
 
156
- ---
157
-
158
- ## Entities
159
-
160
- ### Entity Groups
171
+ ### forRootAsync (Class)
161
172
 
162
173
  ```typescript
163
- // Core entities (no company feature)
164
- export const EmailCoreEntities = [EmailConfig, EmailTemplate];
165
-
166
- // Company-specific entities
167
- export const EmailCompanyEntities = [EmailConfigWithCompany, EmailTemplateWithCompany];
174
+ import { EmailOptionsFactory, IEmailModuleConfig } from '@flusys/nestjs-email';
168
175
 
169
- // Helper function
170
- export function getEmailEntitiesByConfig(enableCompanyFeature: boolean): any[] {
171
- return enableCompanyFeature ? EmailCompanyEntities : EmailCoreEntities;
176
+ @Injectable()
177
+ export class MyEmailConfigFactory implements EmailOptionsFactory {
178
+ createEmailOptions(): IEmailModuleConfig {
179
+ return { defaultDatabaseConfig: { /* ... */ } };
180
+ }
181
+ createOptions() { return this.createEmailOptions(); }
172
182
  }
173
183
 
174
- // Base type aliases for backwards compatibility
175
- export { EmailConfig as EmailConfigBase } from './email-config.entity';
176
- export { EmailTemplate as EmailTemplateBase } from './email-template.entity';
184
+ EmailModule.forRootAsync({
185
+ bootstrapAppConfig: { databaseMode: 'single', enableCompanyFeature: false },
186
+ useClass: MyEmailConfigFactory,
187
+ })
177
188
  ```
178
189
 
179
190
  ---
180
191
 
181
- ## Interfaces
182
-
183
- ### Provider Configuration Interfaces
184
-
185
- ```typescript
186
- interface ISmtpTlsConfig {
187
- rejectUnauthorized?: boolean; // Reject unauthorized certs (default: true)
188
- minVersion?: 'TLSv1.2' | 'TLSv1.3'; // Min TLS version (default: 'TLSv1.2')
189
- }
190
-
191
- interface ISmtpConfig {
192
- host: string;
193
- port: number;
194
- secure?: boolean;
195
- auth?: { user: string; pass: string };
196
- tls?: ISmtpTlsConfig; // TLS configuration
197
- }
198
-
199
- interface ISendGridConfig {
200
- apiKey: string;
201
- }
202
-
203
- interface IMailgunConfig {
204
- apiKey: string;
205
- domain: string;
206
- region?: 'us' | 'eu';
207
- }
208
- ```
209
-
210
- ### Email Template Schema Interfaces
192
+ ## Configuration Reference
211
193
 
212
194
  ```typescript
213
- interface IEmailTemplateVariable {
214
- name: string;
215
- type: 'string' | 'number' | 'boolean' | 'date';
216
- required?: boolean;
217
- defaultValue?: string;
218
- description?: string;
219
- }
220
-
221
- interface IEmailSection {
222
- id: string;
223
- type: 'header' | 'body' | 'footer';
224
- name: string;
225
- blocks: IEmailBlock[];
226
- order: number;
227
- style?: IEmailSectionStyle;
228
- }
229
-
230
- interface IEmailSectionStyle {
231
- backgroundColor?: string;
232
- padding?: string;
233
- maxWidth?: string;
234
- }
235
-
236
- interface IEmailBlock {
237
- id: string;
238
- type: 'text' | 'image' | 'button' | 'divider' | 'html';
239
- order: number;
240
- content: Record<string, any>;
241
- style?: IEmailBlockStyle;
242
- }
243
-
244
- interface IEmailBlockStyle {
245
- textAlign?: 'left' | 'center' | 'right';
246
- padding?: string;
247
- margin?: string;
248
- backgroundColor?: string;
249
- }
195
+ interface IEmailModuleConfig extends IDataSourceServiceOptions {
196
+ /** Optional: default provider type when no config is specified */
197
+ defaultProvider?: 'smtp' | 'sendgrid' | 'mailgun';
250
198
 
251
- interface IEmailSettings {
252
- maxWidth?: string;
253
- backgroundColor?: string;
254
- fontFamily?: string;
255
- baseTextColor?: string;
256
- }
199
+ /** Optional: max emails per minute (default: unlimited) */
200
+ rateLimitPerMinute?: number;
257
201
 
258
- interface IEmailSchema {
259
- id: string;
260
- version: string;
261
- name: string;
262
- subject: string;
263
- preheader?: string;
264
- sections: IEmailSection[];
265
- variables?: IEmailTemplateVariable[];
266
- settings?: IEmailSettings;
202
+ /** Optional: enable debug logging for send operations */
203
+ enableLogging?: boolean;
267
204
  }
268
205
  ```
269
206
 
270
207
  ---
271
208
 
272
- ## Email Providers
273
-
274
- ### Provider Types
209
+ ## Feature Toggles
275
210
 
276
- ```typescript
277
- enum EmailProviderTypeEnum {
278
- SMTP = 'smtp',
279
- SENDGRID = 'sendgrid',
280
- MAILGUN = 'mailgun',
281
- }
282
- ```
211
+ | Feature | Config Key | Default | Effect |
212
+ |---------|-----------|---------|--------|
213
+ | Company scoping | `enableCompanyFeature: true` | `false` | Uses `EmailConfigWithCompany` and `EmailTemplateWithCompany` entities; filters all queries by `companyId` |
214
+ | Multi-tenant | `databaseMode: 'multi-tenant'` | `'single'` | Creates per-tenant DataSource connections |
283
215
 
284
- Providers are auto-registered at module load via `EmailProviderRegistry`.
216
+ ---
285
217
 
286
- ### SMTP Configuration
218
+ ## API Endpoints
287
219
 
288
- ```typescript
289
- {
290
- provider: 'smtp',
291
- config: {
292
- host: 'smtp.gmail.com',
293
- port: 587,
294
- secure: false, // true for port 465
295
- auth: { user: 'your@gmail.com', pass: 'app-password' },
296
- tls: {
297
- rejectUnauthorized: true, // Certificate validation (default: true)
298
- minVersion: 'TLSv1.2', // Minimum TLS version
299
- },
300
- }
301
- }
302
- ```
220
+ All endpoints use **POST**. All require JWT authentication unless noted.
303
221
 
304
- ### SendGrid Configuration
222
+ ### Email Config — `POST /email/email-config/*`
305
223
 
306
- ```typescript
307
- // Requires: npm install @sendgrid/mail
308
- {
309
- provider: 'sendgrid',
310
- config: { apiKey: 'SG.xxxxxxxxxxxx' }
311
- }
312
- ```
224
+ | Endpoint | Permission | Description |
225
+ |----------|-----------|-------------|
226
+ | `POST /email/email-config/insert` | `email-config.create` | Create a provider configuration |
227
+ | `POST /email/email-config/get-all` | `email-config.read` | List all configs |
228
+ | `POST /email/email-config/get/:id` | `email-config.read` | Get config by ID |
229
+ | `POST /email/email-config/update` | `email-config.update` | Update config |
230
+ | `POST /email/email-config/delete` | `email-config.delete` | Delete config |
231
+ | `POST /email/email-config/test` | `email-config.create` | Send a test email to verify config |
232
+ | `POST /email/email-config/set-default` | `email-config.update` | Set as default provider |
313
233
 
314
- ### Mailgun Configuration
234
+ ### Email Templates — `POST /email/email-template/*`
315
235
 
316
- ```typescript
317
- // Requires: npm install mailgun.js form-data
318
- {
319
- provider: 'mailgun',
320
- config: {
321
- apiKey: 'key-xxxxxxxx',
322
- domain: 'mail.yourdomain.com',
323
- region: 'us', // 'us' or 'eu'
324
- }
325
- }
326
- ```
236
+ | Endpoint | Permission | Description |
237
+ |----------|-----------|-------------|
238
+ | `POST /email/email-template/insert` | `email-template.create` | Create a template |
239
+ | `POST /email/email-template/get-all` | `email-template.read` | List all templates |
240
+ | `POST /email/email-template/get/:id` | `email-template.read` | Get template by ID |
241
+ | `POST /email/email-template/update` | `email-template.update` | Update template |
242
+ | `POST /email/email-template/delete` | `email-template.delete` | Delete template |
327
243
 
328
- ### Provider Interface
244
+ ### Email Send — `POST /email/send/*`
329
245
 
330
- ```typescript
331
- interface IEmailProvider {
332
- sendEmail(options: IEmailSendOptions): Promise<IEmailSendResult>;
333
- sendBulkEmails(options: IEmailSendOptions[]): Promise<IEmailSendResult[]>;
334
- healthCheck(): Promise<boolean>;
335
- initialize?(config: any): Promise<void>;
336
- close?(): Promise<void>;
337
- }
246
+ | Endpoint | Permission | Description |
247
+ |----------|-----------|-------------|
248
+ | `POST /email/send/template` | `email-config.create` | Send using a stored template |
249
+ | `POST /email/send/raw` | `email-config.create` | Send raw HTML email |
338
250
 
339
- interface IEmailSendOptions {
340
- to: string | string[];
341
- cc?: string | string[];
342
- bcc?: string | string[];
343
- subject: string;
344
- html?: string;
345
- text?: string;
346
- from?: string;
347
- fromName?: string;
348
- replyTo?: string;
349
- attachments?: IEmailAttachment[];
350
- }
251
+ ---
351
252
 
352
- interface IEmailAttachment {
353
- filename: string;
354
- content: Buffer | string;
355
- contentType?: string;
356
- encoding?: string;
357
- }
253
+ ## Entities
358
254
 
359
- interface IEmailSendResult {
360
- success: boolean;
361
- messageId?: string;
362
- error?: string;
363
- }
364
- ```
255
+ ### Core Entities (always registered)
365
256
 
366
- ### EmailProviderRegistry
257
+ | Entity | Table | Description |
258
+ |--------|-------|-------------|
259
+ | `EmailConfig` | `email_config` | Provider configuration (SMTP credentials, SendGrid API key, etc.) |
260
+ | `EmailTemplate` | `email_template` | Email templates with `{{variable}}` placeholders |
367
261
 
368
- Providers are auto-registered at module load:
262
+ ### Company Feature Entities (`enableCompanyFeature: true`)
369
263
 
370
- ```typescript
371
- // Built-in registration (happens automatically)
372
- EmailProviderRegistry.register(EmailProviderTypeEnum.SMTP, SmtpProvider);
373
- EmailProviderRegistry.register(EmailProviderTypeEnum.SENDGRID, SendGridProvider);
374
- EmailProviderRegistry.register(EmailProviderTypeEnum.MAILGUN, MailgunProvider);
375
-
376
- // Registry methods
377
- EmailProviderRegistry.register(name, ProviderClass); // Register provider
378
- EmailProviderRegistry.get(name); // Get provider class
379
- EmailProviderRegistry.has(name); // Check if registered
380
- EmailProviderRegistry.getAll(); // List all providers
381
- EmailProviderRegistry.clear(); // Clear registry
382
- ```
264
+ | Entity | Table | Description |
265
+ |--------|-------|-------------|
266
+ | `EmailConfigWithCompany` | `email_config` | Same as EmailConfig + `companyId` and `branchId` columns |
267
+ | `EmailTemplateWithCompany` | `email_template` | Same as EmailTemplate + `companyId` column |
383
268
 
384
- ### Custom Provider Example
269
+ #### Register Entities in TypeORM
385
270
 
386
271
  ```typescript
387
- class MyCustomProvider implements IEmailProvider {
388
- async initialize(config: any): Promise<void> {
389
- // Initialize connection
390
- }
391
-
392
- async sendEmail(options: IEmailSendOptions): Promise<IEmailSendResult> {
393
- // Send email
394
- return { success: true, messageId: 'xxx' };
395
- }
396
-
397
- async sendBulkEmails(options: IEmailSendOptions[]): Promise<IEmailSendResult[]> {
398
- return Promise.all(options.map(opt => this.sendEmail(opt)));
399
- }
400
-
401
- async healthCheck(): Promise<boolean> {
402
- return true;
403
- }
404
-
405
- async close(): Promise<void> {
406
- // Cleanup connections
407
- }
408
- }
272
+ import { EmailModule } from '@flusys/nestjs-email';
409
273
 
410
- // Register custom provider
411
- EmailProviderRegistry.register('custom', MyCustomProvider);
274
+ TypeOrmModule.forRoot({
275
+ entities: [
276
+ ...EmailModule.getEntities({ enableCompanyFeature: true }),
277
+ // other entities
278
+ ],
279
+ })
412
280
  ```
413
281
 
414
282
  ---
415
283
 
416
- ## Email Configuration
417
-
418
- ### EmailConfig Entity
419
-
420
- ```typescript
421
- interface IEmailConfig {
422
- id: string;
423
- name: string; // e.g., 'default', 'marketing'
424
- provider: EmailProviderTypeEnum;
425
- config: Record<string, any>; // Provider-specific config
426
- fromEmail: string | null;
427
- fromName: string | null;
428
- isActive: boolean;
429
- isDefault: boolean; // Auto-selected when emailConfigId not provided
430
- companyId?: string | null; // When company feature enabled
431
- }
432
- ```
433
-
434
- ### Default Configuration Resolution
435
-
436
- When `emailConfigId` is not provided:
437
- 1. Find config with `isDefault: true` and `isActive: true`
438
- 2. Fall back to oldest active config
284
+ ## Email Providers
439
285
 
440
- ### Creating Configurations
286
+ ### SMTP (Default)
441
287
 
442
- ```typescript
443
- import { EmailProviderConfigService } from '@flusys/nestjs-email';
288
+ Create an `EmailConfig` record with provider `smtp`:
444
289
 
445
- await emailConfigService.insert({
446
- name: 'default',
447
- provider: 'smtp',
448
- fromEmail: 'noreply@example.com',
449
- fromName: 'My App',
450
- config: {
451
- host: 'smtp.gmail.com',
452
- port: 587,
453
- auth: { user: '...', pass: '...' },
290
+ ```json
291
+ POST /email/email-config/insert
292
+ {
293
+ "name": "Company SMTP",
294
+ "provider": "smtp",
295
+ "config": {
296
+ "host": "smtp.gmail.com",
297
+ "port": 587,
298
+ "secure": false,
299
+ "user": "noreply@example.com",
300
+ "password": "app-password"
454
301
  },
455
- isActive: true,
456
- isDefault: true,
457
- }, user);
458
- ```
459
-
460
- ---
461
-
462
- ## Email Templates
463
-
464
- ### EmailTemplate Entity
465
-
466
- ```typescript
467
- interface IEmailTemplate {
468
- id: string;
469
- name: string;
470
- slug: string; // URL-friendly identifier
471
- description: string | null;
472
- subject: string; // Supports {{variables}}
473
- schema: Record<string, unknown>;
474
- htmlContent: string;
475
- textContent: string | null;
476
- schemaVersion: number; // Auto-incremented on schema change
477
- isActive: boolean;
478
- isHtml: boolean; // true=HTML, false=plain text
479
- metadata: Record<string, unknown> | null;
480
- companyId?: string | null;
302
+ "fromEmail": "noreply@example.com",
303
+ "fromName": "My App",
304
+ "isDefault": true
481
305
  }
482
306
  ```
483
307
 
484
- ### Creating Templates
308
+ ### SendGrid
485
309
 
486
- ```typescript
487
- import { EmailTemplateService } from '@flusys/nestjs-email';
488
-
489
- await templateService.insert({
490
- name: 'Welcome Email',
491
- slug: 'welcome-email',
492
- subject: 'Welcome to {{appName}}, {{userName}}!',
493
- isHtml: true,
494
- htmlContent: `
495
- <h1>Welcome, {{userName}}!</h1>
496
- <p>Thank you for joining {{appName}}.</p>
497
- <a href="{{verificationLink}}">Verify Email</a>
498
- `,
499
- textContent: 'Welcome! Verify: {{verificationLink}}',
500
- schema: {},
501
- isActive: true,
502
- }, user);
503
- ```
310
+ Install `@sendgrid/mail` first, then create a config:
504
311
 
505
- ### Variable Interpolation
506
-
507
- ```typescript
508
- // Template: "Hello {{userName}}, your order #{{orderId}} is {{status}}."
509
- // Variables: { userName: "John", orderId: "12345", status: "shipped" }
510
- // Result: "Hello John, your order #12345 is shipped."
312
+ ```json
313
+ POST /email/email-config/insert
314
+ {
315
+ "name": "SendGrid Production",
316
+ "provider": "sendgrid",
317
+ "config": { "apiKey": "SG.xxxxxxxxxxxx" },
318
+ "fromEmail": "noreply@example.com",
319
+ "fromName": "My App",
320
+ "isDefault": true
321
+ }
511
322
  ```
512
323
 
513
- ### isHtml Toggle
514
-
515
- - **`isHtml: true`** - Sends `htmlContent` as HTML, `textContent` as fallback
516
- - **`isHtml: false`** - Sends `textContent` only (no HTML)
517
-
518
- ---
519
-
520
- ## Email Sending
324
+ ### Mailgun
521
325
 
522
- ### EmailSendService
523
-
524
- ```typescript
525
- import { EmailSendService } from '@flusys/nestjs-email';
326
+ Install `mailgun.js form-data` first, then create a config:
526
327
 
527
- // Send direct email
528
- const result = await emailSendService.sendEmail({
529
- to: 'user@example.com',
530
- subject: 'Hello!',
531
- html: '<h1>Hello World</h1>',
532
- text: 'Hello World',
533
- emailConfigId: 'config-uuid', // Optional - uses default
534
- }, user);
535
-
536
- // Send using template
537
- const result = await emailSendService.sendTemplateEmail({
538
- templateSlug: 'welcome-email',
539
- to: 'user@example.com',
540
- variables: {
541
- userName: 'John',
542
- appName: 'My App',
328
+ ```json
329
+ POST /email/email-config/insert
330
+ {
331
+ "name": "Mailgun",
332
+ "provider": "mailgun",
333
+ "config": {
334
+ "apiKey": "key-xxxxxxxxxxxx",
335
+ "domain": "mg.example.com",
336
+ "region": "us"
543
337
  },
544
- }, user);
545
-
546
- // Send with attachments
547
- const result = await emailSendService.sendEmail({
548
- to: 'user@example.com',
549
- subject: 'Your Invoice',
550
- html: '<p>Invoice attached.</p>',
551
- attachments: [{
552
- filename: 'invoice.pdf',
553
- content: base64Content,
554
- contentType: 'application/pdf',
555
- }],
556
- }, user);
338
+ "fromEmail": "noreply@example.com",
339
+ "fromName": "My App",
340
+ "isDefault": true
341
+ }
557
342
  ```
558
343
 
559
- ---
560
-
561
- ## DTOs
344
+ ### Custom Provider
562
345
 
563
- ### Email Send DTOs
346
+ Implement `IEmailProvider` and register it with `StorageProviderRegistry`:
564
347
 
565
348
  ```typescript
566
- // Base class for common email fields
567
- class BaseEmailDto {
568
- to: string | string[]; // Recipient(s)
569
- cc?: string | string[]; // CC recipients
570
- bcc?: string | string[]; // BCC recipients
571
- from?: string; // Sender email
572
- fromName?: string; // Sender name
573
- replyTo?: string; // Reply-to address
574
- emailConfigId?: string; // Email config to use
575
- attachments?: EmailAttachmentDto[];
576
- }
577
-
578
- class SendEmailDto extends BaseEmailDto {
579
- subject: string;
580
- html: string;
581
- text?: string;
582
- }
349
+ import { IEmailProvider, EmailProviderRegistry } from '@flusys/nestjs-email';
583
350
 
584
- class SendTemplateEmailDto extends BaseEmailDto {
585
- templateId?: string; // Template ID (or slug required)
586
- templateSlug?: string; // Template slug (or ID required)
587
- variables?: Record<string, any>; // Template variables
588
- }
589
-
590
- class TestEmailDto {
591
- emailConfigId: string;
592
- recipient: string;
593
- }
594
-
595
- class EmailAttachmentDto {
596
- filename: string;
597
- content: string; // Base64 encoded
598
- contentType?: string;
599
- }
600
-
601
- class EmailSendResultDto {
602
- success: boolean;
603
- messageId?: string;
604
- error?: string;
351
+ class MyCustomProvider implements IEmailProvider {
352
+ async send(options: IEmailSendOptions): Promise<void> {
353
+ // custom sending logic
354
+ }
355
+ async testConnection(): Promise<boolean> {
356
+ return true;
357
+ }
605
358
  }
606
- ```
607
-
608
- ### Custom Validators
609
359
 
610
- The DTOs use custom validators for email fields:
611
-
612
- ```typescript
613
- // Validates single email or array of emails
614
- @IsEmailOrEmailArray()
615
- to: string | string[];
616
-
617
- // Requires either templateId or templateSlug
618
- @RequireTemplateIdOrSlug()
619
- templateId?: string;
360
+ // Register before module initialization
361
+ EmailProviderRegistry.register('custom', MyCustomProvider);
620
362
  ```
621
363
 
622
364
  ---
623
365
 
624
- ## Utility Functions
366
+ ## Template Engine
625
367
 
626
- Built-in email template utilities for common use cases:
368
+ Templates use `{{variableName}}` syntax. All values are HTML-escaped automatically.
627
369
 
628
- ```typescript
629
- import { getOtpEmailFormat, getResetPasswordEmailFormat } from '@flusys/nestjs-email/utils';
630
-
631
- // Generate OTP email HTML
632
- const otpHtml = getOtpEmailFormat(123456, 'John Doe');
633
-
634
- // Generate password reset email HTML
635
- const resetHtml = getResetPasswordEmailFormat('https://example.com/reset?token=xyz', 'John Doe');
636
- ```
637
-
638
- ### getOtpEmailFormat(otp, userName?)
639
-
640
- Generates styled HTML for OTP verification emails.
641
-
642
- ```typescript
643
- const html = getOtpEmailFormat(123456, 'John');
644
- // Returns formatted HTML with OTP code prominently displayed
370
+ **Create a template:**
371
+ ```json
372
+ POST /email/email-template/insert
373
+ {
374
+ "name": "Welcome Email",
375
+ "slug": "welcome",
376
+ "subject": "Welcome to {{appName}}, {{userName}}!",
377
+ "html": "<h1>Hello {{userName}}</h1><p>Welcome to <strong>{{appName}}</strong>.</p><p><a href=\"{{loginUrl}}\">Login here</a></p>",
378
+ "variables": ["appName", "userName", "loginUrl"]
379
+ }
645
380
  ```
646
381
 
647
- ### getResetPasswordEmailFormat(resetLink, userName?)
648
-
649
- Generates styled HTML for password reset emails.
650
-
651
- ```typescript
652
- const html = getResetPasswordEmailFormat('https://app.example.com/reset?token=abc123', 'John');
653
- // Returns formatted HTML with reset button
382
+ **Send using the template:**
383
+ ```json
384
+ POST /email/send/template
385
+ {
386
+ "templateSlug": "welcome",
387
+ "to": "user@example.com",
388
+ "variables": {
389
+ "appName": "My App",
390
+ "userName": "John Doe",
391
+ "loginUrl": "https://app.example.com/login"
392
+ }
393
+ }
654
394
  ```
655
395
 
656
396
  ---
657
397
 
658
- ## API Endpoints
659
-
660
- All endpoints use POST (RPC pattern) and require JWT authentication.
661
-
662
- ### Email Configuration
663
-
664
- | Endpoint | Description |
665
- |----------|-------------|
666
- | `POST /email/email-config/insert` | Create configuration |
667
- | `POST /email/email-config/get/:id` | Get by ID |
668
- | `POST /email/email-config/get-all` | Get all (paginated) |
669
- | `POST /email/email-config/update` | Update |
670
- | `POST /email/email-config/delete` | Delete |
398
+ ## Exported Services
671
399
 
672
- ### Email Templates
400
+ These services are exported by `EmailModule` and injectable in your application:
673
401
 
674
- | Endpoint | Description |
675
- |----------|-------------|
676
- | `POST /email/email-template/insert` | Create template |
677
- | `POST /email/email-template/get/:id` | Get by ID |
678
- | `POST /email/email-template/get-all` | Get all (paginated) |
679
- | `POST /email/email-template/get-by-slug` | Get by slug |
680
- | `POST /email/email-template/update` | Update |
681
- | `POST /email/email-template/delete` | Delete |
402
+ | Service | Description |
403
+ |---------|-------------|
404
+ | `EmailSendService` | Send emails via template slug or raw HTML |
405
+ | `EmailTemplateService` | CRUD for email templates |
406
+ | `EmailProviderConfigService` | CRUD for provider configurations |
407
+ | `EmailConfigService` | Exposes runtime config (provider defaults, rate limits) |
408
+ | `EmailDataSourceProvider` | Dynamic TypeORM DataSource resolution per request |
682
409
 
683
- ### Email Sending
684
-
685
- | Endpoint | Description |
686
- |----------|-------------|
687
- | `POST /email/send/direct` | Send direct email |
688
- | `POST /email/send/template` | Send using template |
689
- | `POST /email/send/test` | Test configuration |
690
-
691
- ### Example Requests
692
-
693
- ```bash
694
- # Send template email
695
- curl -X POST http://localhost:3000/email/send/template \
696
- -H "Authorization: Bearer <token>" \
697
- -H "Content-Type: application/json" \
698
- -d '{
699
- "templateSlug": "welcome-email",
700
- "to": "user@example.com",
701
- "variables": { "userName": "John" }
702
- }'
703
-
704
- # Test configuration
705
- curl -X POST http://localhost:3000/email/send/test \
706
- -H "Authorization: Bearer <token>" \
707
- -d '{ "emailConfigId": "uuid", "recipient": "test@example.com" }'
708
- ```
709
-
710
- ### Controller Permissions
711
-
712
- Controllers use permission-based security via `createApiController`:
713
-
714
- ```typescript
715
- // Email Config permissions
716
- EMAIL_CONFIG_PERMISSIONS.CREATE // 'email-config.create'
717
- EMAIL_CONFIG_PERMISSIONS.READ // 'email-config.read'
718
- EMAIL_CONFIG_PERMISSIONS.UPDATE // 'email-config.update'
719
- EMAIL_CONFIG_PERMISSIONS.DELETE // 'email-config.delete'
720
-
721
- // Email Template permissions
722
- EMAIL_TEMPLATE_PERMISSIONS.CREATE // 'email-template.create'
723
- EMAIL_TEMPLATE_PERMISSIONS.READ // 'email-template.read'
724
- EMAIL_TEMPLATE_PERMISSIONS.UPDATE // 'email-template.update'
725
- EMAIL_TEMPLATE_PERMISSIONS.DELETE // 'email-template.delete'
726
-
727
- // Email Send permission
728
- 'email.send'
729
- ```
410
+ > **Note:** Always use `@Inject(ServiceClass)` explicitly — esbuild bundling loses TypeScript metadata.
730
411
 
731
412
  ---
732
413
 
733
- ## Swagger Documentation
414
+ ## Sending Emails Programmatically
734
415
 
735
- ### Setup Swagger for Email Module
416
+ Inject `EmailSendService` to send emails from other services:
736
417
 
737
418
  ```typescript
738
- import { emailSwaggerConfig } from '@flusys/nestjs-email';
739
- import { setupSwaggerDocs } from '@flusys/nestjs-core/docs';
740
-
741
- // In your bootstrap function
742
- setupSwaggerDocs(app, emailSwaggerConfig(bootstrapAppConfig));
743
- ```
419
+ import { EmailSendService } from '@flusys/nestjs-email';
744
420
 
745
- ### Swagger Config Options
421
+ @Injectable()
422
+ export class UserService {
423
+ constructor(
424
+ @Inject(EmailSendService) private readonly emailSendService: EmailSendService,
425
+ ) {}
426
+
427
+ async sendWelcomeEmail(user: { email: string; name: string }): Promise<void> {
428
+ await this.emailSendService.sendTemplateEmail({
429
+ templateSlug: 'welcome',
430
+ to: user.email,
431
+ variables: { userName: user.name, appName: 'My App' },
432
+ });
433
+ }
746
434
 
747
- ```typescript
748
- function emailSwaggerConfig(bootstrapConfig?: IBootstrapAppConfig): IModuleSwaggerOptions {
749
- return {
750
- title: 'Email API',
751
- description: '...', // Auto-generated based on features
752
- version: '1.0',
753
- path: 'api/docs/email', // Swagger UI path
754
- bearerAuth: true,
755
- excludeTags: [],
756
- excludeSchemaProperties: [...], // Excludes companyId when company feature disabled
757
- };
435
+ async sendRawEmail(): Promise<void> {
436
+ await this.emailSendService.sendRawEmail({
437
+ to: 'recipient@example.com',
438
+ subject: 'Hello',
439
+ html: '<p>Hello world</p>',
440
+ attachments: [
441
+ {
442
+ filename: 'report.pdf',
443
+ content: base64EncodedPdfString,
444
+ contentType: 'application/pdf',
445
+ },
446
+ ],
447
+ });
448
+ }
758
449
  }
759
450
  ```
760
451
 
761
- The Swagger documentation automatically adapts based on `enableCompanyFeature`:
762
- - When enabled: Shows company isolation and multi-tenant features
763
- - When disabled: Hides companyId fields from schemas
764
-
765
452
  ---
766
453
 
767
- ## Multi-Tenant Support
768
-
769
- ### Company Isolation
770
-
771
- When `enableCompanyFeature: true`:
772
- - Email configs are company-scoped
773
- - Templates are company-scoped
774
- - Users can only access their company's resources
454
+ ## Troubleshooting
775
455
 
776
- ### Company Filtering
456
+ **`No default email config found`**
777
457
 
778
- Services automatically filter by `companyId`:
779
-
780
- ```typescript
781
- // In EmailTemplateService
782
- if (this.emailConfig.isCompanyFeatureEnabled() && user?.companyId) {
783
- query.andWhere('emailTemplate.companyId = :companyId', {
784
- companyId: user.companyId,
785
- });
786
- }
787
- ```
788
-
789
- ### Dynamic Entity Selection
790
-
791
- ```typescript
792
- protected resolveEntity(): EntityTarget<EmailTemplateBase> {
793
- return this.emailConfig.isCompanyFeatureEnabled()
794
- ? EmailTemplateWithCompany
795
- : EmailTemplate;
796
- }
458
+ Create at least one `EmailConfig` record and mark it as default:
459
+ ```json
460
+ POST /email/email-config/set-default
461
+ { "id": "your-config-id" }
797
462
  ```
798
463
 
799
464
  ---
800
465
 
801
- ## Best Practices
802
-
803
- ### 1. Use Template Slugs
804
-
805
- ```typescript
806
- // Use slug for stable references
807
- await emailSendService.sendTemplateEmail({
808
- templateSlug: 'welcome-email', // Stable
809
- ...
810
- });
811
-
812
- // Avoid hardcoded IDs
813
- templateId: 'f47ac10b-58cc-4372-a567-...' // May change
814
- ```
815
-
816
- ### 2. Provide Both HTML and Text
817
-
818
- ```typescript
819
- await templateService.insert({
820
- isHtml: true,
821
- htmlContent: '<h1>Thank you!</h1>',
822
- textContent: 'Thank you!', // For clients that don't render HTML
823
- });
824
- ```
466
+ **`Template not found`**
825
467
 
826
- ### 3. Test Before Production
827
-
828
- ```typescript
829
- const result = await emailSendService.sendTestEmail(
830
- configId,
831
- 'admin@example.com',
832
- user,
833
- );
834
- if (!result.success) throw new Error(result.error);
835
- ```
836
-
837
- ### 4. Use Variables for Personalization
838
-
839
- ```typescript
840
- // Template
841
- subject: 'Hello {{userName}}!'
842
-
843
- // Sending
844
- variables: { userName: 'John' }
845
- ```
468
+ Check the `templateSlug` matches exactly (case-sensitive). Use `POST /email/email-template/get-all` to list available templates.
846
469
 
847
470
  ---
848
471
 
849
- ## API Reference
472
+ **SMTP connection refused**
850
473
 
851
- ### Main Exports
852
-
853
- ```typescript
854
- // Module
855
- import { EmailModule } from '@flusys/nestjs-email';
856
-
857
- // Services
858
- import {
859
- EmailConfigService, // Module configuration service
860
- EmailProviderConfigService, // CRUD for email provider configs
861
- EmailTemplateService,
862
- EmailSendService,
863
- EmailDataSourceProvider,
864
- } from '@flusys/nestjs-email/services';
865
-
866
- // Entities
867
- import {
868
- EmailConfig,
869
- EmailConfigBase,
870
- EmailConfigWithCompany,
871
- EmailTemplate,
872
- EmailTemplateBase,
873
- EmailTemplateWithCompany,
874
- EmailCoreEntities,
875
- EmailCompanyEntities,
876
- getEmailEntitiesByConfig,
877
- } from '@flusys/nestjs-email/entities';
878
-
879
- // DTOs
880
- import {
881
- CreateEmailConfigDto,
882
- UpdateEmailConfigDto,
883
- EmailConfigResponseDto,
884
- CreateEmailTemplateDto,
885
- UpdateEmailTemplateDto,
886
- EmailTemplateResponseDto,
887
- EmailAttachmentDto,
888
- SendEmailDto,
889
- SendTemplateEmailDto,
890
- TestEmailDto,
891
- EmailSendResultDto,
892
- } from '@flusys/nestjs-email/dtos';
893
-
894
- // Interfaces
895
- import {
896
- IEmailProvider,
897
- IEmailSendOptions,
898
- IEmailSendResult,
899
- IEmailAttachment,
900
- IEmailProviderConfig,
901
- IEmailConfig,
902
- IEmailTemplate,
903
- IEmailTemplateVariable,
904
- IEmailSection,
905
- IEmailSectionStyle,
906
- IEmailBlock,
907
- IEmailBlockStyle,
908
- IEmailSettings,
909
- IEmailSchema,
910
- ISmtpConfig,
911
- ISmtpTlsConfig,
912
- ISendGridConfig,
913
- IMailgunConfig,
914
- IEmailModuleConfig,
915
- EmailModuleOptions,
916
- EmailModuleAsyncOptions,
917
- EmailOptionsFactory,
918
- } from '@flusys/nestjs-email/interfaces';
919
-
920
- // Providers
921
- import {
922
- EmailProviderRegistry,
923
- EmailFactoryService,
924
- SmtpProvider,
925
- SendGridProvider,
926
- MailgunProvider,
927
- } from '@flusys/nestjs-email/providers';
928
-
929
- // Controllers
930
- import {
931
- EmailConfigController,
932
- EmailTemplateController,
933
- EmailSendController,
934
- } from '@flusys/nestjs-email/controllers';
935
-
936
- // Enums
937
- import { EmailProviderTypeEnum } from '@flusys/nestjs-email/enums';
938
-
939
- // Constants
940
- import {
941
- EMAIL_MODULE_OPTIONS,
942
- DEFAULT_FROM_NAME,
943
- } from '@flusys/nestjs-email/config';
944
-
945
- // Utilities
946
- import {
947
- getOtpEmailFormat,
948
- getResetPasswordEmailFormat,
949
- } from '@flusys/nestjs-email/utils';
950
-
951
- // Swagger Config
952
- import { emailSwaggerConfig } from '@flusys/nestjs-email/docs';
474
+ Use the test endpoint first:
475
+ ```json
476
+ POST /email/email-config/test
477
+ { "id": "your-config-id", "to": "test@example.com" }
953
478
  ```
954
479
 
955
- ### Service Methods Reference
480
+ For Gmail, enable "App Passwords" and use the app password, not your account password.
956
481
 
957
- #### EmailConfigService
958
-
959
- ```typescript
960
- class EmailConfigService {
961
- isCompanyFeatureEnabled(): boolean;
962
- getDatabaseMode(): DatabaseMode;
963
- isMultiTenant(): boolean;
964
- getDefaultFromName(): string;
965
- getOptions(): EmailModuleOptions;
966
- }
967
- ```
968
-
969
- #### EmailProviderConfigService
970
-
971
- ```typescript
972
- class EmailProviderConfigService {
973
- // Standard CRUD (inherited from RequestScopedApiService)
974
- insert(dto, user): Promise<EmailConfigBase>;
975
- getById(id, user): Promise<EmailConfigBase | null>;
976
- getAll(filterDto, user): Promise<{ list: EmailConfigBase[]; total: number }>;
977
- update(dto, user): Promise<EmailConfigBase>;
978
- delete(id, user): Promise<boolean>;
979
-
980
- // Custom methods
981
- findByIdDirect(id): Promise<EmailConfigBase | null>;
982
- getDefaultConfig(user?): Promise<EmailConfigBase | null>;
983
- getConfigByProvider(provider, user?): Promise<EmailConfigBase[]>;
984
- }
985
- ```
482
+ ---
986
483
 
987
- #### EmailTemplateService
484
+ **`No metadata for entity`**
988
485
 
486
+ Call `EmailModule.getEntities()` with the correct flags when registering `TypeOrmModule`:
989
487
  ```typescript
990
- class EmailTemplateService {
991
- // Standard CRUD (inherited from RequestScopedApiService)
992
- insert(dto, user): Promise<EmailTemplateBase>;
993
- getById(id, user): Promise<EmailTemplateBase | null>;
994
- getAll(filterDto, user): Promise<{ list: EmailTemplateBase[]; total: number }>;
995
- update(dto, user): Promise<EmailTemplateBase>;
996
- delete(id, user): Promise<boolean>;
997
-
998
- // Custom methods
999
- findByIdDirect(id): Promise<EmailTemplateBase | null>;
1000
- findBySlug(slug, user?): Promise<EmailTemplateBase | null>;
1001
- getActiveTemplates(user?): Promise<EmailTemplateBase[]>;
1002
- }
488
+ entities: [...EmailModule.getEntities({ enableCompanyFeature: true })]
1003
489
  ```
1004
490
 
1005
- #### EmailSendService
1006
-
1007
- ```typescript
1008
- class EmailSendService {
1009
- sendEmail(dto: SendEmailDto, user?): Promise<IEmailSendResult>;
1010
- sendTemplateEmail(dto: SendTemplateEmailDto, user?): Promise<IEmailSendResult>;
1011
- sendTestEmail(emailConfigId, recipient, user?): Promise<IEmailSendResult>;
1012
- }
1013
- ```
491
+ ---
1014
492
 
1015
- #### EmailFactoryService
493
+ ## License
1016
494
 
1017
- ```typescript
1018
- class EmailFactoryService {
1019
- createProvider(config: IEmailProviderConfig): Promise<IEmailProvider>;
1020
- // Providers are cached by config hash for reuse
1021
- // Connections cleaned up on module destroy
1022
- }
1023
- ```
495
+ MIT © FLUSYS
1024
496
 
1025
497
  ---
1026
498
 
1027
- **Last Updated:** 2026-02-25
499
+ > Part of the **FLUSYS** framework — a full-stack monorepo powering Angular 21 + NestJS 11 applications.