mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2025-11-14 00:32:35 +10:00
use nodemailer/smtp instead of sendgrid
This commit is contained in:
@ -20,7 +20,6 @@
|
||||
"@nestjs/serve-static": "^3.0.0",
|
||||
"@nestjs/terminus": "^9.1.1",
|
||||
"@nestjs/typeorm": "^9.0.1",
|
||||
"@sendgrid/mail": "^7.7.0",
|
||||
"@types/passport": "^1.0.10",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"cache-manager": "^4.1.0",
|
||||
@ -35,6 +34,7 @@
|
||||
"multer": "^1.4.4",
|
||||
"nanoid": "^3.3.4",
|
||||
"node-stream-zip": "^1.15.0",
|
||||
"nodemailer": "^6.7.8",
|
||||
"passport": "^0.6.0",
|
||||
"passport-jwt": "^4.0.0",
|
||||
"passport-local": "^1.0.0",
|
||||
@ -57,6 +57,7 @@
|
||||
"@types/lodash": "^4.14.184",
|
||||
"@types/multer": "^1.4.7",
|
||||
"@types/node": "^18.7.9",
|
||||
"@types/nodemailer": "^6.4.5",
|
||||
"@types/passport-jwt": "^3.0.6",
|
||||
"@types/passport-local": "^1.0.34",
|
||||
"prettier": "^2.7.1",
|
||||
|
||||
@ -6,7 +6,7 @@ import appConfig from './app.config';
|
||||
import authConfig from './auth.config';
|
||||
import databaseConfig from './database.config';
|
||||
import googleConfig from './google.config';
|
||||
import sendgridConfig from './sendgrid.config';
|
||||
import mailConfig from './mail.config';
|
||||
import storageConfig from './storage.config';
|
||||
|
||||
const validationSchema = Joi.object({
|
||||
@ -36,11 +36,13 @@ const validationSchema = Joi.object({
|
||||
GOOGLE_CLIENT_SECRET: Joi.string().allow(''),
|
||||
PUBLIC_GOOGLE_CLIENT_ID: Joi.string().allow(''),
|
||||
|
||||
// SendGrid
|
||||
SENDGRID_API_KEY: Joi.string().allow(''),
|
||||
SENDGRID_FORGOT_PASSWORD_TEMPLATE_ID: Joi.string().allow(''),
|
||||
SENDGRID_FROM_NAME: Joi.string().allow(''),
|
||||
SENDGRID_FROM_EMAIL: Joi.string().allow(''),
|
||||
// Mail
|
||||
MAIL_FROM_NAME: Joi.string().allow(''),
|
||||
MAIL_FROM_EMAIL: Joi.string().allow(''),
|
||||
MAIL_HOST: Joi.string().allow(''),
|
||||
MAIL_PORT: Joi.string().allow(''),
|
||||
MAIL_USERNAME: Joi.string().allow(''),
|
||||
MAIL_PASSWORD: Joi.string().allow(''),
|
||||
|
||||
// Storage
|
||||
STORAGE_BUCKET: Joi.string().allow(''),
|
||||
@ -54,7 +56,7 @@ const validationSchema = Joi.object({
|
||||
@Module({
|
||||
imports: [
|
||||
NestConfigModule.forRoot({
|
||||
load: [appConfig, authConfig, databaseConfig, googleConfig, sendgridConfig, storageConfig],
|
||||
load: [appConfig, authConfig, databaseConfig, googleConfig, mailConfig, storageConfig],
|
||||
validationSchema: validationSchema,
|
||||
}),
|
||||
],
|
||||
|
||||
12
server/src/config/mail.config.ts
Normal file
12
server/src/config/mail.config.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { registerAs } from '@nestjs/config';
|
||||
|
||||
export default registerAs('mail', () => ({
|
||||
from: {
|
||||
name: process.env.MAIL_FROM_NAME,
|
||||
email: process.env.MAIL_FROM_EMAIL,
|
||||
},
|
||||
host: process.env.MAIL_HOST,
|
||||
port: process.env.MAIL_PORT,
|
||||
username: process.env.MAIL_USERNAME,
|
||||
password: process.env.MAIL_PASSWORD,
|
||||
}));
|
||||
@ -1,8 +0,0 @@
|
||||
import { registerAs } from '@nestjs/config';
|
||||
|
||||
export default registerAs('sendgrid', () => ({
|
||||
apiKey: process.env.SENDGRID_API_KEY,
|
||||
forgotPasswordTemplateId: process.env.SENDGRID_FORGOT_PASSWORD_TEMPLATE_ID,
|
||||
fromName: process.env.SENDGRID_FROM_NAME,
|
||||
fromEmail: process.env.SENDGRID_FROM_EMAIL,
|
||||
}));
|
||||
30
server/src/mail/dto/send-mail.dto.ts
Normal file
30
server/src/mail/dto/send-mail.dto.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import { Type } from 'class-transformer';
|
||||
import { IsDefined, IsNotEmpty, IsString } from 'class-validator';
|
||||
|
||||
export class MailRecipient {
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
name: string;
|
||||
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
email: string;
|
||||
}
|
||||
|
||||
export class SendMailDto {
|
||||
@IsDefined()
|
||||
@Type(() => MailRecipient)
|
||||
from: MailRecipient;
|
||||
|
||||
@IsDefined()
|
||||
@Type(() => MailRecipient)
|
||||
to: MailRecipient;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
subject: string;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
message: string;
|
||||
}
|
||||
@ -1,40 +1,56 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import SendGrid from '@sendgrid/mail';
|
||||
import { createTransport, Transporter } from 'nodemailer';
|
||||
|
||||
import { User } from '@/users/entities/user.entity';
|
||||
|
||||
import { SendMailDto } from './dto/send-mail.dto';
|
||||
|
||||
@Injectable()
|
||||
export class MailService {
|
||||
constructor(private configService: ConfigService) {
|
||||
const sendGridApiKey = this.configService.get<string>('sendgrid.apiKey');
|
||||
transporter: Transporter;
|
||||
|
||||
if (sendGridApiKey) {
|
||||
SendGrid.setApiKey(this.configService.get<string>('sendgrid.apiKey'));
|
||||
}
|
||||
constructor(private configService: ConfigService) {
|
||||
this.transporter = createTransport({
|
||||
host: this.configService.get<string>('mail.host'),
|
||||
port: this.configService.get<number>('mail.port'),
|
||||
pool: true,
|
||||
secure: false,
|
||||
tls: { ciphers: 'SSLv3' },
|
||||
auth: {
|
||||
user: this.configService.get<string>('mail.username'),
|
||||
pass: this.configService.get<string>('mail.password'),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async sendEmail(mail: SendGrid.MailDataRequired) {
|
||||
return SendGrid.send(mail);
|
||||
async sendEmail(sendMailDto: SendMailDto) {
|
||||
this.transporter.sendMail({
|
||||
from: `${sendMailDto.from.name} <${sendMailDto.from.email}>`,
|
||||
to: `${sendMailDto.to.name} <${sendMailDto.to.email}>`,
|
||||
subject: sendMailDto.subject,
|
||||
text: sendMailDto.message,
|
||||
html: sendMailDto.message,
|
||||
});
|
||||
}
|
||||
|
||||
async sendForgotPasswordEmail(user: User, resetToken: string): Promise<void> {
|
||||
const appUrl = this.configService.get<string>('app.url');
|
||||
const url = `${appUrl}?modal=auth.reset&resetToken=${resetToken}`;
|
||||
|
||||
const mailData: SendGrid.MailDataRequired = {
|
||||
const sendMailDto: SendMailDto = {
|
||||
from: {
|
||||
name: this.configService.get<string>('sendgrid.fromName'),
|
||||
email: this.configService.get<string>('sendgrid.fromEmail'),
|
||||
name: this.configService.get<string>('mail.from.name'),
|
||||
email: this.configService.get<string>('mail.from.email'),
|
||||
},
|
||||
to: user.email,
|
||||
hideWarnings: true,
|
||||
dynamicTemplateData: { url },
|
||||
templateId: this.configService.get<string>('sendgrid.forgotPasswordTemplateId'),
|
||||
to: {
|
||||
name: user.name,
|
||||
email: user.email,
|
||||
},
|
||||
subject: 'Reset your Reactive Resume password',
|
||||
message: `<p>Hey ${user.name}!</p> <p>You can reset your password by visiting this link: <a href="${url}">${url}</a>.</p> <p>But hurry, because it will expire in 30 minutes.</p>`,
|
||||
};
|
||||
|
||||
await SendGrid.send(mailData);
|
||||
|
||||
return;
|
||||
await this.sendEmail(sendMailDto);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@ import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
|
||||
import { SchedulerRegistry } from '@nestjs/schedule';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { randomBytes } from 'crypto';
|
||||
import { Connection, Repository } from 'typeorm';
|
||||
import { DataSource, Repository } from 'typeorm';
|
||||
|
||||
import { MailService } from '@/mail/mail.service';
|
||||
|
||||
@ -19,7 +19,7 @@ export class UsersService {
|
||||
@InjectRepository(User) private userRepository: Repository<User>,
|
||||
private schedulerRegistry: SchedulerRegistry,
|
||||
private mailService: MailService,
|
||||
private connection: Connection
|
||||
private dataSource: DataSource
|
||||
) {}
|
||||
|
||||
async findById(id: number): Promise<User> {
|
||||
@ -93,7 +93,7 @@ export class UsersService {
|
||||
const user = await this.findByEmail(email);
|
||||
|
||||
const resetToken = randomBytes(32).toString('hex');
|
||||
const queryRunner = this.connection.createQueryRunner();
|
||||
const queryRunner = this.dataSource.createQueryRunner();
|
||||
|
||||
const timeout = setTimeout(async () => {
|
||||
await this.userRepository.update(user.id, { resetToken: null });
|
||||
|
||||
Reference in New Issue
Block a user