diff --git a/package.json b/package.json
index 1001c0c..08ec5b8 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "siteforge",
"private": true,
- "version": "1.1.19",
+ "version": "1.1.20",
"type": "module",
"scripts": {
"dev": "vite",
diff --git a/server/email.js b/server/email.js
index 4834747..471756c 100644
--- a/server/email.js
+++ b/server/email.js
@@ -585,36 +585,29 @@ export async function sendStudyReminderEmail(email, displayName, studyTitle, sec
}
}
-const REENGAGEMENT_COPY = {
- '7d': {
- subject: 'Your study is waiting for you',
- eyebrow: 'Come Back',
- headline: 'Pick up where you left off',
- body: "It's been a week — your progress is saved and your next lesson is ready whenever you are.",
- cta: 'Continue Studying',
- },
- '14d': {
- subject: "Don't lose your momentum",
- eyebrow: 'Still With You',
- headline: 'Your spot is still saved',
- body: 'Two weeks have passed, but every note and completed lesson is still right there. A little each day adds up.',
- cta: 'Return to Your Study',
- },
- '30d': {
- subject: 'Your progress is still here',
- eyebrow: 'We Saved Your Spot',
- headline: "It's been a month — come back anytime",
- body: 'Your study progress is still intact and waiting. There\'s no deadline — come back whenever you\'re ready.',
- cta: 'Open Your Study',
- },
+const REENGAGEMENT_DEFAULTS = {
+ t1: { subject: 'Your study is waiting for you', body: "It's been a while — your progress is saved and your next lesson is ready whenever you are.", cta: 'Continue Studying' },
+ t2: { subject: "Don't lose your momentum", body: 'Every note and completed lesson is still right there. A little each day adds up — come back and continue.', cta: 'Return to Your Study' },
+ t3: { subject: 'Your progress is still here', body: "Your study progress is still intact and waiting. There's no deadline — come back whenever you're ready.", cta: 'Open Your Study' },
+}
+
+function getReengagementCopy(tier) {
+ const cfg = state.cachedSiteContent ?? {}
+ const t = tier === 't1' ? '1' : tier === 't2' ? '2' : tier === 't3' ? '3' : null
+ const def = REENGAGEMENT_DEFAULTS[tier] ?? REENGAGEMENT_DEFAULTS.t1
+ if (!t) return def
+ return {
+ subject: cfg[`studyReengagementT${t}Subject`]?.trim() || def.subject,
+ body: cfg[`studyReengagementT${t}Body`]?.trim() || def.body,
+ cta: cfg[`studyReengagementT${t}Cta`]?.trim() || def.cta,
+ }
}
export async function sendStudyReengagementEmail(email, displayName, studyTitle, studyUrl, tier, unsubUrl) {
if (!process.env.RESEND_API_KEY) return
- const copy = REENGAGEMENT_COPY[tier]
- if (!copy) return
try {
const resend = new Resend(process.env.RESEND_API_KEY)
+ const copy = getReengagementCopy(tier)
const namePart = typeof displayName === 'string' && displayName.trim() ? displayName.trim() : 'friend'
const subject = `${copy.subject}${studyTitle ? ` — ${studyTitle}` : ''}`
const bodyHtml = (
@@ -629,8 +622,8 @@ export async function sendStudyReengagementEmail(email, displayName, studyTitle,
subject,
text: `Hi ${namePart},\n\n${copy.body}${studyTitle ? ` Your current study: ${studyTitle}.` : ''}\n\n${studyUrl}\n\nGrace and peace,\nVerse by Verse with Nate${unsubUrl ? `\n\nUnsubscribe: ${unsubUrl}` : ''}`,
html: buildBrandedEmailHtml({
- title: copy.headline,
- eyebrow: copy.eyebrow,
+ title: copy.subject,
+ eyebrow: 'Study Reminder',
bodyHtml,
ctaLabel: copy.cta,
ctaUrl: studyUrl,
diff --git a/server/study-helpers.js b/server/study-helpers.js
index ede0278..a002661 100644
--- a/server/study-helpers.js
+++ b/server/study-helpers.js
@@ -669,11 +669,13 @@ export async function scheduleStudyReminders(sendStudyReminderEmail) {
// ── Study re-engagement scheduler ─────────────────────────────────────────
-const REENGAGEMENT_TIERS = [
- { key: '7d', days: 7 },
- { key: '14d', days: 14 },
- { key: '30d', days: 30 },
-]
+function getReengagementTiers() {
+ const cfg = state.cachedSiteContent ?? {}
+ const raw = typeof cfg.studyReengagementDays === 'string' ? cfg.studyReengagementDays : '7,14,30'
+ const days = raw.split(',').map(s => parseInt(s.trim(), 10)).filter(n => !isNaN(n) && n > 0)
+ if (days.length === 0) return [{ key: 't1', days: 7 }, { key: 't2', days: 14 }, { key: 't3', days: 30 }]
+ return days.slice(0, 3).map((d, i) => ({ key: `t${i + 1}`, days: d }))
+}
function makeUnsubUrl(userId) {
const secret = process.env.RESEND_WEBHOOK_TOKEN ?? 'siteforge'
@@ -687,6 +689,7 @@ export async function scheduleReengagementEmails(sendStudyReengagementEmail) {
if (!state.cachedSiteContent) return
const now = new Date()
const nowMs = now.getTime()
+ const tiers = getReengagementTiers()
for (const user of state.studyUsers) {
if (user.studyRemindersEnabled !== true) continue
@@ -713,7 +716,7 @@ export async function scheduleReengagementEmails(sendStudyReengagementEmail) {
? `${base}/study/${firstStudy.slug}`
: `${base}/study`
- for (const tier of REENGAGEMENT_TIERS) {
+ for (const tier of tiers) {
if (daysSinceLogin < tier.days) continue
if (sent[tier.key]) continue
diff --git a/src/AdminPage.tsx b/src/AdminPage.tsx
index 9f2b6d6..bf5bdd9 100644
--- a/src/AdminPage.tsx
+++ b/src/AdminPage.tsx
@@ -478,6 +478,12 @@ function EmailTemplatesPanel({
fieldKeys={['studyReminderEmailSubjectPrefix','studyReminderEmailBody','studyReminderEmailCtaLabel','studyReminderEmailSignoff']}
openKey="study-reminder" />
+
+