Skip to content

Commit 75114d4

Browse files
committed
Fix email sending
1 parent baa56bf commit 75114d4

File tree

6 files changed

+126
-88
lines changed

6 files changed

+126
-88
lines changed

course-matrix/backend/package-lock.json

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

course-matrix/backend/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"@supabase/supabase-js": "^2.48.1",
2222
"@types/express": "^5.0.0",
2323
"ai": "^4.1.45",
24+
"axios": "^1.8.4",
2425
"cookie-parser": "^1.4.7",
2526
"cors": "^2.8.5",
2627
"csv-parser": "^3.2.0",

course-matrix/backend/src/config/setupEmailNotificationService.ts

Lines changed: 0 additions & 8 deletions
This file was deleted.

course-matrix/backend/src/constants/constants.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,10 @@ export const yearToCode = (year: number) => {
3939

4040
// true - notifications will be tested by mocking current Date
4141
// false - normal application behavior
42-
export const TEST_NOTIFICATIONS = true;
42+
export const TEST_NOTIFICATIONS = false;
43+
// Mock the current date
44+
// Note: month index in date constructor is 0 indexed (0 - 11)
45+
export const TEST_DATE_NOW = new Date(2025, 4, 14, 8, 45, 1);
4346

4447
// Set minimum results wanted for a similarity search on the associated namespace.
4548
export const namespaceToMinResults = new Map();

course-matrix/backend/src/index.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,9 @@ app.use("/api/offerings", offeringsRouter);
4343
app.use("/api/timetables", timetableRouter);
4444
app.use("/api/ai", aiRouter);
4545

46-
cron.schedule('* * * * *', checkAndNotifyEvents);
46+
// Initialize cron job
47+
// Note: For testing purposes can set first argument to '*/15 * * * * *' to run every 15s
48+
cron.schedule("* * * * *", checkAndNotifyEvents);
4749

4850
/**
4951
* Root route to test the backend server.
@@ -54,8 +56,8 @@ app.get(
5456
asyncHandler(async (_, response) =>
5557
response.json({
5658
info: "Testing course matrix backend server",
57-
}),
58-
),
59+
})
60+
)
5961
);
6062

6163
/**
@@ -72,7 +74,7 @@ app.get(
7274
} catch (err) {
7375
return res.status(500).send({ err });
7476
}
75-
}),
77+
})
7678
);
7779

7880
server = app.listen(config.PORT, () => {

course-matrix/backend/src/services/emailNotificationService.ts

Lines changed: 99 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,44 @@
1-
import {
2-
brevoApiInstance,
3-
brevoSendSmtpEmail,
4-
} from "../config/setupEmailNotificationService";
5-
import { TEST_NOTIFICATIONS } from "../constants/constants";
1+
import { TEST_DATE_NOW, TEST_NOTIFICATIONS } from "../constants/constants";
62
import { supabaseServersideClient } from "../db/setupDb";
73
import { isDateBetween } from "../utils/compareDates";
4+
import axios from "axios";
5+
6+
type EmaiLData = {
7+
sender: {
8+
email: string;
9+
name: string;
10+
};
11+
to: {
12+
email: string;
13+
name: string;
14+
}[];
15+
subject: string;
16+
htmlContent: string;
17+
};
18+
19+
// Create a function to send emails via Brevo API
20+
async function sendBrevoEmail(emailData: EmaiLData) {
21+
try {
22+
const response = await axios({
23+
method: "post",
24+
url: "https://api.brevo.com/v3/smtp/email",
25+
headers: {
26+
accept: "application/json",
27+
"Api-key": process.env.BREVO_API_KEY!,
28+
"content-type": "application/json",
29+
},
30+
data: emailData,
31+
});
32+
33+
return response?.data;
34+
} catch (error: any) {
35+
console.error(
36+
"Error sending email:",
37+
error?.response ? error?.response?.data : error?.message
38+
);
39+
throw error;
40+
}
41+
}
842

943
// Ensure offering is in current semester and current day of week
1044
export function correctDay(offering: any): boolean {
@@ -14,30 +48,37 @@ export function correctDay(offering: any): boolean {
1448

1549
if (!semester || !day) return false;
1650

17-
const now = new Date();
51+
let now;
52+
if (TEST_NOTIFICATIONS) {
53+
now = TEST_DATE_NOW;
54+
} else {
55+
now = new Date();
56+
}
1857

1958
let startDay;
2059
let endDay;
2160

61+
// console.log(offering)
62+
2263
if (semester === "Summer 2025") {
23-
startDay = new Date(2025, 5, 2);
24-
endDay = new Date(2025, 8, 7);
64+
startDay = new Date(2025, 4, 2);
65+
endDay = new Date(2025, 7, 7);
2566
} else if (semester === "Fall 2025") {
26-
startDay = new Date(2025, 9, 3);
27-
endDay = new Date(2025, 12, 3);
67+
startDay = new Date(2025, 8, 3);
68+
endDay = new Date(2025, 11, 3);
2869
} else {
2970
// Winter 2026
30-
startDay = new Date(2026, 1, 6);
31-
endDay = new Date(2026, 4, 4);
71+
startDay = new Date(2026, 0, 6);
72+
endDay = new Date(2026, 3, 4);
3273
}
3374

3475
if (!isDateBetween(now, startDay, endDay)) {
35-
console.log(`${now.toDateString()} is not between ${startDay.toDateString()} and ${endDay.toDateString()}`)
76+
// console.log(`${now.toDateString()} is not between ${startDay.toDateString()} and ${endDay.toDateString()}`)
3677
return false;
3778
}
3879

3980
if (weekdays[now.getDay()] !== day) {
40-
console.log(`${weekdays[now.getDay()]} is not equal to ${day}`)
81+
// console.log(`${weekdays[now.getDay()]} is not equal to ${day}`)
4182
return false;
4283
}
4384

@@ -49,9 +90,8 @@ export async function checkAndNotifyEvents() {
4990
console.log("Checking for upcoming events...");
5091
let now;
5192
if (TEST_NOTIFICATIONS) {
52-
now = new Date(2025, 7, 5, 18, 45, 1);
53-
}
54-
else {
93+
now = TEST_DATE_NOW;
94+
} else {
5595
now = new Date();
5696
}
5797

@@ -68,7 +108,7 @@ export async function checkAndNotifyEvents() {
68108
.from("course_events")
69109
.select("*")
70110
.gte("event_start", formattedStartTime)
71-
.lte("event_end", formattedEndTime);
111+
.lte("event_start", formattedEndTime);
72112

73113
if (error) {
74114
console.error("Error fetching events:", error);
@@ -80,20 +120,21 @@ export async function checkAndNotifyEvents() {
80120
// Send email notifications for each event
81121
for (const event of events) {
82122
// Get offering
83-
const { data: offerings, error: errorOffering } = await supabaseServersideClient
84-
.schema("course")
85-
.from("offerings")
86-
.select("*")
87-
.eq("id", event.offering_id)
88-
.limit(1);
123+
const { data: offerings, error: errorOffering } =
124+
await supabaseServersideClient
125+
.schema("course")
126+
.from("offerings")
127+
.select("*")
128+
.eq("id", event.offering_id)
129+
.limit(1);
89130

90131
if (errorOffering) {
91-
console.error("Error fetching user: ", errorOffering);
132+
console.error("Error fetching offering: ", errorOffering);
92133
return;
93134
}
94135

95136
if (!offerings || offerings.length === 0) {
96-
console.error("Error fetching offeirng: ", errorOffering);
137+
console.error("Offering not found id:", event.offering_id);
97138
return;
98139
}
99140

@@ -103,59 +144,49 @@ export async function checkAndNotifyEvents() {
103144
}
104145

105146
// Get user info
106-
const { data: users, error } = await supabaseServersideClient
107-
.schema("auth")
108-
.from("users")
109-
.select("*")
110-
.eq("id", event.user_id)
111-
.limit(1);
147+
const { data: userData, error } =
148+
await supabaseServersideClient.auth.admin.getUserById(event.user_id);
112149

113150
if (error) {
114151
console.error("Error fetching user: ", error);
115152
return;
116153
}
117154

118-
if (!users || users.length === 0) {
119-
console.error("User not found");
155+
if (!userData) {
156+
console.error("User not found id:", event.user_id);
120157
return;
121158
}
122159

123-
const user = users[0];
160+
const user = userData?.user;
124161
const userEmail = user?.email;
125-
const userName = user?.raw_user_meta_data?.username;
126-
127-
// Prepare email content
128-
brevoSendSmtpEmail.to = [{ email: userEmail, name: userName }];
129-
brevoSendSmtpEmail.sender = {
130-
email: process.env.SENDER_EMAIL,
131-
name: process.env.SENDER_NAME || "Course Matrix Notifications",
132-
};
133-
brevoSendSmtpEmail.subject = `Reminder: ${event.event_name} starting soon`;
134-
brevoSendSmtpEmail.htmlContent = `
135-
<h2>Event Reminder</h2>
136-
<p>Hello ${userName},</p>
137-
<p>Your event "${
138-
event.event_name
139-
}" is starting in approximately 15 minutes.</p>
140-
<p><strong>Start time:</strong> ${new Date(
141-
event.event_start
142-
).toLocaleString()}</p>
143-
<p><strong>Description:</strong> ${
144-
event.event_description || "No description provided"
145-
}</p>
146-
<p>Thank you for using our calendar service!</p>
147-
`;
148-
149-
// Send email
162+
const userName = user?.user_metadata?.username;
163+
164+
console.log(`Sending email to ${userEmail} for ${event.event_name}`);
165+
150166
try {
151-
const data = await brevoApiInstance.sendTransacEmail(
152-
brevoSendSmtpEmail
153-
);
154-
console.log(
155-
`Email sent to ${userEmail} for event ${event.id}. Message ID: ${data.messageId}`
156-
);
157-
} catch (emailError) {
158-
console.error("Error sending email with Brevo:", emailError);
167+
const email = {
168+
sender: {
169+
email: process.env.SENDER_EMAIL!,
170+
name: process.env.SENDER_NAME || "Course Matrix Notifications",
171+
},
172+
to: [{ email: userEmail!, name: userName }],
173+
subject: `Reminder: ${event.event_name} starting soon`,
174+
htmlContent: `
175+
<h2>Event Reminder</h2>
176+
<p>Hello ${userName},</p>
177+
<p>Your event "${event.event_name}" is starting soon</p>
178+
<p><strong>Start time:</strong> ${event.event_start}</p>
179+
<p><strong>Description:</strong> ${
180+
event.event_description || "No description provided"
181+
}</p>
182+
<p>Thank you for using our calendar service!</p>
183+
`,
184+
};
185+
186+
const result = await sendBrevoEmail(email);
187+
console.log("Email sent successfully:", result);
188+
} catch (error) {
189+
console.error("Failed to send email:", error);
159190
}
160191
}
161192
} catch (err) {

0 commit comments

Comments
 (0)