Skip to content

Commit ae441ea

Browse files
committed
feat: add lib smtp
1 parent fc3d564 commit ae441ea

File tree

4 files changed

+156
-0
lines changed

4 files changed

+156
-0
lines changed

src/config/smtp.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import Nodemailer from '~/lib/smtp/nodemailer'
2+
import { env } from './env'
3+
4+
export const smtp = new Nodemailer({
5+
transporter: {
6+
host: env.MAIL_HOST,
7+
port: env.MAIL_PORT,
8+
secure: env.MAIL_ENCRYPTION === 'ssl',
9+
auth: {
10+
user: env.MAIL_USERNAME,
11+
pass: env.MAIL_PASSWORD,
12+
},
13+
},
14+
defaults: {
15+
from: env.MAIL_FROM,
16+
},
17+
})

src/lib/smtp/nodemailer.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { green } from 'colorette'
2+
import nodemailer from 'nodemailer'
3+
import SMTPTransport from 'nodemailer/lib/smtp-transport'
4+
import { logger } from '~/config/logger'
5+
import { NodemailerParams } from './types'
6+
7+
export default class Nodemailer {
8+
private _transporter: SMTPTransport | SMTPTransport.Options
9+
private _default: SMTPTransport.Options | undefined
10+
11+
constructor({ transporter, defaults }: NodemailerParams) {
12+
this._transporter = transporter
13+
this._default = defaults
14+
}
15+
16+
/**
17+
* Creates a nodemailer client
18+
* @returns nodemailer client
19+
*/
20+
private _client() {
21+
return nodemailer.createTransport(this._transporter, this._default)
22+
}
23+
24+
/**
25+
* Initializes the nodemailer client
26+
* @returns nodemailer client
27+
*/
28+
async initialize() {
29+
const transporter = this._client()
30+
const msgType = `${green('nodemailer')}`
31+
32+
try {
33+
const isValid = await transporter.verify()
34+
if (!isValid) {
35+
logger.error(`${msgType} failed to initialize Nodemailer`)
36+
process.exit(1)
37+
}
38+
39+
logger.info(`${msgType} initialized successfully`)
40+
return transporter
41+
} catch (error: any) {
42+
logger.error(`${msgType} failed to initialize: ${error?.message ?? error}`)
43+
process.exit(1)
44+
}
45+
}
46+
47+
/**
48+
* Sends an email
49+
* @param options - options for sending an email
50+
* @returns email info
51+
*/
52+
async send(options: nodemailer.SendMailOptions) {
53+
const transporter = this._client()
54+
const msgType = `${green('nodemailer')}`
55+
56+
try {
57+
const info = await transporter.sendMail(options)
58+
logger.info(`${msgType} mail sent successfully: ${info.messageId}`)
59+
return info
60+
} catch (error: any) {
61+
logger.error(`${msgType} failed to send mail: ${error?.message ?? error}`)
62+
throw error
63+
}
64+
}
65+
}

src/lib/smtp/template/auth.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { green } from 'colorette'
2+
import fs from 'fs'
3+
import Handlebars from 'handlebars'
4+
import path from 'path'
5+
import { env } from '~/config/env'
6+
import { logger } from '~/config/logger'
7+
import { smtp } from '~/config/smtp'
8+
import { readHTMLFile } from '~/lib/fs/read-file'
9+
import ErrorResponse from '~/lib/http/errors'
10+
import { currentDir } from '~/lib/string'
11+
12+
/**
13+
* Returns the path to the email template
14+
* @param htmlPath - the path to the email template
15+
* @returns the path to the email template
16+
*/
17+
function _emailTemplatePath(htmlPath: string) {
18+
const _path = path.resolve(`${currentDir}/public/email-template/${htmlPath}`)
19+
20+
const msgType = green('email template')
21+
logger.info(`${msgType} - ${_path} exists`)
22+
23+
return _path
24+
}
25+
26+
/**
27+
* Sends an email
28+
* @param _path - the path to the email template
29+
* @param data - the data to be sent
30+
* @returns the email template
31+
*/
32+
async function _sendMail(_path: string, data: any) {
33+
if (!fs.existsSync(_path)) {
34+
throw new ErrorResponse.BadRequest('invalid template path ')
35+
}
36+
37+
const html = await readHTMLFile(_path)
38+
const template = Handlebars.compile(html)
39+
const htmlToSend = template(data)
40+
41+
await smtp.send({
42+
to: data.email,
43+
subject: data.subject,
44+
text: data.text,
45+
html: htmlToSend,
46+
})
47+
}
48+
49+
type SendEmailRegistrationParams = {
50+
fullname: string
51+
email: string
52+
url_token: string
53+
}
54+
55+
/**
56+
* Sends an email to the user
57+
* @param values - the data to be sent
58+
*/
59+
export async function SendEmailRegistration(values: SendEmailRegistrationParams) {
60+
const _path = _emailTemplatePath('register.html')
61+
62+
const { fullname, url_token } = values
63+
const subject = `${fullname}, Thank you for registering on the ${env.APP_NAME} App`
64+
const text = `Please click the link below to verify your email: ${env.APP_URL}/verify/${url_token}`
65+
66+
const data = { ...values, subject, text, APP_NAME: env.APP_NAME }
67+
await _sendMail(_path, data)
68+
}

src/lib/smtp/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import SMTPTransport from 'nodemailer/lib/smtp-transport'
2+
3+
export type NodemailerParams = {
4+
transporter: SMTPTransport | SMTPTransport.Options
5+
defaults?: SMTPTransport.Options
6+
}

0 commit comments

Comments
 (0)