feat: remove defer (#243)

* working on moving the email logic to the backend

* Moved nodemailer to the backend

* uninstall defer

* updated relative import path in tsconfig

* working on the zod validation

* Merge branch 'main' into send-email

* fixed the zod import issue

* fixed the zod import issue

* updated the env example files

* docs: update README.md (#249)

* fix: handle catex error

* chore: remove fly.io github action

* Update README.md

Updated the Readme to reflect changes made to the emailing flow

---------

Co-authored-by: Rotimi Best <best@classroomio.com>

* chore: wrapp up email sending

---------

Co-authored-by: Rotimi Best <best@classroomio.com>
This commit is contained in:
Em_Dev
2024-04-12 04:57:19 +01:00
committed by GitHub
co-authored by Rotimi Best
parent 75ad73b97f
commit 3fe15e6633
27 changed files with 523 additions and 215 deletions
+7
View File
@@ -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=
+2
View File
@@ -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"
+2
View File
@@ -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');
+59
View File
@@ -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" <best@classroomio.com>',
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;
+37
View File
@@ -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 };
+220
View File
@@ -0,0 +1,220 @@
const withEmailTemplate = (content) =>
`<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, minimum-scale=1"
/>
<base target="_blank" />
<style>
body {
background-color: #fff;
font-family: "Poppins", "Helvetica Neue", "Segoe UI", Helvetica,
sans-serif;
font-size: 15px;
font-weight: 300;
line-height: 26px;
margin: 0;
color: #1e293b;
}
pre {
background: #f4f4f4;
padding: 2px;
}
hr {
border-top: 1px dashed #94a3b8;
margin-top: 1rem;
margin-bottom: 1rem;
}
table {
width: 100%;
}
table td {
padding: 5px;
}
.socialicons {
max-width: 200px;
margin-left: auto;
margin-right: auto;
margin-top: 27px;
}
.wrap {
background-color: #f1f5f9;
padding: 30px;
max-width: 525px;
margin: 0 auto;
border-radius: 12px;
}
.brandcolor {
color: #00c4b8;
}
.tooltip {
background-color: #f1f5f9;
padding: 1rem;
border-radius: 1rem;
color: #475569;
margin-top: 15px;
margin-bottom: 15px;
}
.tooltip a {
color: #1e293b;
}
.button {
margin-top:12px;
background: #0233BD;
border-radius: 8px;
text-decoration: none !important;
color: #fff !important;
font-weight: 500;
padding: 10px 30px;
display: inline-block;
font-size: 0.9em;
}
.button:hover {
background: #0233BD;
}
.footer {
text-align: center;
font-size: 12px;
color: #cbd5e1;
}
.footer a {
color: #cbd5e1;
margin-right: 5px;
}
.gutter {
padding: 30px;
text-align: center;
}
img {
max-width: 100%;
height: auto;
}
.gutter img {
max-width: 280px;
}
a {
color: #0233BD;
}
a:hover {
color: #0233BD;
}
h1,
h2,
h3,
h4 {
font-weight: 600;
}
@media screen and (max-width: 600px) {
.wrap {
max-width: auto;
}
.gutter {
padding: 10px;
}
}
</style>
</head>
<body
style="
background-color: #fff;
font-family: 'Poppins', 'Helvetica Neue', 'Segoe UI', Helvetica,
sans-serif;
font-size: 15px;
line-height: 26px;
margin: 0;
color: #1e293b;
"
>
<div class="gutter" style="padding: 30px">
<a href="https://classroomio.com" target="_blank">
<img
src="https://tapaozmyjsuykgerrfkt.supabase.co/storage/v1/object/public/avatars/classroomio-logo-full.png"
alt="ClassroomIO Logo"
/></a>
</div>
<div
class="wrap"
style="
background-color: #f1f5f9;
padding: 30px;
max-width: 525px;
margin: 0 auto;
border-radius: 12px;
"
>
${content}
</div>
<div
class="footer"
style="text-align: center; font-size: 12px; color: #cbd5e1"
>
<table class="socialicons">
<tr>
<td>
<a target="_blank" href="https://twitter.com/classroomio"
><img
title="X"
src="https://tapaozmyjsuykgerrfkt.supabase.co/storage/v1/object/public/avatars/classroomio-x.png"
alt="X"
width="32"
/></a>
</td>
<td>
<a target="_blank" href="https://www.youtube.com/@ClassroomIO"
><img
title="Youtube"
src="https://tapaozmyjsuykgerrfkt.supabase.co/storage/v1/object/public/avatars/classroomio-youtube.png"
alt="Youtube"
width="32"
/></a>
</td>
<td>
<a target="_blank" href="https://www.facebook.com/classroomiohq"
><img
title="Facebook"
src="https://tapaozmyjsuykgerrfkt.supabase.co/storage/v1/object/public/avatars/classroomio-facebook.png"
alt="Facebook"
width="32"
/></a>
</td>
</tr>
</table>
<p style="padding-top: 8px; line-height: initial">
ClassroomIO ${new Date().getFullYear()}. All rights reserved.<br />
<a
style="text-decoration: none"
href="https://app.enzuzo.com/policies/tos/958fc978-5477-11ee-a03b-7b111830c594"
target="_blank"
>Terms</a
>
|
<a
style="text-decoration: none"
href="https://app.enzuzo.com/policies/privacy/958fc978-5477-11ee-a03b-7b111830c594"
target="_blank"
>Privacy Policy</a
>
</p>
</div>
</body>
</html>
`;
module.exports = { withEmailTemplate };
-7
View File
@@ -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=
-46
View File
@@ -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" <best@classroomio.com>', // 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);
-42
View File
@@ -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" <best@classroomio.com>', // 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);
@@ -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;
@@ -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;
@@ -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)" <notify@classroomio.com>`,
to,
subject: `[Submitted]: ${exerciseTitle}`,
content
});
const emailData = [
{
from: `"${orgName} (via ClassroomIO.com)" <notify@classroomio.com>`,
to,
subject: `[Submitted]: ${exerciseTitle}`,
content
}
];
await sendEmail(emailData);
return json({
success: true,
@@ -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" <notify@classroomio.com>`,
to: feed.teacherEmail,
subject: `[${feed.courseTitle}] - News feed comment`,
content: `
const emailData = [
{
from: `"${feed.org.name} - ClassroomIO" <notify@classroomio.com>`,
to: feed.teacherEmail,
subject: `[${feed.courseTitle}] - News feed comment`,
content: `
<p>A student left you a comment on your newsfeed post</p>
<div style="font-style: italic; margin-top: 10px;">${comment}</div>
<div>
<a class="button" href="${postLink}">View comment</a>
</div>
`
});
`,
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" <notify@classroomio.com>`,
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 }) {
@@ -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" <notify@classroomio.com>`,
to,
replyTo: teacherEmail,
subject: `[${courseName}] One more step left`,
content: `
const emailData = [
{
from: `"${orgName} - ClassroomIO" <notify@classroomio.com>`,
to,
replyTo: teacherEmail,
subject: `[${courseName}] One more step left`,
content: `
<p>Hi ${studentFullname},</p>
<p>You are one step closer to joining: <strong>${courseName}</strong></p>
<p>Please send your proof of payment to: <strong>${teacherEmail}</strong>, in order to join the course.</p>
<p>Talk to you soon and see you in class.</p>
<p>${orgName}</p>
`
});
}
];
await sendEmail(emailData);
return json({
success: true,
@@ -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)" <notify@classroomio.com>`,
to,
subject: `${orgName} - Welcome to Class 🎉`,
content: `
const emailData = [
{
from: `"${orgName} (via ClassroomIO.com)" <notify@classroomio.com>`,
to,
subject: `${orgName} - Welcome to Class 🎉`,
content: `
<p>Hi there,</p>
<p>Congratulations 🎉, you’ve successfully joined: <strong>${courseName}</strong></p>
<p>Everything has been set up for you to have an amazing learning experience.</p>
@@ -38,7 +39,9 @@ export async function POST({ request }) {
<p>Cheers,</p>
<p>${orgName}</p>
`
});
}
];
await sendEmail(emailData);
return json({
success: true,
@@ -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)" <notify@classroomio.com>`,
to,
subject: 'Submission Update',
content
});
const emailData = [
{
from: `"${orgName} (via ClassroomIO.com)" <notify@classroomio.com>`,
to,
subject: 'Submission Update',
content
}
];
await sendEmail(emailData);
return json({
success: true,
@@ -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" <notify@classroomio.com>`,
to,
subject: `[${courseName}] Request to Join Course!`,
content: `
const emailData = [
{
from: `ClassroomIO" <notify@classroomio.com>`,
to,
subject: `[${courseName}] Request to Join Course!`,
content: `
<p>Hi amazing tutor,</p>
<p> A new student has requested to join a course you are teaching: “${courseName}"</p>
<p style="font: bold;">Student details</p>
@@ -44,7 +45,9 @@ export async function POST({ request }) {
Email: ${studentEmail}
</p>
`
});
}
];
await sendEmail(emailData);
return json({
success: true,
@@ -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" <notify@classroomio.com>`,
to,
subject: `[${courseName}] You've got a new student 🎉!`,
content: `
const emailData = [
{
from: `"ClassroomIO" <notify@classroomio.com>`,
to,
subject: `[${courseName}] You've got a new student 🎉!`,
content: `
<p>Hi amazing tutor,</p>
<p>Congratulations 🎉, a new student has joined a course you are teaching: ${courseName}</p>
<p>We hope they have a great experience learning from the best (YOU).</p>
<p>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.</p>
`
});
}
];
await sendEmail(emailData);
return json({
success: true,
@@ -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)" <notify@classroomio.com>`,
to,
subject: `You have been invited to a course in ${orgName}!`,
content: `
const emailData = [
{
from: `"${orgName} (via ClassroomIO.com)" <notify@classroomio.com>`,
to,
subject: `You have been invited to a course in ${orgName}!`,
content: `
<p>Hey ${name},</p>
<p> You have been given access to teach a course by ${orgName}</p>
<p>The course is titled: ${courseName}</p>
@@ -40,7 +41,10 @@ export async function POST({ request }) {
<a class="button" href="${inviteLink}">Open Dashboard</a>
</div>
`
});
}
];
await sendEmail(emailData);
return json({
success: true,
@@ -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: `
<p>Hey there,</p>
<p> You have been invited to join ${name} on ClassroomIO 🎉🎉🎉.</p>
<div>
<a class="button" href="${inviteLink}">Accept Invitation</a>
</div>
`,
isPersonalEmail: true
});
isPersonalEmail: true
}
];
await sendEmail(emailData);
return json({
success: true,
@@ -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: `
<p>Thank you for signing up</p>
<p>To verify your email, please click the <strong>Verify</strong> button below </p>
<div>
<a class="button" href="${verificationLink}">Verify</a>
</div>
`,
isPersonalEmail: true
});
const emailData = [
{
to,
subject: '[ClassroomIO]: Verify your email',
content: `
<p>Thank you for signing up</p>
<p>To verify your email, please click the <strong>Verify</strong> button below </p>
<div>
<a class="button" href="${verificationLink}">Verify</a>
</div>
`,
isPersonalEmail: true
}
];
await sendEmail(emailData);
return json({
success: true,
@@ -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: `
<p>Dear ${name},</p>
<p>My name is Best the CEO of ClassroomIO and I will personally like to welcome to the vibrant community of ClassroomIO!</p>
<p>
@@ -47,8 +48,10 @@ export async function POST({ request }) {
<a href="https://app.classroomio.com">Go to Dashboard</a>
</div>
`,
isPersonalEmail: true
});
isPersonalEmail: true
}
];
await sendEmail(emailData);
return json({
success: true,
+1 -1
View File
@@ -12,7 +12,7 @@ const config = {
adapter: vercelAdapter(),
alias: {
$lib: path.resolve('./src/lib'),
$defer: path.resolve('./src/defer')
$nodemailer: path.resolve('./src/nodemailer')
}
}
};
+2 -2
View File
@@ -16,8 +16,8 @@
"paths": {
"$lib": ["./src/lib"],
"$lib/*": ["./src/lib/*"],
"$defer": ["./src/defer"],
"$defer/*": ["./src/defer/*"]
"$nodemailer": ["./src/nodemailer"],
"$nodemailer/*": ["./src/nodemailer/*"]
}
}