diff --git a/package.json b/package.json index a5de02c9..bf03aecc 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "lodash": "^4.17.21", "marked": "^6.0.0", "netlify": "^13.1.10", + "nodemailer": "^6.9.5", "openai-edge": "^1.2.2", "pluralize": "^8.0.0", "sirv": "^2.0.3", @@ -55,6 +56,7 @@ "@sveltejs/kit": "^1.22.4", "@tailwindcss/typography": "^0.5.9", "@types/lodash": "^4.14.196", + "@types/nodemailer": "^6.4.11", "autoprefixer": "^10.4.14", "carbon-components-svelte": "^0.79.0", "carbon-icons-svelte": "^12.1.0", diff --git a/src/lib/utils/services/notification/notification.ts b/src/lib/utils/services/notification/notification.ts index 87d678f6..e6247060 100644 --- a/src/lib/utils/services/notification/notification.ts +++ b/src/lib/utils/services/notification/notification.ts @@ -1,41 +1,44 @@ import { supabase } from '$lib/utils/functions/supabase'; -export class Notification { - name = ''; - accessToken = ''; +export const NOTIFICATION_NAME = { + WELCOME_TO_APP: 'WELCOME TO APP' +}; - constructor(name: string) { - this.name = name; - } +const NAME_TO_PATH = { + [NOTIFICATION_NAME.WELCOME_TO_APP]: '/api/email/welcome' +}; - async getAccessToken() { - const { data } = await supabase.auth.getSession(); - this.accessToken = data.session?.access_token || ''; +const getAccessToken = async () => { + const { data } = await supabase.auth.getSession(); - return this.accessToken; - } + return data.session?.access_token || ''; +}; - async send(path: string, body: { [k: string]: string }) { - try { - await this.getAccessToken(); - const response = await fetch(path, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - Authorization: this.accessToken - }, - body: JSON.stringify(body) - }); - if (!response.ok) { - throw new Error(await response.text()); - } - const result = await response.json(); +export const triggerSendEmail = async (name: string, body: { [k: string]: string }) => { + try { + const accessToken = await getAccessToken(); + const path = NAME_TO_PATH[name]; - console.log(`${this.name} sent email result`, result); - } catch (error) { - console.log(`${this.name} sent email error`, error); + if (!path) { + console.error('Could not trigger send'); + return; } - } -} -export class InviteNotification extends Notification {} + const response = await fetch(path, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: accessToken + }, + body: JSON.stringify(body) + }); + if (!response.ok) { + throw new Error(await response.text()); + } + const result = await response.json(); + + console.log(`${name} sent email result`, result); + } catch (error) { + console.log(`${name} sent email error`, error); + } +}; diff --git a/src/lib/utils/services/notification/send.ts b/src/lib/utils/services/notification/send.ts new file mode 100644 index 00000000..466ef967 --- /dev/null +++ b/src/lib/utils/services/notification/send.ts @@ -0,0 +1,58 @@ +import nodemailer from 'nodemailer'; +import { withEmailTemplate } from './template'; +import { SMTP_HOST, SMTP_USER, SMTP_PASSWORD } from '$env/static/private'; + +const content = ` +
+

Welcome

+
+

Thank you so much for trying out ClassroomIO

+ Login
+
+Your ClassroomIO Team +
+`; + +const transporter = nodemailer.createTransport({ + host: SMTP_HOST, + port: 465, + secure: true, + auth: { + user: SMTP_USER, + pass: SMTP_PASSWORD + } +}); + +const getTransporter = async (): Promise => { + return await new Promise((resolve, reject) => { + transporter.verify(function (error, success) { + if (error) { + console.log('Transporter error', error); + reject(error); + } else { + console.log('Server is ready to take our messages'); + resolve(success); + } + }); + }); +}; + +export const sendEmail = async ({ + to, + subject, + content +}: { + to: string; + subject: string; + content: string; +}) => { + await getTransporter(); + const info = await transporter.sendMail({ + from: '"Best from ClassroomIO" ', // sender address + to, // list of receivers + subject, // Subject line + html: withEmailTemplate(content) // html body + }); + + console.log('Message sent: %s', info.messageId); +}; diff --git a/src/lib/utils/services/notification/template.ts b/src/lib/utils/services/notification/template.ts new file mode 100644 index 00000000..10c9ad39 --- /dev/null +++ b/src/lib/utils/services/notification/template.ts @@ -0,0 +1,219 @@ +export const withEmailTemplate = (content: string) => + ` + + + + + + + + + +
+ + ClassroomIO Logo +
+
+ ${content} +
+ + + + +`; diff --git a/src/routes/api/email/welcome/+server.js b/src/routes/api/email/welcome/+server.js index 1bd10675..702b5f91 100644 --- a/src/routes/api/email/welcome/+server.js +++ b/src/routes/api/email/welcome/+server.js @@ -1,9 +1,10 @@ import { render } from 'svelte-email'; import { json } from '@sveltejs/kit'; -import WelcomeTemplate from './template.svelte'; import { getSendgrid, SENDGRID_FROM } from '$lib/utils/services/sendgrid'; import { getSupabase } from '$lib/utils/functions/supabase'; +import { sendEmail } from '$lib/utils/services/notification/send'; + const sendgrid = getSendgrid(); const supabase = getSupabase(); @@ -28,19 +29,29 @@ export async function POST({ request }) { return json({ success: false, message: 'Unauthenticated user' }, { status: 401 }); } - const options = { - from: SENDGRID_FROM, + await sendEmail({ to, subject: 'Thank you so so so much for choosing ClassroomIO!', - html: render({ - template: WelcomeTemplate, - props: { - name - } - }) - }; - - sendgrid.send(options); + content: ` +

Dear ${name},

+

My name is Best the CEO of ClassroomIO and I will personally like to welcome to the vibrant community of ClassroomIO!

+

+ We're thrilled to have you join us in shaping the future of education. Your decision to embark on + this journey with us is deeply appreciated, and we're genuinely excited about the possibilities + that lie ahead. +

+

+ Once again, welcome to the ClassroomIO family. We're here to help, to listen, and to celebrate + your successes. Get ready to unlock a world of possibilities in education! +

+

+ If you have any question or face any issues just send me an email best@classroomio.com 😃 +

+
+ Go to Dashboard +
+ ` + }); return json({ success: true, diff --git a/src/routes/api/email/welcome/template.svelte b/src/routes/api/email/welcome/template.svelte deleted file mode 100644 index aed13b4a..00000000 --- a/src/routes/api/email/welcome/template.svelte +++ /dev/null @@ -1,13 +0,0 @@ - - - diff --git a/src/routes/api/email/welcome/welcome.svelte b/src/routes/api/email/welcome/welcome.svelte deleted file mode 100644 index 40149e0c..00000000 --- a/src/routes/api/email/welcome/welcome.svelte +++ /dev/null @@ -1,79 +0,0 @@ - - -Dear {name}, -Welcome to the vibrant community of ClassroomIO! - - We're thrilled to have you join us in shaping the future of education. Your decision to embark on - this journey with us is deeply appreciated, and we're genuinely excited about the possibilities - that lie ahead. - - - At ClassroomIO, we understand the dedication and passion that educators like you bring to the - table. That's why we've built a platform that's not only designed to make your teaching experience - seamless but also to empower you to create transformative learning moments for your students. - - - Your choice to use ClassroomIO is a testament to your commitment to innovation in education. It's - educators like you who drive progress and inspire change, and we're here to support you every step - of the way. - - - Our team is working diligently behind the scenes, shipping new features and updates daily. This - means you can expect a platform that's constantly evolving, adapting to your needs, and staying at - the forefront of educational technology. - - - We encourage you to explore ClassroomIO's range of features, from interactive quizzes and robust - course management tools to collaborative forums and AI-integrated lesson planning. Your feedback - is invaluable to us as we work together to refine and enhance these features to best suit your - teaching style and objectives. - - - Once again, welcome to the ClassroomIO family. We're here to help, to listen, and to celebrate - your successes. Get ready to unlock a world of possibilities in education! - -
- -
-Best regards -Rotimi Best -ClassroomIO diff --git a/src/routes/onboarding/+page.svelte b/src/routes/onboarding/+page.svelte index b30428f2..d6545576 100644 --- a/src/routes/onboarding/+page.svelte +++ b/src/routes/onboarding/+page.svelte @@ -10,9 +10,11 @@ import { blockedSubdomain } from '$lib/utils/constants/app'; import { welcomeModalStore } from '$lib/components/WelcomeModal/store'; import { getOrganizations } from '$lib/utils/services/org'; - import { sendWelcomeEmail } from './utils'; - import { currentOrg } from '$lib/utils/store/org'; import { generateSitename } from '$lib/utils/functions/org'; + import { + triggerSendEmail, + NOTIFICATION_NAME + } from '$lib/utils/services/notification/notification'; interface OnboardingField { fullname?: string; @@ -182,7 +184,10 @@ loading = false; if (fields.fullname) { - sendWelcomeEmail($profile.email, fields.fullname); + triggerSendEmail(NOTIFICATION_NAME.WELCOME_TO_APP, { + to: $profile.email, + name: fields.fullname + }); } console.log('data', data); diff --git a/src/routes/onboarding/utils.ts b/src/routes/onboarding/utils.ts deleted file mode 100644 index f0d5c16a..00000000 --- a/src/routes/onboarding/utils.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { supabase } from '$lib/utils/functions/supabase'; - -export async function sendWelcomeEmail(to: string, name: string) { - const { data } = await supabase.auth.getSession(); - const accessToken = data.session?.access_token || ''; - - try { - const response = await fetch('/api/email/welcome', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - Authorization: accessToken - }, - body: JSON.stringify({ - to, - name - }) - }); - if (!response.ok) { - throw new Error(await response.text()); - } - const result = await response.json(); - - console.log('Sent confirmation email', result); - } catch (error) { - console.error('Error sending confirmation email', error); - } -}