Skip to content

Commit 82fb0f1

Browse files
committed
added email service using Nodemailer
1 parent 2a3df43 commit 82fb0f1

File tree

10 files changed

+190
-3
lines changed

10 files changed

+190
-3
lines changed

backend/.env.example

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
MAILER_USER=your-email@gmail.com
2+
MAILER_REFRESH_TOKEN=your-refresh-token
3+
MAILER_CLIENT_SECRET=your-client-secret
4+
MAILER_CLIENT_ID=your-client-id
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import nodemailerConfig from '../nodemailer.config';
2+
import EmailService from '../services/implementations/emailService';
3+
import IEmailService from '../services/interfaces/emailService';
4+
5+
// Initialize the email service with optional display name
6+
const emailService: IEmailService = new EmailService(
7+
nodemailerConfig,
8+
'My App Name' // Optional: Display name that appears in the "From" field
9+
);
10+
11+
// Example 1: Send a welcome email
12+
async function sendWelcomeEmail(userEmail: string, userName: string) {
13+
const subject = 'Welcome to Our App!';
14+
const htmlBody = `
15+
<h1>Welcome, ${userName}!</h1>
16+
<p>Thank you for joining our application.</p>
17+
<p>We're excited to have you on board.</p>
18+
<br>
19+
<p>Best regards,<br>The Team</p>
20+
`;
21+
22+
try {
23+
await emailService.sendEmail(userEmail, subject, htmlBody);
24+
console.log(`Welcome email sent successfully to ${userEmail}`);
25+
} catch (error) {
26+
console.error(`Failed to send welcome email: ${error}`);
27+
}
28+
}
29+
30+
// Example 2: Send a password reset email
31+
async function sendPasswordResetEmail(userEmail: string, resetLink: string) {
32+
const subject = 'Password Reset Request';
33+
const htmlBody = `
34+
<h2>Password Reset</h2>
35+
<p>We received a request to reset your password.</p>
36+
<p>Click the link below to reset your password:</p>
37+
<br>
38+
<a href="${resetLink}" style="
39+
background-color: #4CAF50;
40+
color: white;
41+
padding: 14px 20px;
42+
text-decoration: none;
43+
border-radius: 4px;
44+
display: inline-block;
45+
">Reset Password</a>
46+
<br><br>
47+
<p><strong>This link will expire in 1 hour.</strong></p>
48+
<p>If you didn't request this, please ignore this email.</p>
49+
`;
50+
51+
try {
52+
await emailService.sendEmail(userEmail, subject, htmlBody);
53+
console.log(`Password reset email sent successfully to ${userEmail}`);
54+
} catch (error) {
55+
console.error(`Failed to send password reset email: ${error}`);
56+
}
57+
}
58+
59+
// Example 3: Send a notification email
60+
async function sendNotificationEmail(
61+
userEmail: string,
62+
notificationTitle: string,
63+
notificationMessage: string
64+
) {
65+
const subject = notificationTitle;
66+
const htmlBody = `
67+
<h2>${notificationTitle}</h2>
68+
<p>${notificationMessage}</p>
69+
<br>
70+
<p>Best regards,<br>The Team</p>
71+
`;
72+
73+
try {
74+
await emailService.sendEmail(userEmail, subject, htmlBody);
75+
console.log(`Notification email sent successfully to ${userEmail}`);
76+
} catch (error) {
77+
console.error(`Failed to send notification email: ${error}`);
78+
}
79+
}
80+
81+
// Example usage (uncomment to test):
82+
// sendWelcomeEmail("user@example.com", "John Doe");
83+
// sendPasswordResetEmail("user@example.com", "https://yourapp.com/reset?token=abc123");
84+
// sendNotificationEmail("user@example.com", "New Update", "Check out our new features!");
85+
86+
export { sendWelcomeEmail, sendPasswordResetEmail, sendNotificationEmail };

backend/nodemailer.config.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { NodemailerConfig } from './types';
2+
3+
const config: NodemailerConfig = {
4+
service: 'gmail',
5+
auth: {
6+
type: 'OAuth2',
7+
user: process.env.MAILER_USER ?? '',
8+
clientId: process.env.MAILER_CLIENT_ID ?? '',
9+
clientSecret: process.env.MAILER_CLIENT_SECRET ?? '',
10+
refreshToken: process.env.MAILER_REFRESH_TOKEN ?? '',
11+
},
12+
};
13+
14+
export default config;

backend/package-lock.json

Lines changed: 21 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

backend/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"apollo-server-express": "^3.13.0",
1616
"dotenv": "^17.2.3",
1717
"express": "^4.18.2",
18+
"nodemailer": "^8.0.1",
1819
"pg": "^8.18.0",
1920
"sequelize": "^6.37.7",
2021
"sequelize-typescript": "^2.1.6",
@@ -25,6 +26,7 @@
2526
"@types/dotenv": "^6.1.1",
2627
"@types/express": "^5.0.6",
2728
"@types/node": "^25.1.0",
29+
"@types/nodemailer": "^7.0.9",
2830
"@types/pg": "^8.16.0",
2931
"@types/umzug": "^2.3.9",
3032
"@typescript-eslint/eslint-plugin": "^8.54.0",
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import nodemailer, { Transporter } from 'nodemailer';
2+
import IEmailService from '../interfaces/emailService';
3+
import { NodemailerConfig } from '../../types';
4+
5+
class EmailService implements IEmailService {
6+
transporter: Transporter;
7+
8+
sender: string;
9+
10+
constructor(nodemailerConfig: NodemailerConfig, displayName?: string) {
11+
this.transporter = nodemailer.createTransport(nodemailerConfig);
12+
if (displayName) {
13+
this.sender = `${displayName} <${nodemailerConfig.auth.user}>`;
14+
} else {
15+
this.sender = nodemailerConfig.auth.user;
16+
}
17+
}
18+
19+
async sendEmail(to: string, subject: string, htmlBody: string): Promise<void> {
20+
const mailOptions = {
21+
from: this.sender,
22+
to,
23+
subject,
24+
html: htmlBody,
25+
};
26+
27+
try {
28+
await this.transporter.sendMail(mailOptions);
29+
} catch (error: unknown) {
30+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
31+
console.error(`Failed to send email. Reason = ${errorMessage}`);
32+
throw error;
33+
}
34+
}
35+
}
36+
37+
export default EmailService;
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
interface IEmailService {
2+
/**
3+
* Send email
4+
* @param to recipient's email
5+
* @param subject email subject
6+
* @param htmlBody email body as html
7+
* @throws Error if email was not sent successfully
8+
*/
9+
sendEmail(to: string, subject: string, htmlBody: string): Promise<void>;
10+
}
11+
12+
export default IEmailService;

backend/src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
import dotenv from 'dotenv';
12
import express from 'express';
23

4+
dotenv.config();
5+
36
const app = express();
47
const PORT = process.env.PORT || 3000;
58

backend/types.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,14 @@ export type CreateSampleDTO = {
1212
name: string;
1313
description: string;
1414
};
15+
16+
export type NodemailerConfig = {
17+
service: 'gmail';
18+
auth: {
19+
type: 'OAuth2';
20+
user: string;
21+
clientId: string;
22+
clientSecret: string;
23+
refreshToken: string;
24+
};
25+
};

frontend/package-lock.json

Lines changed: 0 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)