@flusys/nestjs-email
v4.1.1
Published
Modular email package with SMTP, SendGrid, and Mailgun providers
Maintainers
Readme
@flusys/nestjs-email
Production-grade email management for NestJS — multi-provider (SMTP, SendGrid, Mailgun), database-driven template engine with
{{variable}}interpolation, company scoping, and multi-tenant support.
Table of Contents
- Overview
- Features
- Compatibility
- Installation
- Quick Start
- Module Registration
- Configuration Reference
- Feature Toggles
- API Endpoints
- Entities
- Email Providers
- Template Engine
- Exported Services
- Sending Emails Programmatically
- Troubleshooting
- License
Overview
@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.
Features
- Multi-provider — SMTP (nodemailer), SendGrid, Mailgun with a pluggable custom provider interface
- Database-driven templates — Templates stored in PostgreSQL with
{{variable}}interpolation - HTML XSS protection — All variable values are HTML-escaped before interpolation
- Provider caching — SHA-256 config hash prevents duplicate provider instances
- Test email — Verify a provider configuration before using it in production
- Company scoping — Optional
companyIdon configs and templates for multi-company setups - Multi-tenant — Per-tenant DataSource isolation via the DataSource Provider pattern
- Attachments — Base64-encoded file attachments supported
Compatibility
| Package | Version |
|---------|---------|
| @flusys/nestjs-core | ^4.0.0 |
| @flusys/nestjs-shared | ^4.0.0 |
| nodemailer | ^6.0.0 |
| @sendgrid/mail | ^8.0.0 (optional) |
| mailgun.js | ^10.0.0 (optional) |
| Node.js | >= 18.x |
Installation
npm install @flusys/nestjs-email @flusys/nestjs-shared @flusys/nestjs-core
# Provider-specific SDKs (install only what you use)
npm install nodemailer # SMTP (recommended default)
npm install @sendgrid/mail # SendGrid
npm install mailgun.js form-data # MailgunQuick Start
Minimal Setup (SMTP, Single Database)
import { Module } from '@nestjs/common';
import { EmailModule } from '@flusys/nestjs-email';
@Module({
imports: [
EmailModule.forRoot({
global: true,
includeController: true,
bootstrapAppConfig: {
databaseMode: 'single',
enableCompanyFeature: false,
},
config: {
defaultDatabaseConfig: {
type: 'postgres',
host: process.env.DB_HOST,
port: Number(process.env.DB_PORT ?? 5432),
username: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
},
},
}),
],
})
export class AppModule {}After startup, create an email config via the API and then send emails using EmailSendService.
Module Registration
forRoot (Sync)
EmailModule.forRoot({
global: true,
includeController: true,
bootstrapAppConfig: {
databaseMode: 'single', // 'single' | 'multi-tenant'
enableCompanyFeature: false, // true = company-scoped templates & configs
},
config: {
defaultDatabaseConfig: { /* TypeORM DataSourceOptions */ },
defaultProvider: 'smtp', // Optional: default provider type
rateLimitPerMinute: 100, // Optional: rate limit on send endpoint
enableLogging: false, // Optional: debug logging
},
})forRootAsync (Factory)
import { ConfigService } from '@nestjs/config';
EmailModule.forRootAsync({
global: true,
includeController: true,
bootstrapAppConfig: {
databaseMode: 'single',
enableCompanyFeature: true,
},
imports: [ConfigModule],
useFactory: (configService: ConfigService) => ({
defaultDatabaseConfig: {
type: 'postgres',
host: configService.get('DB_HOST'),
port: configService.get<number>('DB_PORT'),
username: configService.get('DB_USER'),
password: configService.get('DB_PASSWORD'),
database: configService.get('DB_NAME'),
},
}),
inject: [ConfigService],
})forRootAsync (Class)
import { EmailOptionsFactory, IEmailModuleConfig } from '@flusys/nestjs-email';
@Injectable()
export class MyEmailConfigFactory implements EmailOptionsFactory {
createEmailOptions(): IEmailModuleConfig {
return { defaultDatabaseConfig: { /* ... */ } };
}
createOptions() { return this.createEmailOptions(); }
}
EmailModule.forRootAsync({
bootstrapAppConfig: { databaseMode: 'single', enableCompanyFeature: false },
useClass: MyEmailConfigFactory,
})Configuration Reference
interface IEmailModuleConfig extends IDataSourceServiceOptions {
/** Optional: default provider type when no config is specified */
defaultProvider?: 'smtp' | 'sendgrid' | 'mailgun';
/** Optional: max emails per minute (default: unlimited) */
rateLimitPerMinute?: number;
/** Optional: enable debug logging for send operations */
enableLogging?: boolean;
}Feature Toggles
| Feature | Config Key | Default | Effect |
|---------|-----------|---------|--------|
| Company scoping | enableCompanyFeature: true | false | Uses EmailConfigWithCompany and EmailTemplateWithCompany entities; filters all queries by companyId |
| Multi-tenant | databaseMode: 'multi-tenant' | 'single' | Creates per-tenant DataSource connections |
API Endpoints
All endpoints use POST. All require JWT authentication unless noted.
Email Config — POST /email/email-config/*
| Endpoint | Permission | Description |
|----------|-----------|-------------|
| POST /email/email-config/insert | email-config.create | Create a provider configuration |
| POST /email/email-config/get-all | email-config.read | List all configs |
| POST /email/email-config/get/:id | email-config.read | Get config by ID |
| POST /email/email-config/update | email-config.update | Update config |
| POST /email/email-config/delete | email-config.delete | Delete config |
| POST /email/email-config/test | email-config.create | Send a test email to verify config |
| POST /email/email-config/set-default | email-config.update | Set as default provider |
Email Templates — POST /email/email-template/*
| Endpoint | Permission | Description |
|----------|-----------|-------------|
| POST /email/email-template/insert | email-template.create | Create a template |
| POST /email/email-template/get-all | email-template.read | List all templates |
| POST /email/email-template/get/:id | email-template.read | Get template by ID |
| POST /email/email-template/update | email-template.update | Update template |
| POST /email/email-template/delete | email-template.delete | Delete template |
Email Send — POST /email/send/*
| Endpoint | Permission | Description |
|----------|-----------|-------------|
| POST /email/send/template | email-config.create | Send using a stored template |
| POST /email/send/raw | email-config.create | Send raw HTML email |
Entities
Core Entities (always registered)
| Entity | Table | Description |
|--------|-------|-------------|
| EmailConfig | email_config | Provider configuration (SMTP credentials, SendGrid API key, etc.) |
| EmailTemplate | email_template | Email templates with {{variable}} placeholders |
Company Feature Entities (enableCompanyFeature: true)
| Entity | Table | Description |
|--------|-------|-------------|
| EmailConfigWithCompany | email_config | Same as EmailConfig + companyId and branchId columns |
| EmailTemplateWithCompany | email_template | Same as EmailTemplate + companyId column |
Register Entities in TypeORM
import { EmailModule } from '@flusys/nestjs-email';
TypeOrmModule.forRoot({
entities: [
...EmailModule.getEntities({ enableCompanyFeature: true }),
// other entities
],
})Email Providers
SMTP (Default)
Create an EmailConfig record with provider smtp:
POST /email/email-config/insert
{
"name": "Company SMTP",
"provider": "smtp",
"config": {
"host": "smtp.gmail.com",
"port": 587,
"secure": false,
"user": "[email protected]",
"password": "app-password"
},
"fromEmail": "[email protected]",
"fromName": "My App",
"isDefault": true
}SendGrid
Install @sendgrid/mail first, then create a config:
POST /email/email-config/insert
{
"name": "SendGrid Production",
"provider": "sendgrid",
"config": { "apiKey": "SG.xxxxxxxxxxxx" },
"fromEmail": "[email protected]",
"fromName": "My App",
"isDefault": true
}Mailgun
Install mailgun.js form-data first, then create a config:
POST /email/email-config/insert
{
"name": "Mailgun",
"provider": "mailgun",
"config": {
"apiKey": "key-xxxxxxxxxxxx",
"domain": "mg.example.com",
"region": "us"
},
"fromEmail": "[email protected]",
"fromName": "My App",
"isDefault": true
}Custom Provider
Implement IEmailProvider and register it with StorageProviderRegistry:
import { IEmailProvider, EmailProviderRegistry } from '@flusys/nestjs-email';
class MyCustomProvider implements IEmailProvider {
async send(options: IEmailSendOptions): Promise<void> {
// custom sending logic
}
async testConnection(): Promise<boolean> {
return true;
}
}
// Register before module initialization
EmailProviderRegistry.register('custom', MyCustomProvider);Template Engine
Templates use {{variableName}} syntax. All values are HTML-escaped automatically.
Create a template:
POST /email/email-template/insert
{
"name": "Welcome Email",
"slug": "welcome",
"subject": "Welcome to {{appName}}, {{userName}}!",
"html": "<h1>Hello {{userName}}</h1><p>Welcome to <strong>{{appName}}</strong>.</p><p><a href=\"{{loginUrl}}\">Login here</a></p>",
"variables": ["appName", "userName", "loginUrl"]
}Send using the template:
POST /email/send/template
{
"templateSlug": "welcome",
"to": "[email protected]",
"variables": {
"appName": "My App",
"userName": "John Doe",
"loginUrl": "https://app.example.com/login"
}
}Exported Services
These services are exported by EmailModule and injectable in your application:
| Service | Description |
|---------|-------------|
| EmailSendService | Send emails via template slug or raw HTML |
| EmailTemplateService | CRUD for email templates |
| EmailProviderConfigService | CRUD for provider configurations |
| EmailConfigService | Exposes runtime config (provider defaults, rate limits) |
| EmailDataSourceProvider | Dynamic TypeORM DataSource resolution per request |
Note: Always use
@Inject(ServiceClass)explicitly — esbuild bundling loses TypeScript metadata.
Sending Emails Programmatically
Inject EmailSendService to send emails from other services:
import { EmailSendService } from '@flusys/nestjs-email';
@Injectable()
export class UserService {
constructor(
@Inject(EmailSendService) private readonly emailSendService: EmailSendService,
) {}
async sendWelcomeEmail(user: { email: string; name: string }): Promise<void> {
await this.emailSendService.sendTemplateEmail({
templateSlug: 'welcome',
to: user.email,
variables: { userName: user.name, appName: 'My App' },
});
}
async sendRawEmail(): Promise<void> {
await this.emailSendService.sendRawEmail({
to: '[email protected]',
subject: 'Hello',
html: '<p>Hello world</p>',
attachments: [
{
filename: 'report.pdf',
content: base64EncodedPdfString,
contentType: 'application/pdf',
},
],
});
}
}Troubleshooting
No default email config found
Create at least one EmailConfig record and mark it as default:
POST /email/email-config/set-default
{ "id": "your-config-id" }Template not found
Check the templateSlug matches exactly (case-sensitive). Use POST /email/email-template/get-all to list available templates.
SMTP connection refused
Use the test endpoint first:
POST /email/email-config/test
{ "id": "your-config-id", "to": "[email protected]" }For Gmail, enable "App Passwords" and use the app password, not your account password.
No metadata for entity
Call EmailModule.getEntities() with the correct flags when registering TypeOrmModule:
entities: [...EmailModule.getEntities({ enableCompanyFeature: true })]License
MIT © FLUSYS
Part of the FLUSYS framework — a full-stack monorepo powering Angular 21 + NestJS 11 applications.
