diff --git a/package.json b/package.json
index dd5d1b3..25da85f 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "siteforge",
"private": true,
- "version": "1.0.9",
+ "version": "1.0.10",
"type": "module",
"scripts": {
"dev": "vite",
diff --git a/server/email.js b/server/email.js
index 72b9300..2c649d6 100644
--- a/server/email.js
+++ b/server/email.js
@@ -385,6 +385,53 @@ export function buildAdminReplyTemplate({ recipientName, message }) {
`
}
+export function buildQuestionAnsweredEmailTemplate({ firstName, question, answer, questionUrl }) {
+ 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 html = `
+
+
+
+
+
+
+ |
+ Verse by Verse with Nate
+ Your Question Was Answered
+ |
+
+
+ |
+ Hi ${safeName},
+ Nate has answered your question on Verse by Verse with Nate.
+
+ Your question
+ ${safeQuestion}
+
+
+ Answer
+ ${safeAnswer}
+
+ Read Full Answer →
+ |
+
+
+ |
+ You received this because you requested a notification when this question was answered.
+ |
+
+
+ |
+
+
+
+ `
+ return { subject: 'Your question has been answered — Verse by Verse with Nate', text, html }
+}
+
// ── Transactional email senders ────────────────────────────────────────────
export async function sendStudyWelcomeEmail(email, displayName) {
diff --git a/server/routes/contact.js b/server/routes/contact.js
index d7e0e9f..2ac8808 100644
--- a/server/routes/contact.js
+++ b/server/routes/contact.js
@@ -102,7 +102,7 @@ function contactRateLimit(req, res, next) {
export function register(app) {
app.post('/api/contact', contactRateLimit, async (req, res) => {
try {
- const { firstName, lastName, email, message, messageType, subscribe, _honey } = req.body ?? {}
+ const { firstName, lastName, email, message, messageType, subscribe, notifyOnAnswer, _honey } = req.body ?? {}
if (_honey) { res.json({ ok: true }); return }
@@ -164,6 +164,7 @@ export function register(app) {
answeredAt: null,
isApproved: false,
approvedAt: null,
+ notifyOnAnswer: notifyOnAnswer === true,
}
state.questions.unshift(question)
state.questions = state.questions.slice(0, MAX_QUESTIONS)
diff --git a/server/routes/questions.js b/server/routes/questions.js
index 4fe32d6..4eda9a2 100644
--- a/server/routes/questions.js
+++ b/server/routes/questions.js
@@ -3,6 +3,9 @@ import { requireAdminAuth } from '../auth.js'
import { MAX_QUESTIONS } from '../config.js'
import { state } from '../state.js'
import { queueQuestionsWrite, queueDraftQuestionsWrite } from '../data.js'
+import { buildQuestionAnsweredEmailTemplate, getResendFromAddress, getResendReplyToAddress, sendResendEmailWithRetry, getCanonicalBaseUrl } from '../email.js'
+import { buildAbsoluteUrl } from '../helpers.js'
+import { Resend } from 'resend'
function ensureDraftQuestions() {
if (state.draftQuestions !== null) return
@@ -103,6 +106,29 @@ export function register(app) {
question.approvedAt = approved === true ? new Date().toISOString() : null
queueDraftQuestionsWrite()
+ if (approved === true && question.notifyOnAnswer && question.email && question.answer) {
+ try {
+ const resend = new Resend(process.env.RESEND_API_KEY)
+ const from = getResendFromAddress()
+ const replyTo = getResendReplyToAddress()
+ const baseUrl = getCanonicalBaseUrl()
+ const questionUrl = buildAbsoluteUrl(baseUrl, `/questions#qa-${question.id}`)
+ const template = buildQuestionAnsweredEmailTemplate({
+ firstName: question.firstName,
+ question: question.question,
+ answer: question.answer,
+ questionUrl,
+ })
+ await sendResendEmailWithRetry({
+ resend,
+ payload: { from, replyTo, to: question.email, subject: template.subject, text: template.text, html: template.html },
+ context: `notify-on-answer:${question.id}`,
+ })
+ } catch (err) {
+ console.error('[notify-on-answer] Failed to send notification email:', err?.message ?? err)
+ }
+ }
+
res.json({ ok: true, question })
})
diff --git a/src/components/ContactForm.tsx b/src/components/ContactForm.tsx
index f5904e1..741cf9f 100644
--- a/src/components/ContactForm.tsx
+++ b/src/components/ContactForm.tsx
@@ -36,6 +36,7 @@ export default function ContactForm() {
}
}, [source, studyName])
const [subscribe, setSubscribe] = useState(true)
+ const [notifyOnAnswer, setNotifyOnAnswer] = useState(true)
const [honey, setHoney] = useState('')
const [status, setStatus] = useState<'idle' | 'submitting' | 'error'>('idle')
const [errorMsg, setErrorMsg] = useState('')
@@ -71,7 +72,7 @@ export default function ContactForm() {
const res = await fetch('/api/contact', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ ...fields, subscribe, _honey: honey }),
+ body: JSON.stringify({ ...fields, subscribe, notifyOnAnswer: fields.messageType === 'question' ? notifyOnAnswer : false, _honey: honey }),
})
if (!res.ok) {
@@ -135,6 +136,16 @@ export default function ContactForm() {
Message
+ {fields.messageType === 'question' && (
+
+ )}