diff --git a/README.md b/README.md
index 50d771bb..d4ba572d 100644
--- a/README.md
+++ b/README.md
@@ -76,7 +76,7 @@ Here is what you need to be able to run ClassroomIO.com
This repo is a mono repo that consists of 3 projects:
1. `classroomio-com`: The landing page of ClassroomIO hosted [here](https://classroomio.com)
-2. `backend`: The backend service that handles PDF & video processing.
+2. `backend`: The backend service that handles PDF, video processing, Emailing and Notifications.
3. `dashboard`: The web application that runs the learning management system hosted [here](https://app.classroomio.com).
4. `docs`: Official documentation of ClassroomIO hosted [here](https://classroomio.com/docs)
@@ -122,8 +122,11 @@ This repo is a mono repo that consists of 3 projects:
4. Set up your `.env` file
- - Go to `apps/classroomio-com` and `apps/dashboard`
+ - Go to `apps/classroomio-com`, `apps/dashboard` and `apps/backend`.
- Duplicate the `.env.example` file and rename it to `.env`
+ - Populate your .env files with the neccessary variables
+
+To get the environmental variables for supabase continue to step(5)
5. Setup Supabase.
diff --git a/apps/backend/.env.example b/apps/backend/.env.example
index 7a4c6e88..f6dd937d 100644
--- a/apps/backend/.env.example
+++ b/apps/backend/.env.example
@@ -13,3 +13,10 @@ CLOUDFLARE_PUBLIC_ACCOUNT_ID=
MUSE_API_KEY=
REDIS_API_KEY=
TELEGRAM_BOT_TOKEN=
+
+SMTP_HOST=
+SMTP_USER=
+SMTP_PASSWORD=
+SMTP_USER_NOTIFY=
+SMTP_PASSWORD_NOTIFY=
+SMTP_PORT=
diff --git a/apps/backend/package.json b/apps/backend/package.json
index 154c5848..b56be5fd 100644
--- a/apps/backend/package.json
+++ b/apps/backend/package.json
@@ -22,6 +22,7 @@
"@aws-sdk/client-s3": "^3.388.0",
"@aws-sdk/lib-storage": "^3.535.0",
"@supabase/supabase-js": "^2.32.0",
+ "@types/nodemailer": "^6.4.11",
"axios": "^1.4.0",
"body-parser": "^1.20.2",
"bull": "^4.11.3",
@@ -33,6 +34,7 @@
"katex": "^0.16.10",
"marked": "^7.0.3",
"multer": "^1.4.3",
+ "nodemailer": "^6.9.5",
"nodemon": "^3.0.1",
"puppeteer": "^20.9.0",
"zod": "^3.21.4"
diff --git a/apps/backend/server.js b/apps/backend/server.js
index 90886b4b..0779d930 100644
--- a/apps/backend/server.js
+++ b/apps/backend/server.js
@@ -10,6 +10,7 @@ const downloadCertificate = require('./src/routes/downloadCertificate');
const downloadLesson = require('./src/routes/downloadLesson');
const downloadCourse = require('./src/routes/downloadCourse');
const katex = require('./src/routes/katex');
+const sendEmail = require('./src/routes/sendEmail');
// Express server
const app = express();
@@ -34,6 +35,7 @@ app.use('/downloadLesson', downloadLesson);
app.use('/downloadCourse', downloadCourse);
app.use('/uploadVideo', uploadVideo);
app.use('/katex', katex);
+app.use('/sendEmail', sendEmail);
app.get('/', (req, res) => {
res.send('Welcome to ClassroomIO');
diff --git a/apps/backend/src/routes/sendEmail.js b/apps/backend/src/routes/sendEmail.js
new file mode 100644
index 00000000..99ba59be
--- /dev/null
+++ b/apps/backend/src/routes/sendEmail.js
@@ -0,0 +1,59 @@
+const express = require('express');
+const zod = require('zod');
+const { getTransporter } = require('../utils/getTransporter');
+const { withEmailTemplate } = require('../utils/withEmailTemplate');
+
+const router = express.Router();
+
+router.post('/', async (req, res) => {
+ try {
+ const mySchema = zod.array(
+ zod.object({
+ from: zod.string().optional(),
+ to: zod.string(),
+ subject: zod.string(),
+ content: zod.string(),
+ isPersonalEmail: zod.boolean().optional(),
+ replyTo: zod.string().optional()
+ })
+ );
+
+ mySchema.parse(req.body);
+
+ const emailDataArray = req.body;
+
+ Promise.all(
+ emailDataArray.map(async (emailData) => {
+ try {
+ const { from, to, subject, content, isPersonalEmail, replyTo } = emailData;
+
+ const transporter = await getTransporter(isPersonalEmail);
+
+ if (!transporter) {
+ return;
+ }
+
+ const res = await transporter.sendMail({
+ from: from || '"Best from ClassroomIO" ',
+ to,
+ subject,
+ replyTo,
+ html: withEmailTemplate(content)
+ });
+
+ console.log('Email status', res);
+ } catch (error) {
+ console.error('Error sending email', error);
+ }
+ })
+ );
+
+ console.log('Email sent');
+ return res.json({ success: true });
+ } catch (error) {
+ console.error('Error sending emails:', error);
+ return res.status(500).json({ error: 'Internal Server Error' });
+ }
+});
+
+module.exports = router;
diff --git a/apps/backend/src/utils/getTransporter.js b/apps/backend/src/utils/getTransporter.js
new file mode 100644
index 00000000..60904d44
--- /dev/null
+++ b/apps/backend/src/utils/getTransporter.js
@@ -0,0 +1,37 @@
+const nodemailer = require('nodemailer');
+
+const { SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASSWORD, SMTP_USER_NOTIFY, SMTP_PASSWORD_NOTIFY } =
+ process.env;
+
+const getTransporter = async (isPersonal = false) => {
+ if (
+ !SMTP_HOST ||
+ !SMTP_PORT ||
+ !SMTP_USER ||
+ !SMTP_PASSWORD ||
+ !SMTP_USER_NOTIFY ||
+ !SMTP_PASSWORD_NOTIFY
+ ) {
+ console.error('SMTP configuration missing');
+ return null;
+ }
+
+ try {
+ const transporter = nodemailer.createTransport({
+ host: SMTP_HOST,
+ port: parseInt(SMTP_PORT || '465', 10),
+ secure: true,
+ auth: {
+ user: isPersonal ? SMTP_USER : SMTP_USER_NOTIFY,
+ pass: isPersonal ? SMTP_PASSWORD : SMTP_PASSWORD_NOTIFY
+ }
+ });
+ await transporter.verify();
+ return transporter;
+ } catch (error) {
+ console.error('Transporter error:', error);
+ return null;
+ }
+};
+
+module.exports = { getTransporter };
diff --git a/apps/backend/src/utils/withEmailTemplate.js b/apps/backend/src/utils/withEmailTemplate.js
new file mode 100644
index 00000000..796cf6a0
--- /dev/null
+++ b/apps/backend/src/utils/withEmailTemplate.js
@@ -0,0 +1,220 @@
+const withEmailTemplate = (content) =>
+ `
+
+
+
+
+
+
+
+
+
+
+
+ 
+
+
+ ${content}
+
+
+
+
+
+`;
+module.exports = { withEmailTemplate };
diff --git a/apps/dashboard/.env.example b/apps/dashboard/.env.example
index b7e25ca7..8603f3ac 100644
--- a/apps/dashboard/.env.example
+++ b/apps/dashboard/.env.example
@@ -4,13 +4,6 @@ PUBLIC_SUPABASE_URL=http://localhost:54321
PUBLIC_SUPABASE_ANON_KEY=
PRIVATE_SUPABASE_SERVICE_ROLE=
-# Email setup - Optional if you want to get/send emails
-SMTP_PASSWORD_NOTIFY=
-SMTP_USER_NOTIFY=
-SMTP_HOST=
-SMTP_PORT=
-SMTP_USER=
-SMTP_PASSWORD=
# Unsplash setup - Optional if you want to use Unsplash images
UNSPLASH_API_KEY=
diff --git a/apps/dashboard/src/defer/sendEmail.ts b/apps/dashboard/src/defer/sendEmail.ts
deleted file mode 100644
index 4888ca92..00000000
--- a/apps/dashboard/src/defer/sendEmail.ts
+++ /dev/null
@@ -1,46 +0,0 @@
-import { defer } from '@defer/client';
-
-import { withEmailTemplate } from '../lib/utils/services/notification/template';
-import { getTransporter } from '../lib/utils/services/notification/send';
-
-const sendEmail = async ({
- from,
- to,
- subject,
- content,
- isPersonalEmail,
- replyTo
-}: {
- from?: string;
- to: string;
- subject: string;
- content: string;
- isPersonalEmail?: boolean;
- replyTo?: string;
-}) => {
- try {
- const transporter = await getTransporter(isPersonalEmail);
-
- if (!transporter) {
- return;
- }
-
- const info = await transporter.sendMail({
- from: from || '"Best from ClassroomIO" ', // sender address
- to, // list of receivers
- subject, // Subject line
- replyTo,
- html: withEmailTemplate(content) // html body
- });
-
- console.log('Message sent: %s', info.messageId);
-
- return info;
- } catch (error) {
- console.error({ error });
-
- return error;
- }
-};
-
-export default defer(sendEmail);
diff --git a/apps/dashboard/src/defer/sendEmails.ts b/apps/dashboard/src/defer/sendEmails.ts
deleted file mode 100644
index 5c499977..00000000
--- a/apps/dashboard/src/defer/sendEmails.ts
+++ /dev/null
@@ -1,42 +0,0 @@
-import { defer } from '@defer/client';
-
-import { withEmailTemplate } from '../lib/utils/services/notification/template';
-import { getTransporter } from '../lib/utils/services/notification/send';
-
-// Send multiple emails
-const sendEmails = async (
- emailParams: {
- from?: string;
- to: string;
- subject: string;
- content: string;
- isPersonalEmail?: boolean;
- replyTo?: string;
- }[]
-) => {
- for (const emailParam of emailParams) {
- const { from, to, subject, content, isPersonalEmail, replyTo } = emailParam;
-
- try {
- const transporter = await getTransporter(isPersonalEmail);
-
- if (!transporter) {
- return;
- }
-
- const info = await transporter.sendMail({
- from: from || '"Best from ClassroomIO" ', // sender address
- to, // list of receivers
- subject, // Subject line
- replyTo,
- html: withEmailTemplate(content) // html body
- });
-
- console.log('Message sent: %s', info.messageId);
- } catch (error) {
- console.error({ error });
- }
- }
-};
-
-export default defer(sendEmails);
diff --git a/apps/dashboard/src/lib/components/Course/components/Lesson/Exercise/ViewMode.svelte b/apps/dashboard/src/lib/components/Course/components/Lesson/Exercise/ViewMode.svelte
index d8dd90f0..a993c055 100644
--- a/apps/dashboard/src/lib/components/Course/components/Lesson/Exercise/ViewMode.svelte
+++ b/apps/dashboard/src/lib/components/Course/components/Lesson/Exercise/ViewMode.svelte
@@ -25,7 +25,6 @@
triggerSendEmail
} from '$lib/utils/services/notification/notification';
import { lesson } from '../store/lessons';
- import { onMount } from 'svelte';
import { browser } from '$app/environment';
export let preview: false;
diff --git a/apps/dashboard/src/nodemailer/sendEmail.ts b/apps/dashboard/src/nodemailer/sendEmail.ts
new file mode 100644
index 00000000..ce4e87ab
--- /dev/null
+++ b/apps/dashboard/src/nodemailer/sendEmail.ts
@@ -0,0 +1,35 @@
+import { PUBLIC_SERVER_URL } from '$env/static/public';
+
+const sendEmail = async (
+ emailDataArray: {
+ from?: string;
+ to: any;
+ subject: string;
+ content: any;
+ replyTo?: any;
+ isPersonalEmail?: boolean;
+ }[]
+) => {
+ try {
+ const response = await fetch(`${PUBLIC_SERVER_URL}/sendEmail`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify(emailDataArray)
+ });
+
+ if (!response.ok) {
+ console.log('Failed to send emails');
+ return;
+ }
+
+ const responseData = await response.json();
+ console.log('Emails sent successfully:', responseData);
+ return responseData;
+ } catch (error) {
+ console.error('Error sending emails:', error);
+ return;
+ }
+};
+export default sendEmail;
diff --git a/apps/dashboard/src/routes/api/email/course/exercise_submission_update/+server.js b/apps/dashboard/src/routes/api/email/course/exercise_submission_update/+server.js
index 2539fed1..5a74a337 100644
--- a/apps/dashboard/src/routes/api/email/course/exercise_submission_update/+server.js
+++ b/apps/dashboard/src/routes/api/email/course/exercise_submission_update/+server.js
@@ -1,6 +1,6 @@
import { json } from '@sveltejs/kit';
import { getSupabase } from '$lib/utils/functions/supabase';
-import sendEmail from '$defer/sendEmail';
+import sendEmail from '$nodemailer/sendEmail';
const supabase = getSupabase();
@@ -24,12 +24,16 @@ export async function POST({ request }) {
return json({ success: false, message: 'Unauthenticated user' }, { status: 401 });
}
- await sendEmail({
- from: `"${orgName} (via ClassroomIO.com)" `,
- to,
- subject: `[Submitted]: ${exerciseTitle}`,
- content
- });
+ const emailData = [
+ {
+ from: `"${orgName} (via ClassroomIO.com)" `,
+ to,
+ subject: `[Submitted]: ${exerciseTitle}`,
+ content
+ }
+ ];
+
+ await sendEmail(emailData);
return json({
success: true,
diff --git a/apps/dashboard/src/routes/api/email/course/newsfeed/+server.ts b/apps/dashboard/src/routes/api/email/course/newsfeed/+server.ts
index ab3ec704..b46a40c3 100644
--- a/apps/dashboard/src/routes/api/email/course/newsfeed/+server.ts
+++ b/apps/dashboard/src/routes/api/email/course/newsfeed/+server.ts
@@ -1,8 +1,7 @@
import { json } from '@sveltejs/kit';
import { getSupabase } from '$lib/utils/functions/supabase';
import { getFeedForNotification } from '$lib/utils/services/newsfeed/index';
-import sendEmail from '$defer/sendEmail';
-import sendEmails from '$defer/sendEmails';
+import sendEmail from '$nodemailer/sendEmail';
const supabase = getSupabase();
@@ -13,20 +12,25 @@ const sendEmailNotification = async (feedId: string, authorId: string, comment?:
const postLink = `https://${feed.org.siteName}.classroomio.com/courses/${feed.courseId}?feedId=${feed.id}`;
if (comment) {
- // send only to teacher
- await sendEmail({
- from: `"${feed.org.name} - ClassroomIO" `,
- to: feed.teacherEmail,
- subject: `[${feed.courseTitle}] - News feed comment`,
- content: `
+ const emailData = [
+ {
+ from: `"${feed.org.name} - ClassroomIO" `,
+ to: feed.teacherEmail,
+ subject: `[${feed.courseTitle}] - News feed comment`,
+ content: `
A student left you a comment on your newsfeed post
${comment}
- `
- });
+ `,
+ isPersonalEmail: false,
+ replyTo: 'noreply@classroomio.com'
+ }
+ ];
+
+ await sendEmail(emailData);
// dont continue
return;
@@ -37,7 +41,7 @@ const sendEmailNotification = async (feedId: string, authorId: string, comment?:
}
// else send to everyone except the author of the post
- const emails = feed.courseMembers.map((member) => ({
+ const emailsData = feed.courseMembers.map((member) => ({
from: `"${feed.org.name} - ClassroomIO" `,
to: member.email,
replyTo: feed.teacherEmail,
@@ -53,8 +57,8 @@ const sendEmailNotification = async (feedId: string, authorId: string, comment?:
}));
console.log('Sending emails to all students', feed.courseMembers.length);
- // This is the defer function with a loop
- await sendEmails(emails);
+ // This is the email sending function with a loop
+ await sendEmail(emailsData);
};
export async function POST({ request }) {
diff --git a/apps/dashboard/src/routes/api/email/course/student_prove_payment/+server.js b/apps/dashboard/src/routes/api/email/course/student_prove_payment/+server.js
index 6a8d5591..6ded8a12 100644
--- a/apps/dashboard/src/routes/api/email/course/student_prove_payment/+server.js
+++ b/apps/dashboard/src/routes/api/email/course/student_prove_payment/+server.js
@@ -1,6 +1,6 @@
import { json } from '@sveltejs/kit';
// import { getSupabase } from '$lib/utils/functions/supabase';
-import sendEmail from '$defer/sendEmail';
+import sendEmail from '$nodemailer/sendEmail';
// const supabase = getSupabase();
@@ -32,19 +32,23 @@ export async function POST({ request }) {
// return json({ success: false, message: 'Unauthenticated user' }, { status: 401 });
// }
- await sendEmail({
- from: `"${orgName} - ClassroomIO" `,
- to,
- replyTo: teacherEmail,
- subject: `[${courseName}] One more step left`,
- content: `
+ const emailData = [
+ {
+ from: `"${orgName} - ClassroomIO" `,
+ to,
+ replyTo: teacherEmail,
+ subject: `[${courseName}] One more step left`,
+ content: `
Hi ${studentFullname},
You are one step closer to joining: ${courseName}
Please send your proof of payment to: ${teacherEmail}, in order to join the course.
Talk to you soon and see you in class.
${orgName}
`
- });
+ }
+ ];
+
+ await sendEmail(emailData);
return json({
success: true,
diff --git a/apps/dashboard/src/routes/api/email/course/student_welcome/+server.js b/apps/dashboard/src/routes/api/email/course/student_welcome/+server.js
index 46686be8..acf97622 100644
--- a/apps/dashboard/src/routes/api/email/course/student_welcome/+server.js
+++ b/apps/dashboard/src/routes/api/email/course/student_welcome/+server.js
@@ -1,6 +1,6 @@
import { json } from '@sveltejs/kit';
import { getSupabase } from '$lib/utils/functions/supabase';
-import sendEmail from '$defer/sendEmail';
+import sendEmail from '$nodemailer/sendEmail';
const supabase = getSupabase();
@@ -26,11 +26,12 @@ export async function POST({ request }) {
return json({ success: false, message: 'Unauthenticated user' }, { status: 401 });
}
- await sendEmail({
- from: `"${orgName} (via ClassroomIO.com)" `,
- to,
- subject: `${orgName} - Welcome to Class π`,
- content: `
+ const emailData = [
+ {
+ from: `"${orgName} (via ClassroomIO.com)" `,
+ to,
+ subject: `${orgName} - Welcome to Class π`,
+ content: `
Hi there,
Congratulations π, youβve successfully joined: ${courseName}
Everything has been set up for you to have an amazing learning experience.
@@ -38,7 +39,9 @@ export async function POST({ request }) {
Cheers,
${orgName}
`
- });
+ }
+ ];
+ await sendEmail(emailData);
return json({
success: true,
diff --git a/apps/dashboard/src/routes/api/email/course/submission_update/+server.js b/apps/dashboard/src/routes/api/email/course/submission_update/+server.js
index fa32bbef..e7770e09 100644
--- a/apps/dashboard/src/routes/api/email/course/submission_update/+server.js
+++ b/apps/dashboard/src/routes/api/email/course/submission_update/+server.js
@@ -1,6 +1,6 @@
import { json } from '@sveltejs/kit';
import { getSupabase } from '$lib/utils/functions/supabase';
-import sendEmail from '$defer/sendEmail';
+import sendEmail from '$nodemailer/sendEmail';
const supabase = getSupabase();
@@ -24,12 +24,16 @@ export async function POST({ request }) {
return json({ success: false, message: 'Unauthenticated user' }, { status: 401 });
}
- await sendEmail({
- from: `"${orgName} (via ClassroomIO.com)" `,
- to,
- subject: 'Submission Update',
- content
- });
+ const emailData = [
+ {
+ from: `"${orgName} (via ClassroomIO.com)" `,
+ to,
+ subject: 'Submission Update',
+ content
+ }
+ ];
+
+ await sendEmail(emailData);
return json({
success: true,
diff --git a/apps/dashboard/src/routes/api/email/course/teacher_student_buycourse/+server.js b/apps/dashboard/src/routes/api/email/course/teacher_student_buycourse/+server.js
index ef29935a..28e8b171 100644
--- a/apps/dashboard/src/routes/api/email/course/teacher_student_buycourse/+server.js
+++ b/apps/dashboard/src/routes/api/email/course/teacher_student_buycourse/+server.js
@@ -1,6 +1,6 @@
import { json } from '@sveltejs/kit';
// import { getSupabase } from '$lib/utils/functions/supabase';
-import sendEmail from '$defer/sendEmail';
+import sendEmail from '$nodemailer/sendEmail';
// const supabase = getSupabase();
@@ -31,11 +31,12 @@ export async function POST({ request }) {
// return json({ success: false, message: 'Unauthenticated user' }, { status: 401 });
// }
- await sendEmail({
- from: `ClassroomIO" `,
- to,
- subject: `[${courseName}] Request to Join Course!`,
- content: `
+ const emailData = [
+ {
+ from: `ClassroomIO" `,
+ to,
+ subject: `[${courseName}] Request to Join Course!`,
+ content: `
Hi amazing tutor,
A new student has requested to join a course you are teaching: β${courseName}"
Student details
@@ -44,7 +45,9 @@ export async function POST({ request }) {
Email: ${studentEmail}
`
- });
+ }
+ ];
+ await sendEmail(emailData);
return json({
success: true,
diff --git a/apps/dashboard/src/routes/api/email/course/teacher_student_joined/+server.js b/apps/dashboard/src/routes/api/email/course/teacher_student_joined/+server.js
index 3ac2523f..a57115a5 100644
--- a/apps/dashboard/src/routes/api/email/course/teacher_student_joined/+server.js
+++ b/apps/dashboard/src/routes/api/email/course/teacher_student_joined/+server.js
@@ -1,6 +1,6 @@
import { json } from '@sveltejs/kit';
import { getSupabase } from '$lib/utils/functions/supabase';
-import sendEmail from '$defer/sendEmail';
+import sendEmail from '$nodemailer/sendEmail';
const supabase = getSupabase();
@@ -25,18 +25,22 @@ export async function POST({ request }) {
return json({ success: false, message: 'Unauthenticated user' }, { status: 401 });
}
- await sendEmail({
- from: `"ClassroomIO" `,
- to,
- subject: `[${courseName}] You've got a new student π!`,
- content: `
+ const emailData = [
+ {
+ from: `"ClassroomIO" `,
+ to,
+ subject: `[${courseName}] You've got a new student π!`,
+ content: `
Hi amazing tutor,
Congratulations π, a new student has joined a course you are teaching: ${courseName}
We hope they have a great experience learning from the best (YOU).
If you run into any issues, please donβt fail to reach out to us, weβd love to make your teaching
experience as easy as possible.
`
- });
+ }
+ ];
+
+ await sendEmail(emailData);
return json({
success: true,
diff --git a/apps/dashboard/src/routes/api/email/course/teacher_welcome/+server.js b/apps/dashboard/src/routes/api/email/course/teacher_welcome/+server.js
index 2380dc27..4227fb16 100644
--- a/apps/dashboard/src/routes/api/email/course/teacher_welcome/+server.js
+++ b/apps/dashboard/src/routes/api/email/course/teacher_welcome/+server.js
@@ -1,6 +1,6 @@
import { json } from '@sveltejs/kit';
import { getSupabase } from '$lib/utils/functions/supabase';
-import sendEmail from '$defer/sendEmail';
+import sendEmail from '$nodemailer/sendEmail';
const supabase = getSupabase();
@@ -28,11 +28,12 @@ export async function POST({ request }) {
const origin = request.headers.get('origin');
const inviteLink = `${origin}/org/${orgSiteName}/courses`;
- await sendEmail({
- from: `"${orgName} (via ClassroomIO.com)" `,
- to,
- subject: `You have been invited to a course in ${orgName}!`,
- content: `
+ const emailData = [
+ {
+ from: `"${orgName} (via ClassroomIO.com)" `,
+ to,
+ subject: `You have been invited to a course in ${orgName}!`,
+ content: `
Hey ${name},
You have been given access to teach a course by ${orgName}
The course is titled: ${courseName}
@@ -40,7 +41,10 @@ export async function POST({ request }) {
Open Dashboard
`
- });
+ }
+ ];
+
+ await sendEmail(emailData);
return json({
success: true,
diff --git a/apps/dashboard/src/routes/api/email/invite/+server.js b/apps/dashboard/src/routes/api/email/invite/+server.js
index 38afeb35..4275b801 100644
--- a/apps/dashboard/src/routes/api/email/invite/+server.js
+++ b/apps/dashboard/src/routes/api/email/invite/+server.js
@@ -1,6 +1,6 @@
import { json } from '@sveltejs/kit';
import { getSupabase } from '$lib/utils/functions/supabase';
-import sendEmail from '$defer/sendEmail';
+import sendEmail from '$nodemailer/sendEmail';
const supabase = getSupabase();
@@ -42,18 +42,21 @@ export async function POST({ request }) {
const inviteLink = `${origin}/invite/t/${encodeURIComponent(btoa(inviteData))}`;
console.log('inviteData', inviteData);
- await sendEmail({
- to: email,
- subject: `Join ${name} on ClassroomIO π`,
- content: `
+ const emailData = [
+ {
+ to: email,
+ subject: `Join ${name} on ClassroomIO π`,
+ content: `
Hey there,
You have been invited to join ${name} on ClassroomIO πππ.
`,
- isPersonalEmail: true
- });
+ isPersonalEmail: true
+ }
+ ];
+ await sendEmail(emailData);
return json({
success: true,
diff --git a/apps/dashboard/src/routes/api/email/verify_email/+server.js b/apps/dashboard/src/routes/api/email/verify_email/+server.js
index d82df4ed..0e7d4099 100644
--- a/apps/dashboard/src/routes/api/email/verify_email/+server.js
+++ b/apps/dashboard/src/routes/api/email/verify_email/+server.js
@@ -1,6 +1,6 @@
import { json } from '@sveltejs/kit';
import { getSupabase } from '$lib/utils/functions/supabase';
-import sendEmail from '$defer/sendEmail';
+import sendEmail from '$nodemailer/sendEmail';
const supabase = getSupabase();
@@ -35,18 +35,22 @@ export async function POST({ request }) {
btoa(verificationData)
)}`;
- await sendEmail({
- to,
- subject: '[ClassroomIO]: Verify your email',
- content: `
- Thank you for signing up
- To verify your email, please click the Verify button below
-
- `,
- isPersonalEmail: true
- });
+ const emailData = [
+ {
+ to,
+ subject: '[ClassroomIO]: Verify your email',
+ content: `
+ Thank you for signing up
+ To verify your email, please click the Verify button below
+
+ `,
+ isPersonalEmail: true
+ }
+ ];
+
+ await sendEmail(emailData);
return json({
success: true,
diff --git a/apps/dashboard/src/routes/api/email/welcome/+server.js b/apps/dashboard/src/routes/api/email/welcome/+server.js
index 160811cd..4ad118d8 100644
--- a/apps/dashboard/src/routes/api/email/welcome/+server.js
+++ b/apps/dashboard/src/routes/api/email/welcome/+server.js
@@ -1,6 +1,6 @@
import { json } from '@sveltejs/kit';
import { getSupabase } from '$lib/utils/functions/supabase';
-import sendEmail from '$defer/sendEmail';
+import sendEmail from '$nodemailer/sendEmail';
const supabase = getSupabase();
@@ -25,10 +25,11 @@ export async function POST({ request }) {
return json({ success: false, message: 'Unauthenticated user' }, { status: 401 });
}
- await sendEmail({
- to,
- subject: 'Thank you so so so much for choosing ClassroomIO!',
- content: `
+ const emailData = [
+ {
+ to,
+ subject: 'Thank you so so so much for choosing ClassroomIO!',
+ content: `
Dear ${name},
My name is Best the CEO of ClassroomIO and I will personally like to welcome to the vibrant community of ClassroomIO!
@@ -47,8 +48,10 @@ export async function POST({ request }) {
Go to Dashboard
`,
- isPersonalEmail: true
- });
+ isPersonalEmail: true
+ }
+ ];
+ await sendEmail(emailData);
return json({
success: true,
diff --git a/apps/dashboard/svelte.config.js b/apps/dashboard/svelte.config.js
index c0b2189f..5966c1d0 100644
--- a/apps/dashboard/svelte.config.js
+++ b/apps/dashboard/svelte.config.js
@@ -12,7 +12,7 @@ const config = {
adapter: vercelAdapter(),
alias: {
$lib: path.resolve('./src/lib'),
- $defer: path.resolve('./src/defer')
+ $nodemailer: path.resolve('./src/nodemailer')
}
}
};
diff --git a/apps/dashboard/tsconfig.json b/apps/dashboard/tsconfig.json
index d9ba990f..e7631eb9 100644
--- a/apps/dashboard/tsconfig.json
+++ b/apps/dashboard/tsconfig.json
@@ -16,8 +16,8 @@
"paths": {
"$lib": ["./src/lib"],
"$lib/*": ["./src/lib/*"],
- "$defer": ["./src/defer"],
- "$defer/*": ["./src/defer/*"]
+ "$nodemailer": ["./src/nodemailer"],
+ "$nodemailer/*": ["./src/nodemailer/*"]
}
}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 5dbc5ed4..ef5c466c 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -47,6 +47,9 @@ importers:
'@supabase/supabase-js':
specifier: ^2.32.0
version: 2.39.6
+ '@types/nodemailer':
+ specifier: ^6.4.11
+ version: 6.4.11
axios:
specifier: ^1.4.0
version: 1.6.2
@@ -80,6 +83,9 @@ importers:
multer:
specifier: ^1.4.3
version: 1.4.4
+ nodemailer:
+ specifier: ^6.9.5
+ version: 6.9.5
nodemon:
specifier: ^3.0.1
version: 3.0.3
@@ -158,7 +164,7 @@ importers:
version: 1.13.6(d3-cloud@1.2.7)(d3-sankey@0.12.3)(d3@7.8.5)(svelte@4.1.2)
'@defer/client':
specifier: ^2.2.0
- version: 2.2.0
+ version: 2.3.0
'@lemonsqueezy/lemonsqueezy.js':
specifier: ^2.2.0
version: 2.2.0
@@ -1607,7 +1613,7 @@ packages:
'@babel/traverse': 7.23.4
'@babel/types': 7.23.4
convert-source-map: 1.9.0
- debug: 4.3.4
+ debug: 4.3.4(supports-color@5.5.0)
gensync: 1.0.0-beta.2
json5: 2.2.3
semver: 6.3.1
@@ -1630,7 +1636,7 @@ packages:
'@babel/traverse': 7.23.4
'@babel/types': 7.23.4
convert-source-map: 2.0.0
- debug: 4.3.4
+ debug: 4.3.4(supports-color@5.5.0)
gensync: 1.0.0-beta.2
json5: 2.2.3
semver: 6.3.1
@@ -1787,7 +1793,7 @@ packages:
'@babel/core': 7.22.9
'@babel/helper-compilation-targets': 7.22.15
'@babel/helper-plugin-utils': 7.22.5
- debug: 4.3.4
+ debug: 4.3.4(supports-color@5.5.0)
lodash.debounce: 4.0.8
resolve: 1.22.8
transitivePeerDependencies:
@@ -3102,7 +3108,7 @@ packages:
'@babel/helper-split-export-declaration': 7.22.6
'@babel/parser': 7.23.4
'@babel/types': 7.23.4
- debug: 4.3.4
+ debug: 4.3.4(supports-color@5.5.0)
globals: 11.12.0
transitivePeerDependencies:
- supports-color
@@ -3221,8 +3227,8 @@ packages:
dependencies:
'@jridgewell/trace-mapping': 0.3.9
- /@defer/client@2.2.0:
- resolution: {integrity: sha512-WYHhPr8Tk5t1T5mai80l79lPEsTlt06adTVJc7GP6zpBW28Qy9YJvJlmUV+I0jpMuIRq/UuSOZRJpAGyjJMetg==}
+ /@defer/client@2.3.0:
+ resolution: {integrity: sha512-2pCQrasR7+mR5EeOK1NgmP2sXk8Hao4IRnkRLcO2QqJpNJnUJE177m+RhEhp2SCebdIG6MXW4/NwNE5sz/uL8w==}
engines: {node: '>=18'}
dependencies:
parse-duration: 1.1.0
@@ -5775,7 +5781,7 @@ packages:
vite: ^4.0.0
dependencies:
'@sveltejs/vite-plugin-svelte': 2.5.3(svelte@4.1.2)(vite@4.4.8)
- debug: 4.3.4
+ debug: 4.3.4(supports-color@5.5.0)
svelte: 4.1.2
vite: 4.4.8(@types/node@18.11.10)(sass@1.64.2)
transitivePeerDependencies:
@@ -5789,7 +5795,7 @@ packages:
vite: ^4.0.0
dependencies:
'@sveltejs/vite-plugin-svelte-inspector': 1.0.4(@sveltejs/vite-plugin-svelte@2.5.3)(svelte@4.1.2)(vite@4.4.8)
- debug: 4.3.4
+ debug: 4.3.4(supports-color@5.5.0)
deepmerge: 4.3.1
kleur: 4.1.5
magic-string: 0.30.5
@@ -6089,7 +6095,6 @@ packages:
resolution: {integrity: sha512-Ld2c0frwpGT4VseuoeboCXQ7UJIkK3X7Lx/4YsZEiUHtHsthWAOCYtf6PAiLhMtfwV0cWJRabLBS3+LD8x6Nrw==}
dependencies:
'@types/node': 18.11.10
- dev: true
/@types/normalize-package-data@2.4.4:
resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==}
@@ -6568,7 +6573,7 @@ packages:
resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==}
engines: {node: '>= 6.0.0'}
dependencies:
- debug: 4.3.4
+ debug: 4.3.4(supports-color@5.5.0)
transitivePeerDependencies:
- supports-color
dev: false
@@ -8379,17 +8384,6 @@ packages:
ms: 2.1.3
dev: true
- /debug@4.3.4:
- resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
- engines: {node: '>=6.0'}
- peerDependencies:
- supports-color: '*'
- peerDependenciesMeta:
- supports-color:
- optional: true
- dependencies:
- ms: 2.1.2
-
/debug@4.3.4(supports-color@5.5.0):
resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
engines: {node: '>=6.0'}
@@ -10486,7 +10480,7 @@ packages:
engines: {node: '>= 6'}
dependencies:
agent-base: 6.0.2
- debug: 4.3.4
+ debug: 4.3.4(supports-color@5.5.0)
transitivePeerDependencies:
- supports-color
dev: false
@@ -11013,7 +11007,7 @@ packages:
resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==}
engines: {node: '>=10'}
dependencies:
- debug: 4.3.4
+ debug: 4.3.4(supports-color@5.5.0)
istanbul-lib-coverage: 3.2.2
source-map: 0.6.1
transitivePeerDependencies:
diff --git a/supabase/seed.sql b/supabase/seed.sql
index 74d580d9..5105fb7a 100644
--- a/supabase/seed.sql
+++ b/supabase/seed.sql
@@ -466,3 +466,8 @@ INSERT INTO "storage"."buckets" ("id", "name", "owner", "created_at", "updated_a
--
-- Data for Name: objects; Type: TABLE DATA; Schema: storage; Owner: supabase_storage_admin
--
+
+-- Get the sequence name from supabase database
+SELECT PG_GET_SERIAL_SEQUENCE('organizationmember', 'id');
+-- In my case i got "public.organizationmember_id_seq"
+SELECT setval('public.organizationmember_id_seq', COALESCE((SELECT MAX(id)+1 FROM organizationmember), 1), false);
\ No newline at end of file