Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add admin mailer. #103

Merged
merged 30 commits into from
Jul 25, 2024
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,14 @@ 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
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix grammatical error: "Setup" should be "Set up".

The word "setup" is a noun. The verb form is "set up".

-## Setup Admin Emailer -- Optional
+## Set Up Admin Emailer -- Optional
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
## Setup Admin Emailer -- Optional
## Set Up Admin Emailer -- Optional


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.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix grammatical error: "setup" should be "set up".

The word "setup" is a noun. The verb form is "set up".

-If you setup the admin emailer, it will email you when users create their profile, or when the 'Contact Us' form is submitted.
+If you set up the admin emailer, it will email you when users create their profile, or when the 'Contact Us' form is submitted.

Committable suggestion was skipped due to low confidence.


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, 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

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.
Expand Down
10 changes: 10 additions & 0 deletions local_env_template
Original file line number Diff line number Diff line change
@@ -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='[email protected]' # see lib/admin_mailer.ts
# PRIVATE_SMTP_HOST='REPLACE_ME'
# PRIVATE_SMTP_PORT='587'
# PRIVATE_SMTP_USER='REPLACE_ME'
# PRIVATE_SMTP_PASS='REPLACE_ME'
10 changes: 10 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -42,5 +43,13 @@
"@supabase/auth-ui-svelte": "^0.2.9",
"@supabase/supabase-js": "^2.33.0",
"stripe": "^13.3.0"
},
"peerDependencies": {
"nodemailer": "^6.9.14"
},
"peerDependenciesMeta": {
"nodemailer": {
"optional": true
}
}
}
133 changes: 133 additions & 0 deletions src/lib/admin_mailer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
// 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 is not installed (Cloudflare Workers). Do nothing.
}

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
}

// Chech if we have a valid nodemailer.
const canCreateTransport = nodemailer?.createTransport
if (canCreateTransport) {
return await sendAdminEmailNodemailer({ subject, body })
} else {
return await 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.",
)
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,
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)
}
}

// 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: "[email protected]",
},
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
}

}
6 changes: 6 additions & 0 deletions src/routes/(admin)/account/api/+page.server.ts
Original file line number Diff line number Diff line change
@@ -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 } }) => {
Expand Down Expand Up @@ -255,6 +256,11 @@ export const actions = {
})
}

await sendAdminEmail({
subject: "Profile Updated",
body: `Profile updated by ${session.user.email}\nFull name: ${fullName}\nCompany name: ${companyName}\nWebsite: ${website}`,
})

return {
fullName,
companyName,
Expand Down
7 changes: 7 additions & 0 deletions src/routes/(marketing)/contact_us/+page.server.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { fail } from "@sveltejs/kit"
import { sendAdminEmail } from "$lib/admin_mailer.js"

/** @type {import('./$types').Actions} */
export const actions = {
Expand Down Expand Up @@ -67,5 +68,11 @@ export const actions = {
if (insertError) {
return fail(500, { errors: { _: "Error saving" } })
}

// Send email to admin
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}`,
})
},
}
Loading