├── src ├── interfaces │ ├── index.ts │ └── notif-sender-module-options.ts ├── index.ts ├── enums │ └── services.enum.ts ├── services │ ├── logger.service.ts │ ├── email-sender.service.ts │ └── telegram-sender.service.ts ├── notif-sender.module.ts ├── notif-sender.service.ts └── type.ts ├── .npmignore ├── .release-it.json ├── .prettierrc ├── tsconfig.json ├── .gitignore ├── package.json ├── README.md └── eslint.config.mjs /src/interfaces/index.ts: -------------------------------------------------------------------------------- 1 | export * from './notif-sender-module-options' -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './type' 2 | export * from './notif-sender.module' 3 | export * from './notif-sender.service' 4 | -------------------------------------------------------------------------------- /src/enums/services.enum.ts: -------------------------------------------------------------------------------- 1 | export enum EServices { 2 | TelegramSenderService = 'TelegramSenderService', 3 | EmailSenderService = 'EmailSenderService' 4 | } -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .github 2 | .husky 3 | src 4 | ./src 5 | test 6 | eslint.config.mjs 7 | .gitignore 8 | .npmrc 9 | .prettierrc 10 | nestjs-cli.json 11 | tsconfig.json 12 | tsconfig.tsbuildinfo -------------------------------------------------------------------------------- /.release-it.json: -------------------------------------------------------------------------------- 1 | { 2 | "git": { 3 | "commit": true, 4 | "tag": true, 5 | "push": true 6 | }, 7 | "npm": { 8 | "publish": true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/interfaces/notif-sender-module-options.ts: -------------------------------------------------------------------------------- 1 | import { TEmailSenderConfig, TLoggingConfig, TTelegramSenderConfig } from '../type' 2 | 3 | export interface INotifSenderOptions { 4 | emailSenderConfig?: TEmailSenderConfig 5 | telegramSenderConfig?: TTelegramSenderConfig 6 | logging?: TLoggingConfig 7 | } 8 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "always", 3 | "bracketSameLine": true, 4 | "bracketSpacing": true, 5 | "semi": false, 6 | "experimentalTernaries": false, 7 | "singleQuote": true, 8 | "jsxSingleQuote": true, 9 | "quoteProps": "consistent", 10 | "trailingComma": "es5", 11 | "singleAttributePerLine": false, 12 | "proseWrap": "preserve", 13 | "insertPragma": false, 14 | "requirePragma": false, 15 | "useTabs": false, 16 | "tabWidth": 4, 17 | "printWidth": 130 18 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "CommonJS", 4 | "target": "ES2021", 5 | "sourceMap": false, 6 | "outDir": "./dist", 7 | "rootDir": "./src", 8 | "strict": true, 9 | "declaration": true, 10 | "removeComments": false, 11 | "emitDecoratorMetadata": true, 12 | "experimentalDecorators": true, 13 | "allowSyntheticDefaultImports": true, 14 | "skipLibCheck": true, 15 | "strictNullChecks": true, 16 | "noImplicitAny": true, 17 | "strictBindCallApply": false, 18 | "forceConsistentCasingInFileNames": false, 19 | "noFallthroughCasesInSwitch": false, 20 | "esModuleInterop":true , 21 | } 22 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist 3 | /node_modules 4 | /build 5 | 6 | # Logs 7 | logs 8 | *.log 9 | npm-debug.log* 10 | pnpm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | lerna-debug.log* 14 | 15 | # Compiler 16 | tsconfig.tsbuildinfo 17 | 18 | # OS 19 | .DS_Store 20 | 21 | # Tests 22 | /coverage 23 | /.nyc_output 24 | 25 | # IDEs and editors 26 | /.idea 27 | .project 28 | .classpath 29 | .c9/ 30 | *.launch 31 | .settings/ 32 | *.sublime-workspace 33 | 34 | # IDE - VSCode 35 | .vscode/* 36 | !.vscode/settings.json 37 | !.vscode/tasks.json 38 | !.vscode/launch.json 39 | !.vscode/extensions.json 40 | 41 | # dotenv environment variable files 42 | .env 43 | .env.development.local 44 | .env.test.local 45 | .env.production.local 46 | .env.local 47 | 48 | # temp directory 49 | .temp 50 | .tmp 51 | 52 | # Runtime data 53 | pids 54 | *.pid 55 | *.seed 56 | *.pid.lock 57 | 58 | # Diagnostic reports (https://nodejs.org/api/report.html) 59 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 60 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "notif-sender-nestjs", 3 | "version": "0.2.5", 4 | "main": "./dist/index.js", 5 | "types": "./dist/index.d.ts", 6 | "exports": { 7 | "require": "./dist/index.js", 8 | "types": "./dist/index.d.ts" 9 | }, 10 | "files": [ 11 | "dist" 12 | ], 13 | "keywords": [ 14 | "nestjs", 15 | "nestjs", 16 | "notification", 17 | "email", 18 | "telegram", 19 | "rxjs" 20 | ], 21 | "author": "Abolfazl Ghaderi ", 22 | "license": "MIT", 23 | "description": "A NestJS module for sending notifications via Email and Telegram.", 24 | "bugs": { 25 | "url": "https://github.com/abolfazlghaderi/notif-sender-nestjs/issues" 26 | }, 27 | "repository": { 28 | "type": "git", 29 | "url": "https://github.com/abolfazlghaderi/notif-sender-nestjs" 30 | }, 31 | "scripts": { 32 | "release": "npm run build && release-it", 33 | "build": "tsc", 34 | "lint:fix": "eslint . --fix", 35 | "lint:nfix": "eslint . --no--fix" 36 | }, 37 | "dependencies": { 38 | "@nestjs/common": "^11.0.8", 39 | "nodemailer": "^6.10.0", 40 | "rxjs": "^7.8.1" 41 | }, 42 | "devDependencies": { 43 | "@eslint/js": "^9.19.0", 44 | "@nestjs/core": "^11.0.8", 45 | "@types/node": "^22.13.1", 46 | "@types/nodemailer": "^6.4.17", 47 | "@typescript-eslint/parser": "^8.23.0", 48 | "eslint-plugin-jsdoc": "^50.6.3", 49 | "eslint-plugin-prettier": "^5.2.3", 50 | "eslint-plugin-security": "^3.0.1", 51 | "eslint-plugin-unicorn": "^56.0.1", 52 | "globals": "^15.14.0", 53 | "release-it": "^18.1.2", 54 | "tsx": "^4.19.2", 55 | "typescript": "^5.7.3", 56 | "typescript-eslint": "^8.23.0" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/services/logger.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Logger } from '@nestjs/common' 2 | 3 | @Injectable() 4 | export class LoggerService 5 | { 6 | private logger = new Logger() 7 | 8 | constructor(private readonly options: { enable: boolean, context?: string }) {} 9 | 10 | private isEnable(): boolean 11 | { 12 | return this.options.enable 13 | } 14 | 15 | error(message: unknown, metadata: { satck? :string, context?: string } = {}): void 16 | { 17 | if (!this.isEnable()) return 18 | this.logger.error(message, metadata.satck, this.options.context ?? metadata.context) 19 | } 20 | 21 | log(message: string, metadata: { context?: string } = {}): void 22 | { 23 | if (!this.isEnable()) return 24 | this.logger.log(message, this.options.context ?? metadata.context) 25 | } 26 | 27 | warn(message: string, metadata: { context?: string } = {}): void 28 | { 29 | if (!this.isEnable()) return 30 | this.logger.warn(message, this.options.context ?? metadata.context) 31 | } 32 | 33 | debug(message: string, metadata: { context?: string } = {}): void 34 | { 35 | if (!this.isEnable()) return 36 | this.logger.debug(message, this.options.context ?? metadata.context) 37 | } 38 | 39 | verbose(message: string, metadata: { context?: string } = {}): void 40 | { 41 | if (!this.isEnable()) return 42 | this.logger.verbose(message, this.options.context ?? metadata.context) 43 | } 44 | 45 | fatal(message: string, metadata: { context?: string } = {}): void 46 | { 47 | if (!this.isEnable()) return 48 | this.logger.fatal(`[FATAL] ${message}`, this.options.context ?? metadata.context) 49 | } 50 | } -------------------------------------------------------------------------------- /src/notif-sender.module.ts: -------------------------------------------------------------------------------- 1 | import { DynamicModule, Module } from '@nestjs/common' 2 | import { NotifSenderService } from './notif-sender.service' 3 | import { INotifSenderOptions } from './interfaces/notif-sender-module-options' 4 | 5 | @Module({}) 6 | export class NotifSenderModule 7 | { 8 | /** 9 | * Registers the NotifSenderModule with the given configuration options. 10 | * This method returns a dynamic module that can be imported into other modules. 11 | * 12 | * @param {INotifSenderOptions} options - The configuration options for the notification sender. 13 | * @returns {DynamicModule} The dynamic module containing the NotifSenderService. 14 | * 15 | * @example 16 | *{ 17 | emailSenderConfig: { 18 | enable: true, 19 | host: 'sandbox.smtp.mailtrap.io', 20 | port: 2525, 21 | secure: false, 22 | auth: { 23 | user: 'username', 24 | pass: '***********', 25 | }, 26 | maxConcurrentRequests: 2 27 | }, 28 | telegramSenderConfig: { 29 | enable: true, 30 | botToken: 'TOKEN', 31 | chatId: 'ID', 32 | maxConcurrentRequests: 2 33 | }, 34 | logging: { 35 | enable: true, 36 | } 37 | } 38 | */ 39 | static register(options: INotifSenderOptions): DynamicModule 40 | { 41 | return { 42 | module: NotifSenderModule, 43 | providers: [ 44 | { 45 | provide: 'NOTIF_SENDER_OPTIONS', 46 | useValue: options, 47 | }, 48 | NotifSenderService, 49 | ], 50 | exports: [ NotifSenderService ], 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/notif-sender.service.ts: -------------------------------------------------------------------------------- 1 | import { Inject, Injectable } from '@nestjs/common' 2 | import { TEmailContent, TTelegramContent } from './type' 3 | import { EmailSenderService } from './services/email-sender.service' 4 | import { TelegramSenderService } from './services/telegram-sender.service' 5 | import { INotifSenderOptions } from './interfaces/notif-sender-module-options' 6 | 7 | @Injectable() 8 | export class NotifSenderService 9 | { 10 | private emailSenderService: EmailSenderService | undefined 11 | private telegramSenderService: TelegramSenderService | undefined 12 | 13 | constructor(@Inject('NOTIF_SENDER_OPTIONS') private options: INotifSenderOptions) 14 | { 15 | if (this.options.emailSenderConfig) 16 | { 17 | this.emailSenderService = new EmailSenderService({ 18 | ...this.options.emailSenderConfig, 19 | logging: { 20 | enable: this.options.logging?.enable ?? true, // defualt is true 21 | }, 22 | }) 23 | } 24 | if (this.options.telegramSenderConfig) 25 | { 26 | this.telegramSenderService = new TelegramSenderService({ 27 | ...this.options.telegramSenderConfig, 28 | logging: { 29 | enable: this.options.logging?.enable ?? true, // defualt is true 30 | }, 31 | }) 32 | } 33 | } 34 | 35 | async sendNotifToEmail(notifContent: TEmailContent) 36 | { 37 | const emailId = Math.floor(Math.random() * 1e8).toString() 38 | return this.emailSenderService?.sendNotifToEmail({ ...notifContent, emailId }) 39 | } 40 | 41 | sendNotifToEmail_addToQueue(notifContent: TEmailContent) 42 | { 43 | const emailId = Math.floor(Math.random() * 1e8).toString() 44 | this.emailSenderService?.sendNotifToEmail_addToQueue({ ...notifContent, emailId }) 45 | } 46 | 47 | async sendNotifToTelegram(notifContent: TTelegramContent) 48 | { 49 | const messageId = Math.floor(Math.random() * 1e8).toString() 50 | return this.telegramSenderService?.sendNotifToTelegram({ ...notifContent, messageId }) 51 | } 52 | 53 | sendNotifToTelegram_addToQueue(notifContent: TTelegramContent) 54 | { 55 | const messageId = Math.floor(Math.random() * 1e8).toString() 56 | this.telegramSenderService?.sendNotifToTelegram_addToQueue({ ...notifContent, messageId }) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/type.ts: -------------------------------------------------------------------------------- 1 | export type TEmailSenderConfig = { 2 | /** 3 | * Indicates whether the email sending service is enabled. 4 | * If `true`, the service will be active. 5 | */ 6 | enable: boolean 7 | 8 | /** 9 | * The SMTP server address used to send emails (e.g., `sandbox.smtp.mailtrap.io`). 10 | */ 11 | host: string 12 | 13 | /** 14 | * The port number used by the SMTP server (e.g., `465` or `587` or ...). 15 | */ 16 | port: number 17 | 18 | /** 19 | * If `true`, a secure connection (SSL/TLS) will be used. 20 | */ 21 | secure: boolean 22 | 23 | /** 24 | * Authentication information for connecting to the SMTP server. 25 | */ 26 | auth: { 27 | /** 28 | * The username used to authenticate with the SMTP server. 29 | */ 30 | user: string 31 | 32 | /** 33 | * The password or authentication token for the SMTP server. 34 | */ 35 | pass: string 36 | } 37 | 38 | /** 39 | * Default email settings for sending messages. 40 | */ 41 | default?: { 42 | /** 43 | * The default "from" address for sent emails. 44 | * If not set, the sender address must be specified manually. 45 | */ 46 | from?: string 47 | } 48 | 49 | /** 50 | * The maximum number of concurrent email sending requests. 51 | * If not specified, the default value may be `2`. 52 | */ 53 | maxConcurrentRequests?: number 54 | } 55 | export type TEmailContent = { 56 | to: string 57 | subject: string 58 | text: string 59 | html?: string 60 | from?: string 61 | } 62 | export type TLoggingConfig = { 63 | enable: boolean 64 | } 65 | export type TTelegramContent = { 66 | text: string 67 | chatId?: string 68 | } 69 | export type TTelegramSenderConfig = { 70 | /** 71 | * Indicates whether the Telegram notification service is enabled. 72 | * If `true`, the service will be active. 73 | */ 74 | enable: boolean 75 | 76 | /** 77 | * The bot token provided by the Telegram Bot API. 78 | * This token is used to authenticate the bot. 79 | */ 80 | botToken: string 81 | 82 | /** 83 | * The chat ID where messages will be sent. 84 | * This can be a user ID or a group/channel ID. 85 | */ 86 | chatId: string 87 | 88 | /** 89 | * The maximum number of concurrent message sending requests. 90 | * If not specified, the default value may be `2`. 91 | */ 92 | maxConcurrentRequests?: number 93 | } 94 | export type LoggingConfigInOption = { logging: { enable: boolean } } -------------------------------------------------------------------------------- /src/services/email-sender.service.ts: -------------------------------------------------------------------------------- 1 | import * as nodemailer from 'nodemailer' 2 | import { mergeMap, Subject } from 'rxjs' 3 | import { Injectable } from '@nestjs/common' 4 | import { LoggerService } from './logger.service' 5 | import { EServices } from '../enums/services.enum' 6 | import { LoggingConfigInOption, TEmailContent, TEmailSenderConfig } from '../type' 7 | 8 | @Injectable() 9 | export class EmailSenderService 10 | { 11 | private emailTransporter: nodemailer.Transporter 12 | private emailQueue = new Subject() 13 | private logger = new LoggerService({ 14 | enable: this.options.logging.enable, 15 | context: EServices.EmailSenderService, 16 | }) 17 | 18 | constructor(private options: TEmailSenderConfig & LoggingConfigInOption ) 19 | { 20 | this.emailTransporter = nodemailer.createTransport({ 21 | host: this.options.host, 22 | port: this.options.port, 23 | secure: this.options.secure, 24 | auth: this.options.auth, 25 | from: this.options.default?.from, 26 | }) 27 | 28 | this.emailQueue 29 | .pipe( 30 | mergeMap(async (notif) => 31 | { 32 | const isSuccess = await this.sendNotifToEmail(notif) 33 | if (!isSuccess) setTimeout(() => this.emailQueue.next(notif), 5000) 34 | await new Promise(resolve => setTimeout(resolve, 1000)) 35 | 36 | }, this.options.maxConcurrentRequests || 2), 37 | ) 38 | .subscribe() 39 | } 40 | 41 | async sendNotifToEmail(notifContent: TEmailContent & { emailId: string }) 42 | { 43 | this.logger.verbose(`<${notifContent.emailId}> Sending email to: ${notifContent.to}, Subject: ${notifContent.subject}`) 44 | if (this.options.enable) 45 | { 46 | try 47 | { 48 | await this.emailTransporter.sendMail({ 49 | from: notifContent.from || this.options.default?.from, 50 | to: notifContent.to, 51 | subject: notifContent.subject, 52 | text: notifContent.text, 53 | html: notifContent.html, 54 | }) 55 | 56 | this.logger.verbose(`<${notifContent.emailId}> Email sent successfully to: ${notifContent.to}`) 57 | 58 | return true 59 | } 60 | catch (error) 61 | { 62 | this.logger.error( 63 | `<${notifContent.emailId}> Failed to send email to: ${notifContent.to}, Error: ${(error as any)?.message || 'Unknown error'}`, 64 | ) 65 | return false 66 | } 67 | } 68 | else 69 | { 70 | this.logger.error( 71 | `<${notifContent.emailId}> Error: Email sender is disable`, 72 | ) 73 | return false 74 | } 75 | } 76 | 77 | sendNotifToEmail_addToQueue(notifContent: TEmailContent & { emailId: string }) 78 | { 79 | this.emailQueue.next(notifContent) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/services/telegram-sender.service.ts: -------------------------------------------------------------------------------- 1 | import { mergeMap, Subject } from 'rxjs' 2 | import { Injectable } from '@nestjs/common' 3 | import { LoggerService } from './logger.service' 4 | import { EServices } from '../enums/services.enum' 5 | import { LoggingConfigInOption, TTelegramContent, TTelegramSenderConfig } from '../type' 6 | 7 | @Injectable() 8 | export class TelegramSenderService 9 | { 10 | private chatId: string 11 | private botToken: string 12 | private messageQueue = new Subject() 13 | private logger = new LoggerService({ 14 | enable: this.options.logging.enable, 15 | context: EServices.TelegramSenderService, 16 | }) 17 | 18 | constructor(private options: TTelegramSenderConfig & LoggingConfigInOption) 19 | { 20 | this.chatId = this.options.chatId 21 | this.botToken = this.options.botToken 22 | 23 | this.messageQueue 24 | .pipe( 25 | mergeMap(async (notif) => 26 | { 27 | const isSuccess = await this.sendNotifToTelegram(notif) 28 | if (!isSuccess) setTimeout(() => this.messageQueue.next(notif), 5000) 29 | await new Promise((resolve) => setTimeout(resolve, 1000)) 30 | }, this.options.maxConcurrentRequests || 2), 31 | ) 32 | .subscribe() 33 | } 34 | 35 | getUrl(botToken: string) 36 | { 37 | return `https://api.telegram.org/bot${botToken}/sendMessage` 38 | } 39 | 40 | async sendNotifToTelegram(notifContent: TTelegramContent & { messageId: string }) 41 | { 42 | const chatId = notifContent.chatId ?? this.options.chatId 43 | const messageId = notifContent.messageId 44 | 45 | this.logger.verbose(`<${ messageId }> Sending message in telegram | ChatId: ${ chatId }`) 46 | if (this.options.enable) 47 | { 48 | try 49 | { 50 | const data = { 51 | chat_id: chatId, 52 | text: notifContent.text, 53 | } 54 | const response = await fetch(this.getUrl(this.botToken), { 55 | method: 'POST', 56 | headers: { 'Content-Type': 'application/json' }, 57 | body: JSON.stringify(data), 58 | }) 59 | const result = await response.json() 60 | if (!result?.ok) 61 | { 62 | throw new Error(`${ result?.description || 'Unknown error' } | ErrorCode: ${ result?.error_code || 'Unknown code' }`) 63 | } 64 | this.logger.verbose( 65 | `<${ messageId }> Message sent successfully to telegram | ChatId: ${ chatId }`) 66 | return true 67 | } 68 | catch (error) 69 | { 70 | this.logger.error( 71 | `<${ messageId }> Message failed to send to Telegram | ChatId: ${ chatId } Error: ${ (error as any)?.message || 'Unknown error' }`) 72 | return false 73 | } 74 | } 75 | else 76 | { 77 | this.logger.error(`<${ messageId }> Error: Telegram sender is disable`) 78 | return false 79 | } 80 | } 81 | 82 | sendNotifToTelegram_addToQueue(notifContent: TTelegramContent & { messageId: string }) 83 | { 84 | this.messageQueue.next(notifContent) 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🔔 Notif-Sender NestJS 2 | 3 | Notif-Sender-NestJS is a notification service built for NestJS applications. It provides an easy way to send notifications via **Email** and **Telegram**, with built-in queue management using `RxJS` for better scalability. 4 | 5 | ## Features 6 | 7 | - ✅ Send **Email** notifications using SMTP 8 | - ✅ Send **Telegram** messages to a bot 9 | - ✅ Built-in **queue management** using `RxJS` 10 | - ✅ Designed for scalability and easy integration into NestJS projects 11 | - ✅ Limit the maximum number of concurrent requests for sending notifications 12 | 13 | ## Queue Management with RxJS 14 | 15 | Notif-Sender uses `RxJS` to queue and manage notifications, ensuring that messages are sent efficiently without blocking the main thread. This is useful for handling large volumes of notifications. 16 | 17 | ## 🪴 Usage 18 | 19 | This example demonstrates how to use `notif-sender-nestjs` for sending emails in a NestJS application. 20 | 21 | ## 📥 Installation 22 | 23 | Make sure you have installed the required package: 24 | 25 | ```sh 26 | npm install notif-sender-nestjs 27 | ``` 28 | Or if you use `pnpm`: 29 | ```sh 30 | pnpm add notif-sender-nestjs 31 | ``` 32 | 33 | ## 🧑‍💻 Configuration 34 | 35 | Import `NotifSenderModule` in your module and configure it as follows: 36 | 37 | ```ts 38 | import { NotifSenderModule } from 'notif-sender-nestjs' 39 | 40 | @Module({ 41 | imports: [ 42 | NotifSenderModule.register({ 43 | emailSenderConfig: { 44 | enable: boolean, 45 | host: 'string', 46 | port: number, 47 | secure: boolean, 48 | auth: { 49 | user: 'string', 50 | pass: '******', 51 | }, 52 | default: { 53 | from: 'string', // optional 54 | }, 55 | maxConcurrentRequests: number // default is 2 56 | }, 57 | telegramSenderConfig: { 58 | enable: boolean, 59 | botToken: 'string', 60 | chatId: 'string', 61 | maxConcurrentRequests: number // default is 2 62 | }, 63 | logging: { 64 | enable: true // default is true! 65 | } 66 | }), 67 | ], 68 | controllers: [ AppController ], 69 | providers: [ AppService ], 70 | }) 71 | export class AppModule {} 72 | ``` 73 | 74 | ## 💎 Usage in Service 75 | 76 | This service provides four methods for sending notifications via Telegram or email: 77 | 78 | ### 📧 Email : 79 | 80 | 💥 **sendNotifToEmail :** Sends an email immediately. Use this when you need to ensure the email is sent before continuing execution. 81 | 82 | 🗃️ **sendNotifToEmail_addToQueue :** Queues the email for later sending. This is useful when you don't need an immediate response and just want the email to be processed asynchronously. 83 | 84 | ### 📨 Telegram : 85 | 86 | 💥 **sendNotifToTelegram :** The `sendNotifToTelegram` method sends notifications directly to the chat ID configured during setup. However, you can also specify a different chat ID when calling this method. 87 | 88 | ⚠️ Important: Before sending a notification, the user must have already started a conversation with the bot; otherwise, the bot won't be able to send messages 89 | 90 | ⚠️ To send a message in a group or channel, the bot must be an admin! 91 | 92 | 🗃️ **sendNotifToTelegram_addToQueue :** This method works similarly to `sendNotifToTelegram`, but instead of sending the notification immediately, it adds it to a queue and sends it in order 93 | 94 | 95 | Inject `NotifSenderService` into your service and send an email as follows: 96 | 97 | ```ts 98 | import { NotifSenderService } from 'notif-sender-nestjs' 99 | 100 | @Injectable() 101 | export class AppService { 102 | constructor(private readonly notifService: NotifSenderService) {} 103 | 104 | async getHello() 105 | { 106 | // Send to email 107 | await this.notifService.sendNotifToEmail({ 108 | subject: 'Hello', 109 | text: 'Hello World!', 110 | to: 'dev.ghaderi@gmail.com', 111 | html: '

Hello World!

', 112 | from: 'Abolfazl Ghaderi ' // Optional - higher priority 113 | }) 114 | 115 | // Send to telegram 116 | await this.notifService.sendNotifToTelegram({ 117 | text: 'Hi mr.Brian', 118 | chatId: 'string', // Optional - higher priority 119 | }) 120 | 121 | return 'Hello World!' 122 | } 123 | 124 | // OR 125 | 126 | getHello() 127 | { 128 | // Send to email 129 | this.notifService.sendNotifToEmail_addToQueue({ 130 | subject: 'Hello', 131 | text: 'Hello World!', 132 | to: 'dev.ghaderi@gmail.com', 133 | html: '

Hello World!

', 134 | from: 'Abolfazl Ghaderi ' // Optional - higher priority 135 | }) 136 | 137 | // Send to telegram 138 | this.notifService.sendNotifToTelegram_addToQueue({ 139 | text: 'Hi mr.Brian', 140 | chatId: 'string', // Optional - higher priority 141 | }) 142 | 143 | return 'Hello World!' 144 | } 145 | } 146 | ``` 147 | ## 🚀 Feedback & Contact 148 | 149 | Got any questions or suggestions? Feel free to reach out ❤️ 150 | 151 | 🌐 **Website:** [abolfazlghaderi.ir](https://abolfazlghaderi.ir) 152 | 📩 **Email:** [dev.ghaderi@gmail.com](mailto:dev.ghaderi@gmail.com) 153 | 💬 **Telegram:** [@Abolfazl_ghaderii](https://t.me/Abolfazl_ghaderii) 154 | 155 | Let's build something amazing together! ✨ 156 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import pluginJs from '@eslint/js' 2 | import tseslint from 'typescript-eslint' 3 | import globals from 'globals' 4 | import eslintPluginPrettier from 'eslint-plugin-prettier' 5 | import eslintPluginJsdoc from 'eslint-plugin-jsdoc' 6 | import eslintPluginSecurity from 'eslint-plugin-security' 7 | import eslintPluginUnicorn from 'eslint-plugin-unicorn' 8 | 9 | export default [ 10 | { 11 | ignores: ['node_modules/', 'dist/', '.env', 'eslint.config.mjs', '.vscode/', 'builds/' ], 12 | }, 13 | { 14 | plugins: { 15 | prettier: eslintPluginPrettier, 16 | jsdoc: eslintPluginJsdoc, 17 | security: eslintPluginSecurity, 18 | unicorn: eslintPluginUnicorn, 19 | }, 20 | 21 | files: ['**/*.{js,mjs,cjs,ts}'], 22 | languageOptions: { 23 | sourceType: 'commonjs', 24 | ecmaVersion: 2021, 25 | globals: { 26 | ...globals.browser, 27 | ...globals.node, 28 | }, 29 | }, 30 | rules: { 31 | 'array-bracket-spacing': ['error', 'always'], 32 | 'brace-style': ['error', 'allman', { allowSingleLine: true }], 33 | 'comma-dangle': ['error', 'always-multiline'], 34 | 'comma-spacing': ['error', { before: false, after: true }], 35 | eqeqeq: 'warn', 36 | indent: [ 37 | 'error', 38 | 4, 39 | { 40 | SwitchCase: 1, 41 | ignoredNodes: ['PropertyDefinition'], 42 | }, 43 | ], 44 | 'init-declarations': 'off', 45 | 'linebreak-style': ['error', 'unix'], 46 | 'no-cond-assign': ['error', 'always'], 47 | 'no-console': 'off', 48 | 'no-empty': 'off', 49 | 'no-inline-comments': 'off', 50 | 'object-curly-spacing': [ 51 | 'error', 52 | 'always', 53 | { arraysInObjects: true, objectsInObjects: true }, 54 | ], 55 | 'one-var': 'off', 56 | quotes: ['error', 'single'], 57 | semi: ['error', 'never'], 58 | 'space-infix-ops': ['error', { int32Hint: false }], 59 | 'space-before-blocks': [ 60 | 'error', 61 | { functions: 'always', keywords: 'always', classes: 'always' }, 62 | ], 63 | 'arrow-spacing': ['error', { before: true, after: true }], 64 | 'switch-colon-spacing': 'error', 65 | 'block-spacing': 'error', 66 | 'space-unary-ops': [ 67 | 'error', 68 | { 69 | words: true, 70 | nonwords: false, 71 | overrides: { 72 | new: false, 73 | '++': true, 74 | }, 75 | }, 76 | ], 77 | 'spaced-comment': [ 78 | 'error', 79 | 'always', 80 | { markers: ['/'], exceptions: ['-'] }, 81 | ], 82 | 'keyword-spacing': 'error', 83 | 'space-before-function-paren': [ 84 | 'error', 85 | { anonymous: 'always', named: 'never', asyncArrow: 'always' }, 86 | ], 87 | 'semi-spacing': ['error', { before: false, after: true }], 88 | 'no-trailing-spaces': ['error', { ignoreComments: true }], 89 | 'no-mixed-spaces-and-tabs': ['error', 'smart-tabs'], 90 | strict: 'off', 91 | 'no-spaced-func': 'error', 92 | 'unicorn/filename-case': [ 93 | 'warn', 94 | { 95 | cases: { 96 | kebabCase: true, 97 | }, 98 | }, 99 | ], 100 | 'no-restricted-imports': [ 101 | 'error', 102 | { 103 | paths: ['src'], 104 | patterns: ['src/*'], 105 | }, 106 | ], 107 | 'no-eval': 'error', 108 | 'unicorn/no-fn-reference-in-iterator': 'off', 109 | 'unicorn/no-array-for-each': 'off', 110 | 'unicorn/no-null': 'off', 111 | 'unicorn/prefer-array-some': 'off', 112 | 'unicorn/consistent-destructuring': 'off', 113 | 'unicorn/no-array-reduce': 'off', 114 | 'unicorn/prefer-module': 'off', 115 | 'unicorn/prefer-spread': 'off', 116 | 'unicorn/no-array-callback-reference': 'off', 117 | 'unicorn/consistent-function-scoping': 'off', 118 | 'unicorn/no-useless-undefined': 'off', 119 | 'unicorn/prefer-ternary': 'off', 120 | 'unicorn/prefer-node-protocol': 'off', 121 | 'unicorn/prefer-top-level-await': 'off', 122 | 'unicorn/prevent-abbreviations': [ 123 | 'off', 124 | { 125 | allowList: { Param: true, Req: true, Res: true }, 126 | ignore: ['\\.e2e-spec$', /^args/i, /^ignore/i], 127 | }, 128 | ], 129 | }, 130 | settings: { 131 | 'import/extensions': ['.js', '.cjs', '.mjs', '.ts', '.cts', '.mts'], 132 | }, 133 | }, 134 | pluginJs.configs.recommended, 135 | ...tseslint.configs.recommended, 136 | { 137 | files: ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx"], 138 | ignores: [ 'eslint.config.mjs', 'dist/' ], 139 | languageOptions: { 140 | ecmaVersion: 2021, 141 | sourceType: "module", 142 | globals: { 143 | Atomics: "readonly", 144 | SharedArrayBuffer: "readonly", 145 | }, 146 | parserOptions: { 147 | project: "./tsconfig.json", 148 | }, 149 | }, 150 | rules: { 151 | '@typescript-eslint/no-unused-vars': [ 152 | 'warn', 153 | { args: 'after-used', vars: 'all', varsIgnorePattern: '^_' }, 154 | ], 155 | '@typescript-eslint/restrict-template-expressions': 'off', 156 | '@typescript-eslint/no-unsafe-member-access': 'off', 157 | '@typescript-eslint/camelcase': 'off', 158 | '@typescript-eslint/no-unsafe-return': 'off', 159 | '@typescript-eslint/no-unsafe-call': 'off', 160 | '@typescript-eslint/no-unsafe-assignment': 'off', 161 | '@typescript-eslint/naming-convention': [ 162 | 'error', 163 | { 164 | selector: 'default', 165 | format: null, 166 | }, 167 | { 168 | selector: 'variable', 169 | format: ['camelCase', 'PascalCase', 'UPPER_CASE'], 170 | types: ['boolean'], 171 | prefix: ['is', 'should', 'has', 'can', 'did', 'will'], 172 | }, 173 | { 174 | selector: 'variableLike', 175 | format: ['camelCase', 'UPPER_CASE', 'PascalCase'], 176 | leadingUnderscore: 'allow', 177 | }, 178 | { 179 | selector: 'parameter', 180 | format: ['camelCase'], 181 | leadingUnderscore: 'allow', 182 | }, 183 | { 184 | selector: 'memberLike', 185 | modifiers: ['private'], 186 | format: ['camelCase'], 187 | leadingUnderscore: 'allow', 188 | }, 189 | { 190 | selector: 'typeLike', 191 | format: ['PascalCase'], 192 | }, 193 | { 194 | selector: 'property', 195 | modifiers: ['readonly'], 196 | format: ['camelCase', 'UPPER_CASE'], 197 | }, 198 | // { 199 | // selector: 'enumMember', 200 | // format: [ 'PascalCase' ], 201 | // }, 202 | ], 203 | }, 204 | }, 205 | ] 206 | --------------------------------------------------------------------------------