From 8d742b86adeb7e97072cf24710f477101b4adace Mon Sep 17 00:00:00 2001 From: scosman Date: Sun, 3 Nov 2024 00:13:29 -0400 Subject: [PATCH] Move email templates to handlebars.js remove tbody tags we added for svelte compatibility --- email_docs.md | 4 +- package-lock.json | 65 ++++ package.json | 1 + src/lib/emails/welcome_email_html.hbs | 207 ++++++++++++ src/lib/emails/welcome_email_html.svelte | 297 ------------------ src/lib/emails/welcome_email_text.hbs | 4 + src/lib/emails/welcome_email_text.svelte | 23 -- src/lib/mailer.test.ts | 17 +- src/lib/mailer.ts | 18 +- .../(admin)/account/api/+page.server.ts | 2 + 10 files changed, 305 insertions(+), 333 deletions(-) create mode 100644 src/lib/emails/welcome_email_html.hbs delete mode 100644 src/lib/emails/welcome_email_html.svelte create mode 100644 src/lib/emails/welcome_email_text.hbs delete mode 100644 src/lib/emails/welcome_email_text.svelte diff --git a/email_docs.md b/email_docs.md index 83c122a0..28f66fd8 100644 --- a/email_docs.md +++ b/email_docs.md @@ -28,6 +28,8 @@ To customize the email: - edit the plaintext email content in src/lib/emails/welcome_email_text.svelte - edit the html email content in src/lib/emails/welcome_email_html.svelte - don't forget address and preheader text which won't render in a preview, but will in the client's email client. +**Note**: use triple braces for properties in plaintext emails, and double braces for html emails. See the [handlebars documentation](https://handlebarsjs.com/guide/expressions.html#html-escaping) for more information. + You can also delete the welcome email by removing the call to sendTemplatedEmail in src/routes/(admin)/account/api/+page.server.ts ## Adding Admin Emails @@ -38,7 +40,7 @@ Simply add a call to sendAdminEmail() in the appropriate place, passing a subjec ## Adding Additional User Emails -You can add more user emails. Create a template in src/lib/emails, using the welcome email as a guide. You should have both a plaintext and html version of the email (see welcome_email_text.svelte and welcome_email_html.svelte), although it will work with just one. +You can add more user emails. Create a template in src/lib/emails, using the welcome email as a guide. You should have both a plaintext and html version of the email (see welcome_email_text.svelte and welcome_email_html.svelte for examples), although it will work with just one. When you want to send the email, call sendUserEmail() with the appropriate parameters, including the name of the email template. diff --git a/package-lock.json b/package-lock.json index ae4b770b..6e017eb6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "@supabase/auth-ui-svelte": "^0.2.9", "@supabase/ssr": "^0.5.1", "@supabase/supabase-js": "^2.45.2", + "handlebars": "^4.7.8", "resend": "^3.5.0", "stripe": "^13.3.0" }, @@ -3289,6 +3290,27 @@ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, + "node_modules/handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -3990,6 +4012,15 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/minipass": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", @@ -4071,6 +4102,12 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "license": "MIT" + }, "node_modules/node-releases": { "version": "2.0.18", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", @@ -5132,6 +5169,15 @@ "node": ">=8" } }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-js": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", @@ -5830,6 +5876,19 @@ "integrity": "sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==", "dev": true }, + "node_modules/uglify-js": { + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "license": "BSD-2-Clause", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/undici-types": { "version": "6.19.8", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", @@ -6159,6 +6218,12 @@ "node": ">=0.10.0" } }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "license": "MIT" + }, "node_modules/wordwrapjs": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/wordwrapjs/-/wordwrapjs-4.0.1.tgz", diff --git a/package.json b/package.json index 8a19e805..02175b6c 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "@supabase/auth-ui-svelte": "^0.2.9", "@supabase/ssr": "^0.5.1", "@supabase/supabase-js": "^2.45.2", + "handlebars": "^4.7.8", "resend": "^3.5.0", "stripe": "^13.3.0" }, diff --git a/src/lib/emails/welcome_email_html.hbs b/src/lib/emails/welcome_email_html.hbs new file mode 100644 index 00000000..f1b10863 --- /dev/null +++ b/src/lib/emails/welcome_email_html.hbs @@ -0,0 +1,207 @@ + + + + + Welcome Email + + + + + + + + + + + + \ No newline at end of file diff --git a/src/lib/emails/welcome_email_html.svelte b/src/lib/emails/welcome_email_html.svelte deleted file mode 100644 index 565eecc1..00000000 --- a/src/lib/emails/welcome_email_html.svelte +++ /dev/null @@ -1,297 +0,0 @@ - - - - - - - Welcome Email - - - - - - - - - - - - - - diff --git a/src/lib/emails/welcome_email_text.hbs b/src/lib/emails/welcome_email_text.hbs new file mode 100644 index 00000000..741f714b --- /dev/null +++ b/src/lib/emails/welcome_email_text.hbs @@ -0,0 +1,4 @@ +Welcome to +{{{companyName}}}! This is a quick sample of a welcome email. You can customize +this email to fit your needs. To unsubscribe, visit: +{{{WebsiteBaseUrl}}}/account/settings/change_email_subscription \ No newline at end of file diff --git a/src/lib/emails/welcome_email_text.svelte b/src/lib/emails/welcome_email_text.svelte deleted file mode 100644 index a03ff612..00000000 --- a/src/lib/emails/welcome_email_text.svelte +++ /dev/null @@ -1,23 +0,0 @@ - - -Welcome to {companyName}! - -This is a quick sample of a welcome email. You can customize this email to fit your needs. - -To unsubscribe, visit: {WebsiteBaseUrl}/account/settings/change_email_subscription diff --git a/src/lib/mailer.test.ts b/src/lib/mailer.test.ts index c2887fe6..f4b0772b 100644 --- a/src/lib/mailer.test.ts +++ b/src/lib/mailer.test.ts @@ -57,7 +57,10 @@ describe("mailer", () => { subject: "Test", from_email: "test@example.com", template_name: "welcome_email", - template_properties: {}, + template_properties: { + companyName: "Test Company", + WebsiteBaseUrl: "https://test.com", + }, }) expect(mockSend).toHaveBeenCalled() @@ -106,7 +109,10 @@ describe("mailer", () => { from_email: "from@example.com", to_emails: ["to@example.com"], template_name: "welcome_email", - template_properties: {}, + template_properties: { + companyName: "Test Company", + WebsiteBaseUrl: "https://test.com", + }, }) expect(mockSend).toHaveBeenCalled() @@ -115,7 +121,12 @@ describe("mailer", () => { expect(email.to).toEqual(["to@example.com"]) expect(email.subject).toEqual("Test subject") expect(email.text).toContain("This is a quick sample of a welcome email") - expect(email.html).toContain(">This is a quick sample of a welcome email") + expect(email.html).toContain("This is a quick sample of a welcome email") + expect(email.html).toContain(" mod.default) - const { body } = render(emailTemplate, { props: template_properties }) - plaintextBody = body.replace(//g, "") + const template = handlebars.compile(textTemplate) + plaintextBody = template(template_properties) } catch (e) { // ignore, plaintextBody is optional plaintextBody = undefined @@ -133,11 +133,11 @@ export const sendTemplatedEmail = async ({ let htmlBody: string | undefined = undefined try { - const emailTemplate = await import( - `./emails/${template_name}_html.svelte` + const htmlTemplate = await import( + `./emails/${template_name}_html.hbs?raw` ).then((mod) => mod.default) - const { body } = render(emailTemplate, { props: template_properties }) - htmlBody = body + const template = handlebars.compile(htmlTemplate) + htmlBody = template(template_properties) } catch (e) { // ignore, htmlBody is optional htmlBody = undefined diff --git a/src/routes/(admin)/account/api/+page.server.ts b/src/routes/(admin)/account/api/+page.server.ts index 498570e0..0ed47a19 100644 --- a/src/routes/(admin)/account/api/+page.server.ts +++ b/src/routes/(admin)/account/api/+page.server.ts @@ -1,5 +1,6 @@ import { fail, redirect } from "@sveltejs/kit" import { sendAdminEmail, sendUserEmail } from "$lib/mailer" +import { WebsiteBaseUrl } from "../../../../config" export const actions = { toggleEmailSubscription: async ({ locals: { supabase, safeGetSession } }) => { @@ -316,6 +317,7 @@ export const actions = { template_name: "welcome_email", template_properties: { companyName: "SaaS Starter", + WebsiteBaseUrl: WebsiteBaseUrl, }, }) }