From fad94df92bf4092b3be6ee3f61ba88bb87c08fcd Mon Sep 17 00:00:00 2001 From: scosman Date: Wed, 24 Jul 2024 23:01:25 -0400 Subject: [PATCH 01/30] Add admin mailer. Fixes https://github.com/CriticalMoments/CMSaasStarter/issues/83 --- package-lock.json | 18 ++++++ package.json | 2 + src/lib/admin_mailer.ts | 62 +++++++++++++++++++ .../(admin)/account/api/+page.server.ts | 6 ++ 4 files changed, 88 insertions(+) create mode 100644 src/lib/admin_mailer.ts diff --git a/package-lock.json b/package-lock.json index a550e1cc..7b4287d0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,8 @@ "@supabase/auth-helpers-sveltekit": "^0.11.0", "@supabase/auth-ui-svelte": "^0.2.9", "@supabase/supabase-js": "^2.33.0", + "@types/nodemailer": "^6.4.15", + "nodemailer": "^6.9.14", "stripe": "^13.3.0" }, "devDependencies": { @@ -1128,6 +1130,14 @@ "undici-types": "~5.26.4" } }, + "node_modules/@types/nodemailer": { + "version": "6.4.15", + "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.15.tgz", + "integrity": "sha512-0EBJxawVNjPkng1zm2vopRctuWVCxk34JcIlRuXSf54habUWdz1FB7wHDqOqvDa8Mtpt0Q3LTXQkAs2LNyK5jQ==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/phoenix": { "version": "1.6.4", "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.4.tgz", @@ -3410,6 +3420,14 @@ "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", "dev": true }, + "node_modules/nodemailer": { + "version": "6.9.14", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.14.tgz", + "integrity": "sha512-Dobp/ebDKBvz91sbtRKhcznLThrKxKt97GI2FAlAyy+fk19j73Uz3sBXolVtmcXjaorivqsbbbjDY+Jkt4/bQA==", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", diff --git a/package.json b/package.json index 86f039dc..d5fdbcf0 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,8 @@ "@supabase/auth-helpers-sveltekit": "^0.11.0", "@supabase/auth-ui-svelte": "^0.2.9", "@supabase/supabase-js": "^2.33.0", + "@types/nodemailer": "^6.4.15", + "nodemailer": "^6.9.14", "stripe": "^13.3.0" } } diff --git a/src/lib/admin_mailer.ts b/src/lib/admin_mailer.ts new file mode 100644 index 00000000..77b98253 --- /dev/null +++ b/src/lib/admin_mailer.ts @@ -0,0 +1,62 @@ +import nodemailer from "nodemailer" + +import { env } from "$env/dynamic/private" + +// Sends an email to the admin email address. +// Does not throw errors, but logs them. +export const sendAdminEmail = async ({ + subject, + body, +}: { + subject: string + body: string +}) => { + // Check admin email is set. + if (!env.PRIVATE_ADMIN_EMAIL) { + return + } + + // Check if smtp settings are set. + if ( + !env.PRIVATE_SMTP_HOST || + !env.PRIVATE_SMTP_USER || + !env.PRIVATE_SMTP_PASS + ) { + console.log( + "No smtp settings, not sending admil email. See CMSaasStarter setup instructions.", + ) + return + } + + // Default to port 587 if not set. + let port = 587 + if (env.PRIVATE_SMTP_PORT) { + port = parseInt(env.PRIVATE_SMTP_PORT) + } + + try { + const transporter = nodemailer.createTransport({ + host: env.PRIVATE_SMTP_HOST, + port: port, + secure: port === 465, // https://nodemailer.com/smtp/ + requireTLS: true, // Email should be encrypted in 2024 + auth: { + user: env.PRIVATE_SMTP_USER, + pass: env.PRIVATE_SMTP_PASS, + }, + }) + + const info = await transporter.sendMail({ + from: env.PRIVATE_ADMIN_EMAIL, + to: env.PRIVATE_ADMIN_EMAIL, + subject: "ADMIN_MAIL: " + subject, + text: body, + }) + + if (info.rejected && info.rejected.length > 0) { + console.log("Failed to send admin email, rejected:", info.rejected) + } + } catch (e) { + console.log("Failed to send admin email, error:", e) + } +} diff --git a/src/routes/(admin)/account/api/+page.server.ts b/src/routes/(admin)/account/api/+page.server.ts index fc64c697..27eb80d7 100644 --- a/src/routes/(admin)/account/api/+page.server.ts +++ b/src/routes/(admin)/account/api/+page.server.ts @@ -1,4 +1,5 @@ import { fail, redirect } from "@sveltejs/kit" +import { sendAdminEmail } from "$lib/admin_mailer" export const actions = { updateEmail: async ({ request, locals: { supabase, safeGetSession } }) => { @@ -255,6 +256,11 @@ export const actions = { }) } + sendAdminEmail({ + subject: "Profile Updated", + body: `Profile updated by ${session.user.email}\nFull name: ${fullName}\nCompany name: ${companyName}\nWebsite: ${website}`, + }) + return { fullName, companyName, From a70edfaab3419de8db352ff5ce474be9fbbcf503 Mon Sep 17 00:00:00 2001 From: scosman Date: Wed, 24 Jul 2024 23:14:47 -0400 Subject: [PATCH 02/30] Add docs for admin mailer --- README.md | 10 ++++++++++ local_env_template | 10 ++++++++++ 2 files changed, 20 insertions(+) diff --git a/README.md b/README.md index 98cbf5c8..342c03a6 100644 --- a/README.md +++ b/README.md @@ -271,6 +271,16 @@ If you prefer another host you can explore alternatives: - [Community adapters](https://sveltesociety.dev/components#adapters) including Github pages, AppEngine, Azure, and more - [Supabase](https://supabase.com/docs/guides/getting-started/quickstarts/sveltekit) if you want one host for everything. Note: they do charge $10 a month for custom domains, unlike Cloudflare. +## Setup Admin Emailer -- Optional + +SaaS Starter includes an admin emailer for sending yourself email notifications when important events happen, such as a new user creating their profile. This let's you monitor your app and respond to users without watching the database. + +Provide email SMTP credientials in your environment variables: `PRIVATE_SMTP_HOST`, `PRIVATE_SMTP_PORT`, `PRIVATE_SMTP_USER`, `PRIVATE_SMTP_PASS`. You can use any SMTP providers such as Gmail, Sendgrid, AWS SES, Resend, or Mailgun. + +Set the email address which admil emails will be sent to in `PRIVATE_ADMIN_EMAIL`. + +You can add additional calls to sendAdminEmail() for any other events you want to monitor. + ## Add Your Content After the steps above, you’ll have a working version like the demo page. However, it’s not branded, and doesn’t have your content. The following checklist helps you customize the template to make a SaaS homepage for your company. diff --git a/local_env_template b/local_env_template index fadeee14..667774aa 100644 --- a/local_env_template +++ b/local_env_template @@ -1,4 +1,14 @@ +# Supabase settings PUBLIC_SUPABASE_URL='https://REPLACE_ME.supabase.co' PUBLIC_SUPABASE_ANON_KEY='REPLACE_ME' PRIVATE_SUPABASE_SERVICE_ROLE='REPLACE_ME' + +# Stripe settings PRIVATE_STRIPE_API_KEY='REPLACE_ME' + +# SMTP settings for email - optional +# PRIVATE_ADMIN_EMAIL='your_email@example.com' # see lib/admin_mailer.ts +# PRIVATE_SMTP_HOST='REPLACE_ME' +# PRIVATE_SMTP_PORT='587' +# PRIVATE_SMTP_USER='REPLACE_ME' +# PRIVATE_SMTP_PASS='REPLACE_ME' \ No newline at end of file From 14d3ca9c4a3fe5c000b29cb5491e925e811b808e Mon Sep 17 00:00:00 2001 From: scosman Date: Wed, 24 Jul 2024 23:50:51 -0400 Subject: [PATCH 03/30] Make nodemailer optional, see if this fixed cloudflare --- README.md | 8 +++----- package.json | 9 ++++++++- src/routes/(marketing)/contact_us/+page.server.ts | 7 +++++++ 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 342c03a6..2135d703 100644 --- a/README.md +++ b/README.md @@ -273,13 +273,11 @@ If you prefer another host you can explore alternatives: ## Setup Admin Emailer -- Optional -SaaS Starter includes an admin emailer for sending yourself email notifications when important events happen, such as a new user creating their profile. This let's you monitor your app and respond to users without watching the database. +SaaS Starter includes an admin emailer for sending yourself email notifications when important events happen. This let's you monitor your app and respond to users without watching the database. -Provide email SMTP credientials in your environment variables: `PRIVATE_SMTP_HOST`, `PRIVATE_SMTP_PORT`, `PRIVATE_SMTP_USER`, `PRIVATE_SMTP_PASS`. You can use any SMTP providers such as Gmail, Sendgrid, AWS SES, Resend, or Mailgun. +If you setup the admin emailer, it will email you when users create their profile or the 'Contact Us' form is submitted. You can add additional calls to sendAdminEmail() for any other events you want to monitor. -Set the email address which admil emails will be sent to in `PRIVATE_ADMIN_EMAIL`. - -You can add additional calls to sendAdminEmail() for any other events you want to monitor. +To setup, provide email SMTP credientials in your environment variables: `PRIVATE_SMTP_HOST`, `PRIVATE_SMTP_PORT`, `PRIVATE_SMTP_USER`, `PRIVATE_SMTP_PASS`. You can use any SMTP providers such as Gmail, Sendgrid, AWS SES, Resend, or Mailgun. Then set the email address to which admin emails will be sent in `PRIVATE_ADMIN_EMAIL`. ## Add Your Content diff --git a/package.json b/package.json index d5fdbcf0..f2491fcd 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,14 @@ "@supabase/auth-ui-svelte": "^0.2.9", "@supabase/supabase-js": "^2.33.0", "@types/nodemailer": "^6.4.15", - "nodemailer": "^6.9.14", "stripe": "^13.3.0" + }, + "peerDependencies": { + "nodemailer": "^6.9.14" + }, + "peerDependenciesMeta": { + "nodemailer": { + "optional": true + } } } diff --git a/src/routes/(marketing)/contact_us/+page.server.ts b/src/routes/(marketing)/contact_us/+page.server.ts index ffaceb4a..681c4780 100644 --- a/src/routes/(marketing)/contact_us/+page.server.ts +++ b/src/routes/(marketing)/contact_us/+page.server.ts @@ -1,4 +1,5 @@ import { fail } from "@sveltejs/kit" +import { sendAdminEmail } from "$lib/admin_mailer.js" /** @type {import('./$types').Actions} */ export const actions = { @@ -67,5 +68,11 @@ export const actions = { if (insertError) { return fail(500, { errors: { _: "Error saving" } }) } + + // Send email to admin + sendAdminEmail({ + subject: "New contact request", + body: `New contact request from ${firstName} ${lastName}.\n\nEmail: ${email}\n\nPhone: ${phone}\n\nCompany: ${company}\n\nMessage: ${message}`, + }) }, } From bbda648f3d3b5b387c37b0d7a1c16b139ba294a6 Mon Sep 17 00:00:00 2001 From: scosman Date: Wed, 24 Jul 2024 23:53:38 -0400 Subject: [PATCH 04/30] Move nodemailer types to dev only dep --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f2491fcd..720c7e1f 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "@tailwindcss/typography": "^0.5.13", "@typescript-eslint/eslint-plugin": "^6.20.0", "@typescript-eslint/parser": "^6.19.0", + "@types/nodemailer": "^6.4.15", "autoprefixer": "^10.4.15", "daisyui": "^4.7.3", "eslint": "^8.28.0", @@ -41,7 +42,6 @@ "@supabase/auth-helpers-sveltekit": "^0.11.0", "@supabase/auth-ui-svelte": "^0.2.9", "@supabase/supabase-js": "^2.33.0", - "@types/nodemailer": "^6.4.15", "stripe": "^13.3.0" }, "peerDependencies": { From 1074e5cee7ae52d12d6cda4850f0f8019b52b883 Mon Sep 17 00:00:00 2001 From: scosman Date: Thu, 25 Jul 2024 00:02:42 -0400 Subject: [PATCH 05/30] Handle exception on nodemailer import --- src/lib/admin_mailer.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/lib/admin_mailer.ts b/src/lib/admin_mailer.ts index 77b98253..dcad5701 100644 --- a/src/lib/admin_mailer.ts +++ b/src/lib/admin_mailer.ts @@ -1,4 +1,9 @@ -import nodemailer from "nodemailer" +let nodemailer: typeof import("nodemailer") | undefined +try { + nodemailer = await import("nodemailer") +} catch (e) { + // nodemailer is not installed (Cloudflare Workers). Do nothing. +} import { env } from "$env/dynamic/private" @@ -16,6 +21,13 @@ export const sendAdminEmail = async ({ return } + if (!nodemailer) { + console.log( + "This environment does not support sending emails. Nodemailer requires node.js and doesn't work in environments like Cloudflare Workers.", + ) + return + } + // Check if smtp settings are set. if ( !env.PRIVATE_SMTP_HOST || From e5484da1f3756abd511deab052264d6b8f1bb091 Mon Sep 17 00:00:00 2001 From: scosman Date: Thu, 25 Jul 2024 00:21:14 -0400 Subject: [PATCH 06/30] remove nodemailer from package.lock --- package-lock.json | 8 -------- 1 file changed, 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7b4287d0..b9a7df9f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3420,14 +3420,6 @@ "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", "dev": true }, - "node_modules/nodemailer": { - "version": "6.9.14", - "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.14.tgz", - "integrity": "sha512-Dobp/ebDKBvz91sbtRKhcznLThrKxKt97GI2FAlAyy+fk19j73Uz3sBXolVtmcXjaorivqsbbbjDY+Jkt4/bQA==", - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", From d12ea051d7f0512888fc29cbb7d6c0ca2608ba67 Mon Sep 17 00:00:00 2001 From: scosman Date: Thu, 25 Jul 2024 00:37:24 -0400 Subject: [PATCH 07/30] Add cloudflare worker support --- src/lib/admin_mailer.ts | 54 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/src/lib/admin_mailer.ts b/src/lib/admin_mailer.ts index dcad5701..fc8f9ba5 100644 --- a/src/lib/admin_mailer.ts +++ b/src/lib/admin_mailer.ts @@ -1,3 +1,4 @@ +// Optional dependency, only used if platform suppport node.js (not Cloudflare Workers). let nodemailer: typeof import("nodemailer") | undefined try { nodemailer = await import("nodemailer") @@ -21,6 +22,20 @@ export const sendAdminEmail = async ({ return } + if (nodemailer) { + return sendAdminEmailNodemailer({ subject, body }) + } else { + return sendAdminEmailCloudflareWorkers({ subject, body }) + } +} + +const sendAdminEmailNodemailer = async ({ + subject, + body, +}: { + subject: string + body: string +}) => { if (!nodemailer) { console.log( "This environment does not support sending emails. Nodemailer requires node.js and doesn't work in environments like Cloudflare Workers.", @@ -72,3 +87,42 @@ export const sendAdminEmail = async ({ console.log("Failed to send admin email, error:", e) } } + +// https://blog.cloudflare.com/sending-email-from-workers-with-mailchannels/ +const sendAdminEmailCloudflareWorkers = async ({ + subject, + body, +}: { + subject: string + body: string +}) => { + const send_request = new Request("https://api.mailchannels.net/tx/v1/send", { + method: "POST", + headers: { + "content-type": "application/json", + }, + body: JSON.stringify({ + personalizations: [ + { + to: [{ email: env.PRIVATE_ADMIN_EMAIL }], + }, + ], + from: { + email: env.PRIVATE_ADMIN_EMAIL, + }, + subject: "ADMIN_MAIL: " + subject, + content: [ + { + type: "text/plain", + value: body, + }, + ], + }), + }) + + const response = await fetch(send_request) + if (!response.ok) { + console.log("Error sending admin email with MailChannels API", response) + return + } +} From 0a6a2daa270170d18aa093ef16da9a4a1704cf3e Mon Sep 17 00:00:00 2001 From: scosman Date: Thu, 25 Jul 2024 00:47:20 -0400 Subject: [PATCH 08/30] Nodemailer defined on CF? --- src/lib/admin_mailer.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lib/admin_mailer.ts b/src/lib/admin_mailer.ts index fc8f9ba5..434a542b 100644 --- a/src/lib/admin_mailer.ts +++ b/src/lib/admin_mailer.ts @@ -22,6 +22,8 @@ export const sendAdminEmail = async ({ return } + console.log("Nodemailer", nodemailer) + if (nodemailer) { return sendAdminEmailNodemailer({ subject, body }) } else { @@ -50,7 +52,7 @@ const sendAdminEmailNodemailer = async ({ !env.PRIVATE_SMTP_PASS ) { console.log( - "No smtp settings, not sending admil email. See CMSaasStarter setup instructions.", + "No smtp settings, not sending admin email. See CMSaasStarter setup instructions.", ) return } From f1e406732fb4fc1009ae05862bcfcbabb35af477 Mon Sep 17 00:00:00 2001 From: scosman Date: Thu, 25 Jul 2024 00:48:19 -0400 Subject: [PATCH 09/30] test CF --- src/lib/admin_mailer.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/admin_mailer.ts b/src/lib/admin_mailer.ts index 434a542b..2ac54a53 100644 --- a/src/lib/admin_mailer.ts +++ b/src/lib/admin_mailer.ts @@ -3,6 +3,7 @@ let nodemailer: typeof import("nodemailer") | undefined try { nodemailer = await import("nodemailer") } catch (e) { + console.log("nodemailer is not installed (Cloudflare Workers). Do nothing.", e) // nodemailer is not installed (Cloudflare Workers). Do nothing. } From 62f4df1641d5fc60f40f6c5aef0a26da4b50d0d4 Mon Sep 17 00:00:00 2001 From: scosman Date: Thu, 25 Jul 2024 00:57:16 -0400 Subject: [PATCH 10/30] test CF --- src/lib/admin_mailer.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/lib/admin_mailer.ts b/src/lib/admin_mailer.ts index 2ac54a53..37bda2a5 100644 --- a/src/lib/admin_mailer.ts +++ b/src/lib/admin_mailer.ts @@ -23,9 +23,11 @@ export const sendAdminEmail = async ({ return } - console.log("Nodemailer", nodemailer) + const canCreateTransport = nodemailer?.createTransport + console.log("ct1", canCreateTransport === undefined) + console.log("ct2", canCreateTransport) - if (nodemailer) { + if (canCreateTransport) { return sendAdminEmailNodemailer({ subject, body }) } else { return sendAdminEmailCloudflareWorkers({ subject, body }) From f0fc32a15ac8bcc9f54ec2f8a6adf38576ec5c1a Mon Sep 17 00:00:00 2001 From: scosman Date: Thu, 25 Jul 2024 01:03:21 -0400 Subject: [PATCH 11/30] test CF --- src/lib/admin_mailer.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/lib/admin_mailer.ts b/src/lib/admin_mailer.ts index 37bda2a5..5fffbf4c 100644 --- a/src/lib/admin_mailer.ts +++ b/src/lib/admin_mailer.ts @@ -3,7 +3,6 @@ let nodemailer: typeof import("nodemailer") | undefined try { nodemailer = await import("nodemailer") } catch (e) { - console.log("nodemailer is not installed (Cloudflare Workers). Do nothing.", e) // nodemailer is not installed (Cloudflare Workers). Do nothing. } @@ -23,14 +22,12 @@ export const sendAdminEmail = async ({ return } + // Chech if we have a valid nodemailer. const canCreateTransport = nodemailer?.createTransport - console.log("ct1", canCreateTransport === undefined) - console.log("ct2", canCreateTransport) - if (canCreateTransport) { - return sendAdminEmailNodemailer({ subject, body }) + return await sendAdminEmailNodemailer({ subject, body }) } else { - return sendAdminEmailCloudflareWorkers({ subject, body }) + return await sendAdminEmailCloudflareWorkers({ subject, body }) } } From 31071b115093cf97bb962d8e146da55c24c5fa31 Mon Sep 17 00:00:00 2001 From: scosman Date: Thu, 25 Jul 2024 01:05:12 -0400 Subject: [PATCH 12/30] test CF --- src/lib/admin_mailer.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/admin_mailer.ts b/src/lib/admin_mailer.ts index 5fffbf4c..78c8d754 100644 --- a/src/lib/admin_mailer.ts +++ b/src/lib/admin_mailer.ts @@ -123,6 +123,7 @@ const sendAdminEmailCloudflareWorkers = async ({ }) const response = await fetch(send_request) + console.log("MailChannels API response:", response) if (!response.ok) { console.log("Error sending admin email with MailChannels API", response) return From ebd2e1ef730b783c9778a7998da643ea76a7fb29 Mon Sep 17 00:00:00 2001 From: scosman Date: Thu, 25 Jul 2024 01:07:56 -0400 Subject: [PATCH 13/30] test CF --- src/lib/admin_mailer.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/lib/admin_mailer.ts b/src/lib/admin_mailer.ts index 78c8d754..0afc864c 100644 --- a/src/lib/admin_mailer.ts +++ b/src/lib/admin_mailer.ts @@ -27,6 +27,7 @@ export const sendAdminEmail = async ({ if (canCreateTransport) { return await sendAdminEmailNodemailer({ subject, body }) } else { + console.log("scf") return await sendAdminEmailCloudflareWorkers({ subject, body }) } } @@ -98,6 +99,7 @@ const sendAdminEmailCloudflareWorkers = async ({ subject: string body: string }) => { + console.log("cf2") const send_request = new Request("https://api.mailchannels.net/tx/v1/send", { method: "POST", headers: { @@ -122,6 +124,7 @@ const sendAdminEmailCloudflareWorkers = async ({ }), }) + console.log("cf3") const response = await fetch(send_request) console.log("MailChannels API response:", response) if (!response.ok) { From f5f8307cd0dc3d555df336dd43b3d805850e2a86 Mon Sep 17 00:00:00 2001 From: scosman Date: Thu, 25 Jul 2024 01:09:28 -0400 Subject: [PATCH 14/30] test CF --- src/routes/(admin)/account/api/+page.server.ts | 2 +- src/routes/(marketing)/contact_us/+page.server.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/routes/(admin)/account/api/+page.server.ts b/src/routes/(admin)/account/api/+page.server.ts index 27eb80d7..7fef2f13 100644 --- a/src/routes/(admin)/account/api/+page.server.ts +++ b/src/routes/(admin)/account/api/+page.server.ts @@ -256,7 +256,7 @@ export const actions = { }) } - sendAdminEmail({ + await sendAdminEmail({ subject: "Profile Updated", body: `Profile updated by ${session.user.email}\nFull name: ${fullName}\nCompany name: ${companyName}\nWebsite: ${website}`, }) diff --git a/src/routes/(marketing)/contact_us/+page.server.ts b/src/routes/(marketing)/contact_us/+page.server.ts index 681c4780..982142ca 100644 --- a/src/routes/(marketing)/contact_us/+page.server.ts +++ b/src/routes/(marketing)/contact_us/+page.server.ts @@ -70,7 +70,7 @@ export const actions = { } // Send email to admin - sendAdminEmail({ + await sendAdminEmail({ subject: "New contact request", body: `New contact request from ${firstName} ${lastName}.\n\nEmail: ${email}\n\nPhone: ${phone}\n\nCompany: ${company}\n\nMessage: ${message}`, }) From 07421de934e7cc2e894f1bedec4d1793d5f1f469 Mon Sep 17 00:00:00 2001 From: scosman Date: Thu, 25 Jul 2024 01:12:08 -0400 Subject: [PATCH 15/30] test CF --- src/lib/admin_mailer.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/lib/admin_mailer.ts b/src/lib/admin_mailer.ts index 0afc864c..83c6f94b 100644 --- a/src/lib/admin_mailer.ts +++ b/src/lib/admin_mailer.ts @@ -27,7 +27,6 @@ export const sendAdminEmail = async ({ if (canCreateTransport) { return await sendAdminEmailNodemailer({ subject, body }) } else { - console.log("scf") return await sendAdminEmailCloudflareWorkers({ subject, body }) } } @@ -99,7 +98,6 @@ const sendAdminEmailCloudflareWorkers = async ({ subject: string body: string }) => { - console.log("cf2") const send_request = new Request("https://api.mailchannels.net/tx/v1/send", { method: "POST", headers: { @@ -124,11 +122,9 @@ const sendAdminEmailCloudflareWorkers = async ({ }), }) - console.log("cf3") const response = await fetch(send_request) - console.log("MailChannels API response:", response) if (!response.ok) { - console.log("Error sending admin email with MailChannels API", response) + console.log("Error sending admin email with MailChannels API", JSON.stringify(response)) return } } From 97799c66d0edfe63690fc807bb0619c556927ca3 Mon Sep 17 00:00:00 2001 From: scosman Date: Thu, 25 Jul 2024 01:14:03 -0400 Subject: [PATCH 16/30] test CF --- src/lib/admin_mailer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/admin_mailer.ts b/src/lib/admin_mailer.ts index 83c6f94b..d916abd1 100644 --- a/src/lib/admin_mailer.ts +++ b/src/lib/admin_mailer.ts @@ -124,7 +124,7 @@ const sendAdminEmailCloudflareWorkers = async ({ const response = await fetch(send_request) if (!response.ok) { - console.log("Error sending admin email with MailChannels API", JSON.stringify(response)) + console.log("Error sending admin email with MailChannels API", JSON.stringify(response.body)) return } } From 237850b76afc6e72240f50376c0568a3f2b16695 Mon Sep 17 00:00:00 2001 From: scosman Date: Thu, 25 Jul 2024 01:16:43 -0400 Subject: [PATCH 17/30] test CF --- src/lib/admin_mailer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/admin_mailer.ts b/src/lib/admin_mailer.ts index d916abd1..6abcfa45 100644 --- a/src/lib/admin_mailer.ts +++ b/src/lib/admin_mailer.ts @@ -124,7 +124,7 @@ const sendAdminEmailCloudflareWorkers = async ({ const response = await fetch(send_request) if (!response.ok) { - console.log("Error sending admin email with MailChannels API", JSON.stringify(response.body)) + console.log("Error sending admin email with MailChannels API", response.body) return } } From accb4c42614acec59c2b45996f02794d04278bf5 Mon Sep 17 00:00:00 2001 From: scosman Date: Thu, 25 Jul 2024 01:19:31 -0400 Subject: [PATCH 18/30] test CF --- src/lib/admin_mailer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/admin_mailer.ts b/src/lib/admin_mailer.ts index 6abcfa45..f8413286 100644 --- a/src/lib/admin_mailer.ts +++ b/src/lib/admin_mailer.ts @@ -124,7 +124,7 @@ const sendAdminEmailCloudflareWorkers = async ({ const response = await fetch(send_request) if (!response.ok) { - console.log("Error sending admin email with MailChannels API", response.body) + console.log("Error sending admin email with MailChannels API", response.text()) return } } From b136506cec482066680a41f5bb9f31fcb6bfe627 Mon Sep 17 00:00:00 2001 From: scosman Date: Thu, 25 Jul 2024 01:21:12 -0400 Subject: [PATCH 19/30] test CF --- src/lib/admin_mailer.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib/admin_mailer.ts b/src/lib/admin_mailer.ts index f8413286..8703eddc 100644 --- a/src/lib/admin_mailer.ts +++ b/src/lib/admin_mailer.ts @@ -124,7 +124,8 @@ const sendAdminEmailCloudflareWorkers = async ({ const response = await fetch(send_request) if (!response.ok) { - console.log("Error sending admin email with MailChannels API", response.text()) + const issue = await response.text() + console.log("Error sending admin email with MailChannels API", issue) return } } From 428b3a5015b19e3b236b8791458d516cb6d94aa3 Mon Sep 17 00:00:00 2001 From: scosman Date: Thu, 25 Jul 2024 01:23:43 -0400 Subject: [PATCH 20/30] test CF --- src/lib/admin_mailer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/admin_mailer.ts b/src/lib/admin_mailer.ts index 8703eddc..652ea56b 100644 --- a/src/lib/admin_mailer.ts +++ b/src/lib/admin_mailer.ts @@ -110,7 +110,7 @@ const sendAdminEmailCloudflareWorkers = async ({ }, ], from: { - email: env.PRIVATE_ADMIN_EMAIL, + email: "noreply@you_domain_here_optional.com", }, subject: "ADMIN_MAIL: " + subject, content: [ From 468ef616e8cd198a6d2716a117200913f51ee5ff Mon Sep 17 00:00:00 2001 From: scosman Date: Thu, 25 Jul 2024 01:27:40 -0400 Subject: [PATCH 21/30] test CF --- README.md | 2 +- src/lib/admin_mailer.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2135d703..5ea442ed 100644 --- a/README.md +++ b/README.md @@ -277,7 +277,7 @@ SaaS Starter includes an admin emailer for sending yourself email notifications If you setup the admin emailer, it will email you when users create their profile or the 'Contact Us' form is submitted. You can add additional calls to sendAdminEmail() for any other events you want to monitor. -To setup, provide email SMTP credientials in your environment variables: `PRIVATE_SMTP_HOST`, `PRIVATE_SMTP_PORT`, `PRIVATE_SMTP_USER`, `PRIVATE_SMTP_PASS`. You can use any SMTP providers such as Gmail, Sendgrid, AWS SES, Resend, or Mailgun. Then set the email address to which admin emails will be sent in `PRIVATE_ADMIN_EMAIL`. +To setup, set the email address to which admin emails will be sent in the env var `PRIVATE_ADMIN_EMAIL`. That's all that's required if you host on Cloudflare Pages! If you host elsewhere, provide email SMTP credientials in your environment variables: `PRIVATE_SMTP_HOST`, `PRIVATE_SMTP_PORT`, `PRIVATE_SMTP_USER`, `PRIVATE_SMTP_PASS`. You can use any SMTP providers such as Gmail, Sendgrid, AWS SES, Resend, or Mailgun. ## Add Your Content diff --git a/src/lib/admin_mailer.ts b/src/lib/admin_mailer.ts index 652ea56b..0ddea64a 100644 --- a/src/lib/admin_mailer.ts +++ b/src/lib/admin_mailer.ts @@ -110,7 +110,8 @@ const sendAdminEmailCloudflareWorkers = async ({ }, ], from: { - email: "noreply@you_domain_here_optional.com", + // Can't spoof a lot of email addresses, so use a generic email address. + email: "noreply@your_domain_here_optional.com", }, subject: "ADMIN_MAIL: " + subject, content: [ From 552fe696e1b33bcd22d513a4aebdbc0c43224427 Mon Sep 17 00:00:00 2001 From: scosman Date: Thu, 25 Jul 2024 01:33:01 -0400 Subject: [PATCH 22/30] test CF --- src/lib/admin_mailer.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/lib/admin_mailer.ts b/src/lib/admin_mailer.ts index 0ddea64a..c9033efd 100644 --- a/src/lib/admin_mailer.ts +++ b/src/lib/admin_mailer.ts @@ -110,8 +110,7 @@ const sendAdminEmailCloudflareWorkers = async ({ }, ], from: { - // Can't spoof a lot of email addresses, so use a generic email address. - email: "noreply@your_domain_here_optional.com", + email: "noreply@saasstarter.pages.dev", }, subject: "ADMIN_MAIL: " + subject, content: [ From 84e1b1310ac45e2ff88eaa12610a423f9c3f0d6c Mon Sep 17 00:00:00 2001 From: scosman Date: Thu, 25 Jul 2024 01:37:23 -0400 Subject: [PATCH 23/30] test CF --- src/lib/admin_mailer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/admin_mailer.ts b/src/lib/admin_mailer.ts index c9033efd..89a3cb26 100644 --- a/src/lib/admin_mailer.ts +++ b/src/lib/admin_mailer.ts @@ -110,7 +110,7 @@ const sendAdminEmailCloudflareWorkers = async ({ }, ], from: { - email: "noreply@saasstarter.pages.dev", + email: "noreply@scosman.workers.dev", }, subject: "ADMIN_MAIL: " + subject, content: [ From 1f1a017d01a81617d1d911465565f1356d5eebb0 Mon Sep 17 00:00:00 2001 From: scosman Date: Thu, 25 Jul 2024 01:43:12 -0400 Subject: [PATCH 24/30] test CF --- src/lib/admin_mailer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/admin_mailer.ts b/src/lib/admin_mailer.ts index 89a3cb26..7dcda0f8 100644 --- a/src/lib/admin_mailer.ts +++ b/src/lib/admin_mailer.ts @@ -110,7 +110,7 @@ const sendAdminEmailCloudflareWorkers = async ({ }, ], from: { - email: "noreply@scosman.workers.dev", + email: "noreply@saasstarter.work", }, subject: "ADMIN_MAIL: " + subject, content: [ From 81585f754c174255a0853879dac6d514e8bb43b2 Mon Sep 17 00:00:00 2001 From: scosman Date: Thu, 25 Jul 2024 01:54:56 -0400 Subject: [PATCH 25/30] test CF --- src/lib/admin_mailer.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/lib/admin_mailer.ts b/src/lib/admin_mailer.ts index 7dcda0f8..30a9853f 100644 --- a/src/lib/admin_mailer.ts +++ b/src/lib/admin_mailer.ts @@ -123,9 +123,11 @@ const sendAdminEmailCloudflareWorkers = async ({ }) const response = await fetch(send_request) + const responseBody = await response.text() + console.log("MailChannels API response:", responseBody) if (!response.ok) { - const issue = await response.text() - console.log("Error sending admin email with MailChannels API", issue) + console.log("Error sending admin email with MailChannels API", responseBody) return } + } From 1568777498017c6d0ef378b211bcedabedde3338 Mon Sep 17 00:00:00 2001 From: scosman Date: Thu, 25 Jul 2024 02:01:29 -0400 Subject: [PATCH 26/30] test CF --- src/lib/admin_mailer.ts | 48 ++++------------------------------------- 1 file changed, 4 insertions(+), 44 deletions(-) diff --git a/src/lib/admin_mailer.ts b/src/lib/admin_mailer.ts index 30a9853f..a29be343 100644 --- a/src/lib/admin_mailer.ts +++ b/src/lib/admin_mailer.ts @@ -3,7 +3,7 @@ let nodemailer: typeof import("nodemailer") | undefined try { nodemailer = await import("nodemailer") } catch (e) { - // nodemailer is not installed (Cloudflare Workers). Do nothing. + nodemailer = undefined } import { env } from "$env/dynamic/private" @@ -27,7 +27,9 @@ export const sendAdminEmail = async ({ if (canCreateTransport) { return await sendAdminEmailNodemailer({ subject, body }) } else { - return await sendAdminEmailCloudflareWorkers({ subject, body }) + console.log( + "This environment does not support sending emails. Nodemailer requires node.js and doesn't work in environments like Cloudflare Workers.", + ) } } @@ -89,45 +91,3 @@ const sendAdminEmailNodemailer = async ({ console.log("Failed to send admin email, error:", e) } } - -// https://blog.cloudflare.com/sending-email-from-workers-with-mailchannels/ -const sendAdminEmailCloudflareWorkers = async ({ - subject, - body, -}: { - subject: string - body: string -}) => { - const send_request = new Request("https://api.mailchannels.net/tx/v1/send", { - method: "POST", - headers: { - "content-type": "application/json", - }, - body: JSON.stringify({ - personalizations: [ - { - to: [{ email: env.PRIVATE_ADMIN_EMAIL }], - }, - ], - from: { - email: "noreply@saasstarter.work", - }, - subject: "ADMIN_MAIL: " + subject, - content: [ - { - type: "text/plain", - value: body, - }, - ], - }), - }) - - const response = await fetch(send_request) - const responseBody = await response.text() - console.log("MailChannels API response:", responseBody) - if (!response.ok) { - console.log("Error sending admin email with MailChannels API", responseBody) - return - } - -} From 3b8defce0bee94bc3f2f407af3efce7e752de9b1 Mon Sep 17 00:00:00 2001 From: scosman Date: Thu, 25 Jul 2024 02:02:44 -0400 Subject: [PATCH 27/30] test CF --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 5ea442ed..55323a4a 100644 --- a/README.md +++ b/README.md @@ -277,6 +277,8 @@ SaaS Starter includes an admin emailer for sending yourself email notifications If you setup the admin emailer, it will email you when users create their profile or the 'Contact Us' form is submitted. You can add additional calls to sendAdminEmail() for any other events you want to monitor. +**Not supported on Cloudflare Workers**: the admin emailer is not supported on Cloudflare Workers as it does not have a full node.js environment. + To setup, set the email address to which admin emails will be sent in the env var `PRIVATE_ADMIN_EMAIL`. That's all that's required if you host on Cloudflare Pages! If you host elsewhere, provide email SMTP credientials in your environment variables: `PRIVATE_SMTP_HOST`, `PRIVATE_SMTP_PORT`, `PRIVATE_SMTP_USER`, `PRIVATE_SMTP_PASS`. You can use any SMTP providers such as Gmail, Sendgrid, AWS SES, Resend, or Mailgun. ## Add Your Content From 04be4e389dce6a7ff6f27b0a3e8fd0c554facc62 Mon Sep 17 00:00:00 2001 From: scosman Date: Thu, 25 Jul 2024 02:04:15 -0400 Subject: [PATCH 28/30] test CF --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 55323a4a..a41db5cd 100644 --- a/README.md +++ b/README.md @@ -279,7 +279,7 @@ If you setup the admin emailer, it will email you when users create their profil **Not supported on Cloudflare Workers**: the admin emailer is not supported on Cloudflare Workers as it does not have a full node.js environment. -To setup, set the email address to which admin emails will be sent in the env var `PRIVATE_ADMIN_EMAIL`. That's all that's required if you host on Cloudflare Pages! If you host elsewhere, provide email SMTP credientials in your environment variables: `PRIVATE_SMTP_HOST`, `PRIVATE_SMTP_PORT`, `PRIVATE_SMTP_USER`, `PRIVATE_SMTP_PASS`. You can use any SMTP providers such as Gmail, Sendgrid, AWS SES, Resend, or Mailgun. +To setup, first set the email address to which admin emails will be sent in the env var `PRIVATE_ADMIN_EMAIL`. Then provide email SMTP credientials in your environment variables: `PRIVATE_SMTP_HOST`, `PRIVATE_SMTP_PORT`, `PRIVATE_SMTP_USER`, `PRIVATE_SMTP_PASS`. You can use any SMTP providers such as Gmail, Sendgrid, AWS SES, Resend, or Mailgun. ## Add Your Content From 1fbd52583b67d0665037ea4e47aaae0ca630b965 Mon Sep 17 00:00:00 2001 From: scosman Date: Thu, 25 Jul 2024 02:06:33 -0400 Subject: [PATCH 29/30] test CF --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a41db5cd..4b9b0322 100644 --- a/README.md +++ b/README.md @@ -279,7 +279,7 @@ If you setup the admin emailer, it will email you when users create their profil **Not supported on Cloudflare Workers**: the admin emailer is not supported on Cloudflare Workers as it does not have a full node.js environment. -To setup, first set the email address to which admin emails will be sent in the env var `PRIVATE_ADMIN_EMAIL`. Then provide email SMTP credientials in your environment variables: `PRIVATE_SMTP_HOST`, `PRIVATE_SMTP_PORT`, `PRIVATE_SMTP_USER`, `PRIVATE_SMTP_PASS`. You can use any SMTP providers such as Gmail, Sendgrid, AWS SES, Resend, or Mailgun. +To setup, first set the email address to which admin emails will be sent in the env var `PRIVATE_ADMIN_EMAIL`. Then provide email SMTP credientials in your environment variables: `PRIVATE_SMTP_HOST`, `PRIVATE_SMTP_PORT`, `PRIVATE_SMTP_USER`, `PRIVATE_SMTP_PASS`. You can use any SMTP providers such as Gmail, Sendgrid, AWS SES, Resend, or Mailgun. ## Add Your Content From 6eb33691427a757fdf2d6b097edb3d4881a93568 Mon Sep 17 00:00:00 2001 From: scosman Date: Thu, 25 Jul 2024 02:30:19 -0400 Subject: [PATCH 30/30] test CF --- README.md | 8 +- local_env_template | 10 +- package-lock.json | 461 ++++++++++++++++++++++++++++++++++------ package.json | 10 +- src/lib/admin_mailer.ts | 76 +------ 5 files changed, 419 insertions(+), 146 deletions(-) diff --git a/README.md b/README.md index 4b9b0322..fcc2eb1d 100644 --- a/README.md +++ b/README.md @@ -275,11 +275,13 @@ If you prefer another host you can explore alternatives: SaaS Starter includes an admin emailer for sending yourself email notifications when important events happen. This let's you monitor your app and respond to users without watching the database. -If you setup the admin emailer, it will email you when users create their profile or the 'Contact Us' form is submitted. You can add additional calls to sendAdminEmail() for any other events you want to monitor. +If you setup the admin emailer, it will email you when users create their profile, or when the 'Contact Us' form is submitted. You can add additional calls to sendAdminEmail() for any other events you want to monitor. -**Not supported on Cloudflare Workers**: the admin emailer is not supported on Cloudflare Workers as it does not have a full node.js environment. +To setup, set these environment variables: -To setup, first set the email address to which admin emails will be sent in the env var `PRIVATE_ADMIN_EMAIL`. Then provide email SMTP credientials in your environment variables: `PRIVATE_SMTP_HOST`, `PRIVATE_SMTP_PORT`, `PRIVATE_SMTP_USER`, `PRIVATE_SMTP_PASS`. You can use any SMTP providers such as Gmail, Sendgrid, AWS SES, Resend, or Mailgun. +- `PRIVATE_ADMIN_EMAIL`: the email address to which admin emails will be sent. +- `PRIVATE_FROM_ADMIN_EMAIL`: the email address to use as the from address for admin emails (defaults to `PRIVATE_ADMIN_EMAIL` if not set). +- `PRIVATE_RESEND_API_KEY`: a https://resend.com API key. Note, you'll need to verify your domain with them before you can use their service. ## Add Your Content diff --git a/local_env_template b/local_env_template index 667774aa..b2a9de9c 100644 --- a/local_env_template +++ b/local_env_template @@ -6,9 +6,7 @@ PRIVATE_SUPABASE_SERVICE_ROLE='REPLACE_ME' # Stripe settings PRIVATE_STRIPE_API_KEY='REPLACE_ME' -# SMTP settings for email - optional -# PRIVATE_ADMIN_EMAIL='your_email@example.com' # see lib/admin_mailer.ts -# PRIVATE_SMTP_HOST='REPLACE_ME' -# PRIVATE_SMTP_PORT='587' -# PRIVATE_SMTP_USER='REPLACE_ME' -# PRIVATE_SMTP_PASS='REPLACE_ME' \ No newline at end of file +# settings for email - optional +# PRIVATE_ADMIN_EMAIL='your_email@example.com' +# PRIVATE_FROM_ADMIN_EMAIL='REPLACE_ME' +# PRIVATE_RESEND_API_KEY='REPLACE_ME' \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index b9a7df9f..1dfb731c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,8 +11,7 @@ "@supabase/auth-helpers-sveltekit": "^0.11.0", "@supabase/auth-ui-svelte": "^0.2.9", "@supabase/supabase-js": "^2.33.0", - "@types/nodemailer": "^6.4.15", - "nodemailer": "^6.9.14", + "resend": "^3.5.0", "stripe": "^13.3.0" }, "devDependencies": { @@ -544,7 +543,6 @@ "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", @@ -561,7 +559,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "dev": true, "engines": { "node": ">=12" }, @@ -573,7 +570,6 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, "dependencies": { "ansi-regex": "^6.0.1" }, @@ -674,11 +670,15 @@ "node": ">= 8" } }, + "node_modules/@one-ini/wasm": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@one-ini/wasm/-/wasm-0.1.1.tgz", + "integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==" + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, "optional": true, "engines": { "node": ">=14" @@ -689,6 +689,23 @@ "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.25.tgz", "integrity": "sha512-j7P6Rgr3mmtdkeDGTe0E/aYyWEWVtc5yFXtHCRHs28/jptDEWfaVOc5T7cblqy1XKPPfCxJc/8DwQ5YgLOZOVQ==" }, + "node_modules/@react-email/render": { + "version": "0.0.16", + "resolved": "https://registry.npmjs.org/@react-email/render/-/render-0.0.16.tgz", + "integrity": "sha512-wDaMy27xAq1cJHtSFptp0DTKPuV2GYhloqia95ub/DH9Dea1aWYsbdM918MOc/b/HvVS3w1z8DWzfAk13bGStQ==", + "dependencies": { + "html-to-text": "9.0.5", + "js-beautify": "^1.14.11", + "react-promise-suspense": "0.3.4" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "react": "^18.2.0", + "react-dom": "^18.2.0" + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.18.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.0.tgz", @@ -881,6 +898,18 @@ "win32" ] }, + "node_modules/@selderee/plugin-htmlparser2": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@selderee/plugin-htmlparser2/-/plugin-htmlparser2-0.11.0.tgz", + "integrity": "sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==", + "dependencies": { + "domhandler": "^5.0.3", + "selderee": "^0.11.0" + }, + "funding": { + "url": "https://ko-fi.com/killymxi" + } + }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", @@ -1130,14 +1159,6 @@ "undici-types": "~5.26.4" } }, - "node_modules/@types/nodemailer": { - "version": "6.4.15", - "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.15.tgz", - "integrity": "sha512-0EBJxawVNjPkng1zm2vopRctuWVCxk34JcIlRuXSf54habUWdz1FB7wHDqOqvDa8Mtpt0Q3LTXQkAs2LNyK5jQ==", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/phoenix": { "version": "1.6.4", "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.4.tgz", @@ -1455,6 +1476,14 @@ "url": "https://opencollective.com/vitest" } }, + "node_modules/abbrev": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", + "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/acorn": { "version": "8.11.3", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", @@ -1504,7 +1533,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "engines": { "node": ">=8" } @@ -1513,7 +1541,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -1629,8 +1656,7 @@ "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "node_modules/binary-extensions": { "version": "2.3.0", @@ -1648,7 +1674,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, "dependencies": { "balanced-match": "^1.0.0" } @@ -1869,7 +1894,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "dependencies": { "color-name": "~1.1.4" }, @@ -1880,8 +1904,7 @@ "node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "node_modules/commander": { "version": "4.1.1", @@ -1904,6 +1927,15 @@ "integrity": "sha512-uJcB/FKZtBMCJpK8MQji6bJHgu1tixKPxRLeGkNzBoOZzpnZUJm0jm2/sBDWcuBx1dYgxV4JU+g5hmNxCyAmdA==", "dev": true }, + "node_modules/config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "dependencies": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, "node_modules/cookie": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", @@ -1916,7 +1948,6 @@ "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -2113,11 +2144,100 @@ "node": ">=6.0.0" } }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" + }, + "node_modules/editorconfig": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-1.0.4.tgz", + "integrity": "sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==", + "dependencies": { + "@one-ini/wasm": "0.1.1", + "commander": "^10.0.0", + "minimatch": "9.0.1", + "semver": "^7.5.3" + }, + "bin": { + "editorconfig": "bin/editorconfig" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/editorconfig/node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "engines": { + "node": ">=14" + } + }, + "node_modules/editorconfig/node_modules/minimatch": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz", + "integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } }, "node_modules/electron-to-chromium": { "version": "1.4.777", @@ -2128,8 +2248,18 @@ "node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } }, "node_modules/es-define-property": { "version": "1.0.0", @@ -2614,7 +2744,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", - "dev": true, "dependencies": { "cross-spawn": "^7.0.0", "signal-exit": "^4.0.1" @@ -2880,6 +3009,39 @@ "node": ">= 0.4" } }, + "node_modules/html-to-text": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/html-to-text/-/html-to-text-9.0.5.tgz", + "integrity": "sha512-qY60FjREgVZL03vJU6IfMV4GDjGBIoOyvuFdpBDIX9yTlDw0TjxVBQp+P8NvpdIXNJvfWBTNul7fsAQJq2FNpg==", + "dependencies": { + "@selderee/plugin-htmlparser2": "^0.11.0", + "deepmerge": "^4.3.1", + "dom-serializer": "^2.0.0", + "htmlparser2": "^8.0.2", + "selderee": "^0.11.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/htmlparser2": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", + "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "entities": "^4.4.0" + } + }, "node_modules/human-signals": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", @@ -2948,6 +3110,11 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" + }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -2985,7 +3152,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, "engines": { "node": ">=8" } @@ -3043,14 +3209,12 @@ "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" }, "node_modules/jackspeak": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.1.2.tgz", "integrity": "sha512-kWmLKn2tRtfYMF/BakihVVRzBKOxz4gJMiL2Rj91WnAB5TPZumSH99R/Yf1qE1u4uRimvCSJfm6hnxohXeEXjQ==", - "dev": true, "dependencies": { "@isaacs/cliui": "^8.0.2" }, @@ -3081,6 +3245,67 @@ "url": "https://github.com/sponsors/panva" } }, + "node_modules/js-beautify": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.15.1.tgz", + "integrity": "sha512-ESjNzSlt/sWE8sciZH8kBF8BPlwXPwhR6pWKAw8bw4Bwj+iZcnKW6ONWUutJ7eObuBZQpiIb8S7OYspWrKt7rA==", + "dependencies": { + "config-chain": "^1.1.13", + "editorconfig": "^1.0.4", + "glob": "^10.3.3", + "js-cookie": "^3.0.5", + "nopt": "^7.2.0" + }, + "bin": { + "css-beautify": "js/bin/css-beautify.js", + "html-beautify": "js/bin/html-beautify.js", + "js-beautify": "js/bin/js-beautify.js" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/js-beautify/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/js-beautify/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/js-cookie": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", + "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", + "engines": { + "node": ">=14" + } + }, "node_modules/js-tokens": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.0.tgz", @@ -3140,6 +3365,14 @@ "integrity": "sha512-sBPIUGTNF0czz0mwGGUoKKJC8Q7On1GPbCSFPfyEsfHb2DyBG0Y4QtV+EVWpINSaiGKZblDNuF5AezxSgOhesQ==", "dev": true }, + "node_modules/leac": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/leac/-/leac-0.6.0.tgz", + "integrity": "sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==", + "funding": { + "url": "https://ko-fi.com/killymxi" + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -3222,6 +3455,24 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "peer": true, + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/loose-envify/node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "peer": true + }, "node_modules/loupe": { "version": "2.3.7", "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", @@ -3235,7 +3486,6 @@ "version": "10.2.2", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz", "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==", - "dev": true, "engines": { "node": "14 || >=16.14" } @@ -3327,10 +3577,9 @@ } }, "node_modules/minipass": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.1.tgz", - "integrity": "sha512-UZ7eQ+h8ywIRAW1hIEl2AqdwzJucU/Kp59+8kkZeSvafXhZjul247BvIJjEVFVeON6d7lM46XX1HXCduKAS8VA==", - "dev": true, + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", "engines": { "node": ">=16 || 14 >=14.17" } @@ -3420,6 +3669,20 @@ "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", "dev": true }, + "node_modules/nopt": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz", + "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==", + "dependencies": { + "abbrev": "^2.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -3562,6 +3825,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", + "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -3574,6 +3842,18 @@ "node": ">=6" } }, + "node_modules/parseley": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/parseley/-/parseley-0.12.1.tgz", + "integrity": "sha512-e6qHKe3a9HWr0oMRVDTRhKce+bRO8VGQR3NyVwcjwrbhMmFCX9KszEV35+rn4AdilFAq9VPxP/Fe1wC9Qjd2lw==", + "dependencies": { + "leac": "^0.6.0", + "peberminta": "^0.9.0" + }, + "funding": { + "url": "https://ko-fi.com/killymxi" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -3596,7 +3876,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, "engines": { "node": ">=8" } @@ -3611,7 +3890,6 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "dev": true, "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" @@ -3647,6 +3925,14 @@ "node": "*" } }, + "node_modules/peberminta": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/peberminta/-/peberminta-0.9.0.tgz", + "integrity": "sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ==", + "funding": { + "url": "https://ko-fi.com/killymxi" + } + }, "node_modules/periscopic": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/periscopic/-/periscopic-3.1.0.tgz", @@ -3948,6 +4234,11 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==" + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -3991,12 +4282,50 @@ } ] }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, "node_modules/react-is": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "dev": true }, + "node_modules/react-promise-suspense": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/react-promise-suspense/-/react-promise-suspense-0.3.4.tgz", + "integrity": "sha512-I42jl7L3Ze6kZaq+7zXWSunBa3b1on5yfvUW6Eo/3fFOj6dZ5Bqmcd264nJbTK/gn1HjjILAjSwnZbV4RpSaNQ==", + "dependencies": { + "fast-deep-equal": "^2.0.1" + } + }, + "node_modules/react-promise-suspense/node_modules/fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w==" + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -4018,6 +4347,17 @@ "node": ">=8.10.0" } }, + "node_modules/resend": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/resend/-/resend-3.5.0.tgz", + "integrity": "sha512-bKu4LhXSecP6krvhfDzyDESApYdNfjirD5kykkT1xO0Cj9TKSiGh5Void4pGTs3Am+inSnp4dg0B5XzdwHBJOQ==", + "dependencies": { + "@react-email/render": "0.0.16" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", @@ -4161,11 +4501,30 @@ "rimraf": "bin.js" } }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/selderee": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/selderee/-/selderee-0.11.0.tgz", + "integrity": "sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA==", + "dependencies": { + "parseley": "^0.12.0" + }, + "funding": { + "url": "https://ko-fi.com/killymxi" + } + }, "node_modules/semver": { "version": "7.6.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", - "dev": true, "bin": { "semver": "bin/semver.js" }, @@ -4198,7 +4557,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "dependencies": { "shebang-regex": "^3.0.0" }, @@ -4210,7 +4568,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, "engines": { "node": ">=8" } @@ -4242,7 +4599,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, "engines": { "node": ">=14" }, @@ -4311,7 +4667,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", @@ -4329,7 +4684,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -4342,14 +4696,12 @@ "node_modules/string-width-cjs/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "node_modules/string-width/node_modules/ansi-regex": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "dev": true, "engines": { "node": ">=12" }, @@ -4361,7 +4713,6 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, "dependencies": { "ansi-regex": "^6.0.1" }, @@ -4376,7 +4727,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -4389,7 +4739,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -5157,7 +5506,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "dependencies": { "isexe": "^2.0.0" }, @@ -5197,7 +5545,6 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", @@ -5215,7 +5562,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -5231,14 +5577,12 @@ "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "node_modules/wrap-ansi-cjs/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -5252,7 +5596,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "dev": true, "engines": { "node": ">=12" }, @@ -5264,7 +5607,6 @@ "version": "6.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, "engines": { "node": ">=12" }, @@ -5276,7 +5618,6 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, "dependencies": { "ansi-regex": "^6.0.1" }, diff --git a/package.json b/package.json index 720c7e1f..e88e7617 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,6 @@ "@tailwindcss/typography": "^0.5.13", "@typescript-eslint/eslint-plugin": "^6.20.0", "@typescript-eslint/parser": "^6.19.0", - "@types/nodemailer": "^6.4.15", "autoprefixer": "^10.4.15", "daisyui": "^4.7.3", "eslint": "^8.28.0", @@ -42,14 +41,7 @@ "@supabase/auth-helpers-sveltekit": "^0.11.0", "@supabase/auth-ui-svelte": "^0.2.9", "@supabase/supabase-js": "^2.33.0", + "resend": "^3.5.0", "stripe": "^13.3.0" - }, - "peerDependencies": { - "nodemailer": "^6.9.14" - }, - "peerDependenciesMeta": { - "nodemailer": { - "optional": true - } } } diff --git a/src/lib/admin_mailer.ts b/src/lib/admin_mailer.ts index a29be343..589a1ed3 100644 --- a/src/lib/admin_mailer.ts +++ b/src/lib/admin_mailer.ts @@ -1,11 +1,4 @@ -// Optional dependency, only used if platform suppport node.js (not Cloudflare Workers). -let nodemailer: typeof import("nodemailer") | undefined -try { - nodemailer = await import("nodemailer") -} catch (e) { - nodemailer = undefined -} - +import { Resend } from "resend" import { env } from "$env/dynamic/private" // Sends an email to the admin email address. @@ -17,75 +10,22 @@ export const sendAdminEmail = async ({ subject: string body: string }) => { - // Check admin email is set. + // Check admin email is setup if (!env.PRIVATE_ADMIN_EMAIL) { return } - // Chech if we have a valid nodemailer. - const canCreateTransport = nodemailer?.createTransport - if (canCreateTransport) { - return await sendAdminEmailNodemailer({ subject, body }) - } else { - console.log( - "This environment does not support sending emails. Nodemailer requires node.js and doesn't work in environments like Cloudflare Workers.", - ) - } -} - -const sendAdminEmailNodemailer = async ({ - subject, - body, -}: { - subject: string - body: string -}) => { - if (!nodemailer) { - console.log( - "This environment does not support sending emails. Nodemailer requires node.js and doesn't work in environments like Cloudflare Workers.", - ) - return - } - - // Check if smtp settings are set. - if ( - !env.PRIVATE_SMTP_HOST || - !env.PRIVATE_SMTP_USER || - !env.PRIVATE_SMTP_PASS - ) { - console.log( - "No smtp settings, not sending admin email. See CMSaasStarter setup instructions.", - ) - return - } - - // Default to port 587 if not set. - let port = 587 - if (env.PRIVATE_SMTP_PORT) { - port = parseInt(env.PRIVATE_SMTP_PORT) - } - try { - const transporter = nodemailer.createTransport({ - host: env.PRIVATE_SMTP_HOST, - port: port, - secure: port === 465, // https://nodemailer.com/smtp/ - requireTLS: true, // Email should be encrypted in 2024 - auth: { - user: env.PRIVATE_SMTP_USER, - pass: env.PRIVATE_SMTP_PASS, - }, - }) - - const info = await transporter.sendMail({ - from: env.PRIVATE_ADMIN_EMAIL, - to: env.PRIVATE_ADMIN_EMAIL, + const resend = new Resend(env.PRIVATE_RESEND_API_KEY) + const resp = await resend.emails.send({ + from: env.PRIVATE_FROM_ADMIN_EMAIL || env.PRIVATE_ADMIN_EMAIL, + to: [env.PRIVATE_ADMIN_EMAIL], subject: "ADMIN_MAIL: " + subject, text: body, }) - if (info.rejected && info.rejected.length > 0) { - console.log("Failed to send admin email, rejected:", info.rejected) + if (resp.error) { + console.log("Failed to send admin email, error:", resp.error) } } catch (e) { console.log("Failed to send admin email, error:", e)