use nodemailer/smtp instead of sendgrid

This commit is contained in:
Amruth Pillai
2022-08-22 19:26:13 +02:00
parent 02587255fe
commit 5b6f6b7621
11 changed files with 120 additions and 103 deletions

View File

@ -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",

View File

@ -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,
}),
],

View 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,
}));

View File

@ -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,
}));

View 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;
}

View File

@ -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);
}
}

View File

@ -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 });