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

@ -22,10 +22,12 @@ JWT_SECRET=
JWT_EXPIRY_TIME=604800
GOOGLE_CLIENT_SECRET=
GOOGLE_API_KEY=
SENDGRID_API_KEY=
SENDGRID_FORGOT_PASSWORD_TEMPLATE_ID=
SENDGRID_FROM_NAME=
SENDGRID_FROM_EMAIL=
MAIL_FROM_NAME=
MAIL_FROM_EMAIL=
MAIL_HOST=
MAIL_PORT=
MAIL_USERNAME=
MAIL_PASSWORD=
STORAGE_BUCKET=
STORAGE_REGION=
STORAGE_ENDPOINT=

View File

@ -6,10 +6,7 @@ services:
container_name: postgres
ports:
- 5432:5432
environment:
- POSTGRES_DB=postgres
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
env_file: .env.docker
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
@ -38,7 +35,7 @@ services:
context: .
dockerfile: ./server/Dockerfile
container_name: server
env_file: .env
env_file: .env.docker
depends_on:
- traefik
- postgres
@ -57,7 +54,7 @@ services:
context: .
dockerfile: ./client/Dockerfile
container_name: client
env_file: .env
env_file: .env.docker
depends_on:
- traefik
- server

View File

@ -136,30 +136,16 @@ You can get your own key here: https://developers.google.com/fonts/docs/develope
If you do not have a Google API Key, it was make use of the cached response JSON that's stored within the project source. Please note that this cache is not updated and may not have all the latest fonts that Google Fonts has to offer.
## SendGrid
## Mail
The server makes use of SendGrid to send the password reset email to those who have forgotten their password. **This section is completely optional for those who do not require this functionality.**
The server makes use of SMTP to send the password reset email to those who have forgotten their password. **This section is completely optional for those who do not require this functionality.**
### `SENDGRID_API_KEY`
**Required**: `no`
**Description:** SendGrid API Key
Does not require any payment or credit card information to obtain an API key.
You can get your own key here: https://docs.sendgrid.com/ui/account-and-settings/api-keys
### `SENDGRID_FORGOT_PASSWORD_TEMPLATE_ID`
**Required**: `no`
**Description:** Dynamic Template ID for Forgot Password
### `SENDGRID_FROM_NAME`
### `MAIL_FROM_NAME`
**Required**: `no`
**Description:** Sender's Name
### `SENDGRID_FROM_EMAIL`
### `MAIL_FROM_EMAIL`
**Required**: `no`
**Description:** Sender's Email Address

55
pnpm-lock.yaml generated
View File

@ -221,13 +221,13 @@ importers:
'@nestjs/terminus': ^9.1.1
'@nestjs/typeorm': ^9.0.1
'@reactive-resume/schema': workspace:*
'@sendgrid/mail': ^7.7.0
'@types/bcryptjs': ^2.4.2
'@types/cookie-parser': ^1.4.3
'@types/express': ^4.17.13
'@types/lodash': ^4.14.184
'@types/multer': ^1.4.7
'@types/node': ^18.7.9
'@types/nodemailer': ^6.4.5
'@types/passport': ^1.0.10
'@types/passport-jwt': ^3.0.6
'@types/passport-local': ^1.0.34
@ -244,6 +244,7 @@ importers:
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
@ -276,7 +277,6 @@ importers:
'@nestjs/serve-static': 3.0.0_khr6mt6ojlxbw7bo55fknouh34
'@nestjs/terminus': 9.1.1_mr622mjrz7hsw5sxbt7k6brday
'@nestjs/typeorm': 9.0.1_sp4gtzkbiyh24r2ydcb6yqstha
'@sendgrid/mail': 7.7.0
'@types/passport': 1.0.10
bcryptjs: 2.4.3
cache-manager: 4.1.0
@ -291,6 +291,7 @@ importers:
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
@ -312,6 +313,7 @@ importers:
'@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
@ -4696,33 +4698,6 @@ packages:
resolution: {integrity: sha512-WiBSI6JBIhC6LRIsB2Kwh8DsGTlbBU+mLRxJmAe3LjHTdkDpwIbEOZgoXBbZilk/vlfjK8i6nKRAvIRn1XaIMw==}
dev: true
/@sendgrid/client/7.7.0:
resolution: {integrity: sha512-SxH+y8jeAQSnDavrTD0uGDXYIIkFylCo+eDofVmZLQ0f862nnqbC3Vd1ej6b7Le7lboyzQF6F7Fodv02rYspuA==}
engines: {node: 6.* || 8.* || >=10.*}
dependencies:
'@sendgrid/helpers': 7.7.0
axios: 0.26.1
transitivePeerDependencies:
- debug
dev: false
/@sendgrid/helpers/7.7.0:
resolution: {integrity: sha512-3AsAxfN3GDBcXoZ/y1mzAAbKzTtUZ5+ZrHOmWQ279AuaFXUNCh9bPnRpN504bgveTqoW+11IzPg3I0WVgDINpw==}
engines: {node: '>= 6.0.0'}
dependencies:
deepmerge: 4.2.2
dev: false
/@sendgrid/mail/7.7.0:
resolution: {integrity: sha512-5+nApPE9wINBvHSUxwOxkkQqM/IAAaBYoP9hw7WwgDNQPxraruVqHizeTitVtKGiqWCKm2mnjh4XGN3fvFLqaw==}
engines: {node: 6.* || 8.* || >=10.*}
dependencies:
'@sendgrid/client': 7.7.0
'@sendgrid/helpers': 7.7.0
transitivePeerDependencies:
- debug
dev: false
/@sideway/address/4.1.4:
resolution: {integrity: sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==}
dependencies:
@ -5101,10 +5076,17 @@ packages:
/@types/node/18.6.2:
resolution: {integrity: sha512-KcfkBq9H4PI6Vpu5B/KoPeuVDAbmi+2mDBqGPGUgoL7yXQtcWGu2vJWmmRkneWK3Rh0nIAX192Aa87AqKHYChQ==}
dev: false
/@types/node/18.7.9:
resolution: {integrity: sha512-0N5Y1XAdcl865nDdjbO0m3T6FdmQ4ijE89/urOHLREyTXbpMWbSafx9y7XIsgWGtwUP2iYTinLyyW3FatAxBLQ==}
/@types/nodemailer/6.4.5:
resolution: {integrity: sha512-zuP3nBRQHI6M2PkXnGGy1Ww4VB+MyYHGgnfV2T+JR9KLkeWqPJuyVUgLpKXuFnA/b7pZaIDFh2sV4759B7jK1g==}
dependencies:
'@types/node': 18.7.9
dev: true
/@types/normalize-package-data/2.4.1:
resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==}
dev: true
@ -5961,14 +5943,6 @@ packages:
- debug
dev: false
/axios/0.26.1:
resolution: {integrity: sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==}
dependencies:
follow-redirects: 1.14.9
transitivePeerDependencies:
- debug
dev: false
/axios/0.27.2:
resolution: {integrity: sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==}
dependencies:
@ -9804,7 +9778,7 @@ packages:
resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==}
engines: {node: '>= 10.13.0'}
dependencies:
'@types/node': 18.6.2
'@types/node': 18.7.9
merge-stream: 2.0.0
supports-color: 8.1.1
@ -11069,6 +11043,11 @@ packages:
engines: {node: '>=0.12.0'}
dev: false
/nodemailer/6.7.8:
resolution: {integrity: sha512-2zaTFGqZixVmTxpJRCFC+Vk5eGRd/fYtvIR+dl5u9QXLTQWGIf48x/JXvo58g9sa0bU6To04XUv554Paykum3g==}
engines: {node: '>=6.0.0'}
dev: false
/normalize-package-data/2.5.0:
resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==}
dependencies:

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