diff --git a/package.json b/package.json
index 25da85f..b7dd235 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "siteforge",
"private": true,
- "version": "1.0.10",
+ "version": "1.0.11",
"type": "module",
"scripts": {
"dev": "vite",
diff --git a/server/email.js b/server/email.js
index 2c649d6..dd516db 100644
--- a/server/email.js
+++ b/server/email.js
@@ -386,11 +386,19 @@ export function buildAdminReplyTemplate({ recipientName, message }) {
}
export function buildQuestionAnsweredEmailTemplate({ firstName, question, answer, questionUrl }) {
+ const cfg = state.cachedSiteContent ?? {}
+ const subject = cfg.qaAnsweredEmailSubject?.trim() || 'Your question has been answered — Verse by Verse with Nate'
+ const bodyText = cfg.qaAnsweredEmailBody?.trim() || 'Nate has answered your question on Verse by Verse with Nate.'
+ const ctaLabel = cfg.qaAnsweredEmailCtaLabel?.trim() || 'Read Full Answer →'
+ const signoff = cfg.qaAnsweredEmailSignoff?.trim() || 'Grace and peace,\nNate'
+
const safeName = escapeHtml(firstName || 'friend')
const safeQuestion = escapeHtml(question)
const safeAnswer = escapeHtml(answer.length > 600 ? answer.slice(0, 597) + '…' : answer).replace(/\n/g, '
')
const safeUrl = escapeHtml(questionUrl)
- const text = `Hi ${firstName || 'friend'},\n\nYour question has been answered on Verse by Verse with Nate.\n\nYour question: ${question}\n\nAnswer: ${answer}\n\nRead it at: ${questionUrl}\n\nGrace and peace,\nNate`
+ const safeBody = escapeHtml(bodyText)
+ const safeSignoff = escapeHtml(signoff).replace(/\n/g, '
')
+ const text = `Hi ${firstName || 'friend'},\n\n${bodyText}\n\nYour question: ${question}\n\nAnswer: ${answer}\n\nRead it at: ${questionUrl}\n\n${signoff}`
const html = `
@@ -406,7 +414,7 @@ export function buildQuestionAnsweredEmailTemplate({ firstName, question, answer
|
Hi ${safeName},
- Nate has answered your question on Verse by Verse with Nate.
+ ${safeBody}
Your question
${safeQuestion}
@@ -415,11 +423,12 @@ export function buildQuestionAnsweredEmailTemplate({ firstName, question, answer
Answer
${safeAnswer}
- Read Full Answer →
+ ${escapeHtml(ctaLabel)}
|
|
+ ${safeSignoff}
You received this because you requested a notification when this question was answered.
|
@@ -429,7 +438,7 @@ export function buildQuestionAnsweredEmailTemplate({ firstName, question, answer
`
- return { subject: 'Your question has been answered — Verse by Verse with Nate', text, html }
+ return { subject, text, html }
}
// ── Transactional email senders ────────────────────────────────────────────
diff --git a/src/AdminPage.tsx b/src/AdminPage.tsx
index f686789..1e13f7c 100644
--- a/src/AdminPage.tsx
+++ b/src/AdminPage.tsx
@@ -482,6 +482,12 @@ function EmailTemplatesPanel({
fieldKeys={['twoFaOtpEmailSubject','twoFaOtpEmailBody','twoFaOtpEmailExpiry']}
openKey="2fa-otp" />
+
+
{renderSaveStatus()}
)
@@ -990,6 +996,10 @@ const FIELDS: Array<{ key: StringField; label: string; multiline?: boolean; sect
{ key: 'twoFaOtpEmailSubject', label: 'Subject Line', section: 'email-templates' },
{ key: 'twoFaOtpEmailBody', label: 'Body Text (shown above the code)', multiline: true, section: 'email-templates' },
{ key: 'twoFaOtpEmailExpiry', label: 'Expiry / Security Note (shown below the code)', multiline: true, section: 'email-templates' },
+ { key: 'qaAnsweredEmailSubject', label: 'Subject Line', section: 'email-templates' },
+ { key: 'qaAnsweredEmailBody', label: 'Body Text (paragraph shown below the greeting)', multiline: true, section: 'email-templates' },
+ { key: 'qaAnsweredEmailCtaLabel', label: 'Button Label', section: 'email-templates' },
+ { key: 'qaAnsweredEmailSignoff', label: 'Signoff', multiline: true, section: 'email-templates' },
]
function normalizeStudies(siteContent: SiteContent): StudyProgram[] {
diff --git a/src/content.ts b/src/content.ts
index 542b18c..b5bea26 100644
--- a/src/content.ts
+++ b/src/content.ts
@@ -239,6 +239,11 @@ export interface SiteContent {
twoFaOtpEmailSubject: string
twoFaOtpEmailBody: string
twoFaOtpEmailExpiry: string
+ // ── Q&A Answered Notification ──
+ qaAnsweredEmailSubject: string
+ qaAnsweredEmailBody: string
+ qaAnsweredEmailCtaLabel: string
+ qaAnsweredEmailSignoff: string
// ── Header ──
headerFollowLabel: string
// ── Cookie banner ──
@@ -430,6 +435,10 @@ export const DEFAULTS: SiteContent = {
twoFaOtpEmailSubject: 'Your sign-in code — Verse by Verse with Nate',
twoFaOtpEmailBody: 'Your two-factor sign-in code is below. Enter it to complete sign-in.',
twoFaOtpEmailExpiry: 'This code expires in 10 minutes. If you did not request this, you can ignore this message.',
+ qaAnsweredEmailSubject: 'Your question has been answered — Verse by Verse with Nate',
+ qaAnsweredEmailBody: 'Nate has answered your question on Verse by Verse with Nate.',
+ qaAnsweredEmailCtaLabel: 'Read Full Answer →',
+ qaAnsweredEmailSignoff: 'Grace and peace,\nNate',
headerFollowLabel: 'Follow on Spotify',
cookieBannerText: 'We use optional analytics cookies to better understand how people find and engage with this content. By accepting, you allow us to measure how you interact with the site — no personal data is sold or shared.',
customLinks: [],