mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2026-07-11 05:24:59 +10:00
use nodemailer/smtp instead of sendgrid
This commit is contained in:
+6
-4
@@ -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=
|
||||
|
||||
+3
-6
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Generated
+17
-38
@@ -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:
|
||||
|
||||
+2
-1
@@ -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,
|
||||
}),
|
||||
],
|
||||
|
||||
@@ -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,
|
||||
}));
|
||||
@@ -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