mailer configured

This commit is contained in:
vv-01 2025-03-20 17:52:53 +05:30
parent 831ea0eeef
commit 67a302ec50
10 changed files with 255 additions and 9 deletions

14
package-lock.json generated
View File

@ -21,7 +21,7 @@
"dotenv": "^16.3.1",
"handlebars": "^4.7.8",
"moment": "^2.30.1",
"nodemailer": "^6.9.9",
"nodemailer": "^6.10.0",
"otp-generator": "^4.0.1",
"passport-jwt": "^4.0.1",
"pg": "^8.14.1",
@ -7304,9 +7304,9 @@
"license": "MIT"
},
"node_modules/nodemailer": {
"version": "6.9.9",
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.9.tgz",
"integrity": "sha512-dexTll8zqQoVJEZPwQAKzxxtFn0qTnjdQTchoU6Re9BUUGBJiOy3YMn/0ShTW6J5M0dfQ1NeDeRTTl4oIWgQMA==",
"version": "6.10.0",
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.10.0.tgz",
"integrity": "sha512-SQ3wZCExjeSatLE/HBaXS5vqUOQk6GtBdIIKxiFdmm01mOQZX/POJkO3SUX1wDiYcwUOJwT23scFSC9fY2H8IA==",
"engines": {
"node": ">=6.0.0"
}
@ -15717,9 +15717,9 @@
"dev": true
},
"nodemailer": {
"version": "6.9.9",
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.9.tgz",
"integrity": "sha512-dexTll8zqQoVJEZPwQAKzxxtFn0qTnjdQTchoU6Re9BUUGBJiOy3YMn/0ShTW6J5M0dfQ1NeDeRTTl4oIWgQMA=="
"version": "6.10.0",
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.10.0.tgz",
"integrity": "sha512-SQ3wZCExjeSatLE/HBaXS5vqUOQk6GtBdIIKxiFdmm01mOQZX/POJkO3SUX1wDiYcwUOJwT23scFSC9fY2H8IA=="
},
"nodemon": {
"version": "3.0.2",

View File

@ -35,7 +35,7 @@
"dotenv": "^16.3.1",
"handlebars": "^4.7.8",
"moment": "^2.30.1",
"nodemailer": "^6.9.9",
"nodemailer": "^6.10.0",
"otp-generator": "^4.0.1",
"passport-jwt": "^4.0.1",
"pg": "^8.14.1",

View File

@ -21,6 +21,8 @@ import { TicketModule } from './ticket/ticket.module';
import { TicketPricingModule } from './ticketPricing/ticketPricing.module';
import { TimeSlotModule } from './timeSlot/timeSlot.module';
import { PushSubscriptionModule } from './push-subscription/push-subscription.module';
import { MailerModule } from './node-mailer/mailer.module';
import { ConfigModule } from '@nestjs/config';
@Module({
imports: [
@ -40,7 +42,9 @@ import { PushSubscriptionModule } from './push-subscription/push-subscription.mo
TicketPricingModule,
TimeSlotModule,
UserModule,
PushSubscriptionModule
PushSubscriptionModule,
MailerModule,
ConfigModule.forRoot()
],
controllers: [AppController, AppConfigController],

View File

@ -0,0 +1,20 @@
import { Test, TestingModule } from '@nestjs/testing';
import { MailerController } from './mailer.controller';
import { MailerService } from './mailer.service';
describe('MailerController', () => {
let controller: MailerController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [MailerController],
providers: [MailerService],
}).compile();
controller = module.get<MailerController>(MailerController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});

View File

@ -0,0 +1,63 @@
import {
Body,
Controller,
// Post,
// UsePipes,
// ValidationPipe,
} from '@nestjs/common';
import { MailerService } from './mailer.service';
// import { ApiResponse } from '@nestjs/swagger';
// import { SuccessResponse, ErrorResponse } from '../utils/response.util';
import { SendMailerDto } from './mailer.interface';
import { Address } from 'nodemailer/lib/mailer';
import * as path from 'path';
import * as fs from 'fs';
@Controller('mailer')
export class MailerController {
constructor(private readonly mailerService: MailerService) {}
// @Post('send-email')
// @ApiResponse({
// status: 201,
// description: 'Email sent successfully',
// type: SuccessResponse,
// })
// @ApiResponse({
// status: 400,
// description: 'Bad Request',
// type: ErrorResponse,
// })
// @UsePipes(new ValidationPipe({ whitelist: true }))
async sendEmail(
@Body()
body: {
from?: Address;
name: string;
email: string;
subject?: string;
otp: string;
},
) {
// const htmlTemplate = fs.readFileSync('emailTemplate.html', 'utf8');
const templatePath = path.join(
process.cwd(),
'src\\node-mailer\\templates\\emailTemplate.html',
);
if (!fs.existsSync(templatePath)) {
throw new Error(`Template file not found at: ${templatePath}`);
}
const htmlTemplate = fs.readFileSync(templatePath, 'utf8');
const sendDto: SendMailerDto = {
from: body.from,
reciepients: [{ name: body.name, address: body.email }],
subject: 'Registration OTP',
html: htmlTemplate
.replace('{{name}}', body.name)
.replace('{{otp}}', body.otp),
};
return this.mailerService.sendMail(sendDto);
}
}

View File

@ -0,0 +1,10 @@
import { Address } from 'nodemailer/lib/mailer';
export type SendMailerDto = {
from?: Address;
reciepients: Address[];
subject: string;
text?: string;
html: string;
placeholderReplacements?: Record<string, string>;
};

View File

@ -0,0 +1,12 @@
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { MailerService } from './mailer.service';
import { MailerController } from './mailer.controller';
@Module({
imports: [ConfigModule],
controllers: [MailerController],
providers: [MailerService, MailerController],
exports: [MailerService, MailerController],
})
export class MailerModule {}

View File

@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { MailerService } from './mailer.service';
describe('MailerService', () => {
let service: MailerService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [MailerService],
}).compile();
service = module.get<MailerService>(MailerService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});

View File

@ -0,0 +1,57 @@
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import * as nodemailer from 'nodemailer';
import { SendMailerDto } from './mailer.interface';
import Mail from 'nodemailer/lib/mailer';
@Injectable()
export class MailerService {
constructor(
private readonly configService: ConfigService
) {}
mailTransport() {
const transporter = nodemailer.createTransport({
service: 'gmail',
host: this.configService.get<string>('MAIL_HOST'),
auth: {
user: this.configService.get<string>('MAIL_USER'),
pass: this.configService.get<string>('MAIL_PASS'),
},
secure: true,
port: this.configService.get<number>('MAIL_PORT'),
});
return transporter;
}
template(html: string, replacements: Record<string, string>) {
return html.replace(/%(\w*)%/g, function (m, key) {
return replacements.hasOwnProperty(key) ? replacements[key] : '';
});
}
async sendMail(dto: SendMailerDto) {
const { from, reciepients, subject } = dto;
const html = dto.placeholderReplacements
? this.template(dto.html, dto.placeholderReplacements)
: dto.html;
const transporter = this.mailTransport();
const fromName = from?.name ?? this.configService.get<string>('APP_NAME');
const fromAddress =
from?.address ?? this.configService.get<string>('MAIL_USER');
const options: Mail.Options = {
from: {
name: fromName,
address: fromAddress,
},
to: reciepients,
subject,
html: this.template(html, dto.placeholderReplacements || {}),
};
try {
const result = await transporter.sendMail(options);
return result;
} catch (er) {
console.log(er);
}
}
}

View File

@ -0,0 +1,62 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Arogya Hrudaya Registration</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
background-color: #f4f4f4;
}
.container {
width: 100%;
max-width: 400px;
margin: 0 auto;
background-color: #ffffff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
.header {
background-color: #4CAF50;
color: white;
padding: 10px 0;
text-align: center;
border-radius: 8px 8px 0 0;
}
.content {
padding: 20px;
line-height: 1.6;
}
.footer {
text-align: center;
padding: 10px 0;
color: #777;
font-size: 12px;
}
a {
color: #4CAF50;
text-decoration: none;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>Codevice Solutions Private Limited</h1>
</div>
<div class="content">
<h2>Hello, {{name}}!</h2>
<p>Use the following OTP to login. OTP is valid for 5 mins. Do not share this OTP to anyone.</p>
<h2>{{otp}}</h2>
<p>Best regards,<br>CSPL</p>
</div>
<div class="footer">
<p>&copy; 2025 Codevice Solutions Pvt. Ltd. All rights reserved.</p>
</div>
</div>
</body>
</html>