diff --git a/server.js b/server.js
index 72bb03d..6e4c5cd 100644
--- a/server.js
+++ b/server.js
@@ -1094,6 +1094,9 @@ function buildContactWelcomeEmailTemplate({
welcomeWebsiteUrl,
welcomeEpisodeUrl,
welcomeImageUrl,
+ welcomeSpotifyBtnLabel = 'Listen on Spotify',
+ welcomeAppleBtnLabel = 'Apple Podcasts',
+ welcomeStartHereLinkLabel = 'Open Start Here page',
}) {
const welcomeSignoffHtml = escapeHtml(welcomeSignoff).replace(/\n/g, '
')
@@ -1142,10 +1145,10 @@ function buildContactWelcomeEmailTemplate({
`
| ` +
@@ -2433,31 +2436,30 @@ async function sendStudyWelcomeEmail(email, displayName) {
if (!process.env.RESEND_API_KEY) return
try {
const resend = new Resend(process.env.RESEND_API_KEY)
+ const cfg = cachedSiteContent ?? {}
const namePart = typeof displayName === 'string' && displayName.trim() ? displayName.trim() : 'friend'
const baseUrl = getCanonicalBaseUrl()
- const studiesUrl = buildAbsoluteUrl(baseUrl, '/study')
const accountUrl = buildAbsoluteUrl(baseUrl, '/study/account')
+ const subject = cfg.studyWelcomeEmailSubject?.trim() || 'Welcome to the Study Community'
+ const bodyText = cfg.studyWelcomeEmailBody?.trim() || 'Your student account is ready. Open your studies and continue learning, or manage your account details anytime.'
+ const ctaLabel = cfg.studyWelcomeEmailCtaLabel?.trim() || 'Open Studies'
+ const studiesUrl = buildAbsoluteUrl(baseUrl, cfg.studyWelcomeEmailCtaPath?.trim() || '/study')
+ const signoff = cfg.studyWelcomeEmailSignoff?.trim() || 'Grace and peace,\nVerse by Verse with Nate'
const bodyHtml = (
` Welcome, ${escapeHtml(namePart)}. ` +
- `Your student account is ready. ` +
- `Open studies and continue learning, or manage your account details anytime. `
+ `${escapeHtml(bodyText)} `
)
- const footerHtml = `Grace and peace, Verse by Verse with Nate `
+ const footerHtml = `${escapeHtml(signoff).replace(/\n/g, ' ')} `
const { error } = await resend.emails.send({
from: process.env.RESEND_FROM ?? 'Verse by Verse with Nate ',
to: [email],
- subject: process.env.RESEND_WELCOME_SUBJECT ?? 'Welcome to Verse by Verse with Nate',
- text:
- `Welcome, ${namePart}.\n\n` +
- `Your student account is ready.\n\n` +
- `Open studies: ${studiesUrl}\n` +
- `Manage account: ${accountUrl}\n\n` +
- `Grace and peace,\nVerse by Verse with Nate`,
+ subject,
+ text: `Welcome, ${namePart}.\n\n${bodyText}\n\nOpen studies: ${studiesUrl}\nManage account: ${accountUrl}\n\n${signoff}`,
html: buildBrandedEmailHtml({
title: 'Welcome to the Study Community',
eyebrow: 'Study Account',
bodyHtml,
- ctaLabel: 'Open Studies',
+ ctaLabel,
ctaUrl: studiesUrl,
footerHtml: footerHtml + `Manage your account `,
}),
@@ -2472,30 +2474,29 @@ async function sendStudyAccountDeletedEmail(email, displayName) {
if (!process.env.RESEND_API_KEY) return
try {
const resend = new Resend(process.env.RESEND_API_KEY)
+ const cfg = cachedSiteContent ?? {}
const namePart = typeof displayName === 'string' && displayName.trim() ? displayName.trim() : 'friend'
const baseUrl = getCanonicalBaseUrl()
- const signupUrl = buildAbsoluteUrl(baseUrl, '/study/signup')
+ const subject = cfg.studyDeletedEmailSubject?.trim() || 'Your study account was deleted'
+ const bodyText = cfg.studyDeletedEmailBody?.trim() || 'This confirms your study account and saved notes were deleted. If this was not you, please contact us immediately.'
+ const ctaLabel = cfg.studyDeletedEmailCtaLabel?.trim() || 'Create a New Account'
+ const signupUrl = buildAbsoluteUrl(baseUrl, cfg.studyDeletedEmailCtaPath?.trim() || '/study/signup')
+ const signoff = cfg.studyDeletedEmailSignoff?.trim() || 'Verse by Verse with Nate'
const bodyHtml = (
`Hi ${escapeHtml(namePart)}, ` +
- `This confirms your study account and saved notes were deleted. ` +
- `If this was not you, please contact us immediately. `
+ `${escapeHtml(bodyText)} `
)
- const footerHtml = `Verse by Verse with Nate `
+ const footerHtml = `${escapeHtml(signoff).replace(/\n/g, ' ')} `
const { error } = await resend.emails.send({
from: process.env.RESEND_FROM ?? 'Verse by Verse with Nate ',
to: [email],
- subject: 'Your study account was deleted',
- text:
- `Hi ${namePart},\n\n` +
- `This confirms your study account and saved notes were deleted.\n\n` +
- `If this was not you, please contact us immediately.\n\n` +
- `Create a new account anytime: ${signupUrl}\n\n` +
- `Verse by Verse with Nate`,
+ subject,
+ text: `Hi ${namePart},\n\n${bodyText}\n\nCreate a new account anytime: ${signupUrl}\n\n${signoff}`,
html: buildBrandedEmailHtml({
title: 'Study Account Deleted',
eyebrow: 'Account Update',
bodyHtml,
- ctaLabel: 'Create a New Account',
+ ctaLabel,
ctaUrl: signupUrl,
footerHtml,
}),
@@ -2782,28 +2783,29 @@ async function sendStudyReminderEmail(email, displayName, studyTitle, sectionTit
if (!process.env.RESEND_API_KEY) return
try {
const resend = new Resend(process.env.RESEND_API_KEY)
+ const cfg = cachedSiteContent ?? {}
const namePart = typeof displayName === 'string' && displayName.trim() ? displayName.trim() : 'friend'
+ const subjectPrefix = cfg.studyReminderEmailSubjectPrefix?.trim() || 'New lesson available:'
+ const bodyText = cfg.studyReminderEmailBody?.trim() || 'A new lesson has been unlocked in your study track. Open it below to continue where you left off.'
+ const ctaLabel = cfg.studyReminderEmailCtaLabel?.trim() || 'Open the Lesson'
+ const signoff = cfg.studyReminderEmailSignoff?.trim() || 'Grace and peace,\nVerse by Verse with Nate'
+ const subject = process.env.RESEND_REMINDER_SUBJECT ?? `${subjectPrefix} ${sectionTitle}`
const bodyHtml = (
`Hi ${escapeHtml(namePart)}, ` +
- `A new study lesson is available in ${escapeHtml(studyTitle)}. ` +
- `${escapeHtml(sectionTitle)} (${escapeHtml(sectionReference)}) is now unlocked. `
+ `${escapeHtml(bodyText)} ` +
+ `${escapeHtml(sectionTitle)} (${escapeHtml(sectionReference)}) — ${escapeHtml(studyTitle)} `
)
- const footerHtml = `Grace and peace, Verse by Verse with Nate `
+ const footerHtml = `${escapeHtml(signoff).replace(/\n/g, ' ')} `
const { error } = await resend.emails.send({
from: process.env.RESEND_FROM ?? 'Verse by Verse with Nate ',
to: [email],
- subject: process.env.RESEND_REMINDER_SUBJECT ?? `New lesson available: ${sectionTitle}`,
- text:
- `Hi ${namePart},\n\n` +
- `A new lesson is available in ${studyTitle}.\n` +
- `${sectionTitle} (${sectionReference}) is now unlocked.\n\n` +
- `Open it here: ${sectionUrl}\n\n` +
- `Grace and peace,\nVerse by Verse with Nate`,
+ subject,
+ text: `Hi ${namePart},\n\n${bodyText}\n\n${sectionTitle} (${sectionReference}) — ${studyTitle}\n\nOpen it here: ${sectionUrl}\n\n${signoff}`,
html: buildBrandedEmailHtml({
title: 'New Lesson Available',
eyebrow: 'Study Reminder',
bodyHtml,
- ctaLabel: 'Open the Lesson',
+ ctaLabel,
ctaUrl: sectionUrl,
footerHtml,
}),
@@ -3790,19 +3792,23 @@ app.post('/api/study-account/request-email-change', studyAuthRateLimiter, requir
const resend = new Resend(process.env.RESEND_API_KEY)
const baseUrl = getCanonicalBaseUrl()
const verifyUrl = buildAbsoluteUrl(baseUrl, `/study/account?verifyEmailToken=${encodeURIComponent(rawToken)}`)
+ const cfg = cachedSiteContent ?? {}
+ const emailChangeSubject = cfg.emailChangeSubject?.trim() || 'Confirm your new email address'
+ const emailChangeBody = cfg.emailChangeBody?.trim() || 'Click the link below to confirm your new account email. If you did not request this change, ignore this message.'
+ const emailChangeCtaLabel = cfg.emailChangeCtaLabel?.trim() || 'Confirm Email Change'
const { error } = await resend.emails.send({
from: process.env.RESEND_FROM ?? 'Verse by Verse with Nate ',
to: [newEmail],
- subject: 'Confirm your new email address',
- text:
- `Use this link to confirm your new account email:\n${verifyUrl}\n\n` +
- `If you did not request this change, ignore this message.`,
- html:
- `` +
- ` Click the link below to confirm your new account email: ` +
- ` Confirm email change ` +
- ` If you did not request this change, ignore this message. ` +
- ` `,
+ subject: emailChangeSubject,
+ text: `${emailChangeBody}\n\n${verifyUrl}`,
+ html: buildBrandedEmailHtml({
+ title: emailChangeSubject,
+ eyebrow: 'Account Security',
+ bodyHtml: `${escapeHtml(emailChangeBody)} `,
+ ctaLabel: emailChangeCtaLabel,
+ ctaUrl: verifyUrl,
+ footerHtml: `Verse by Verse with Nate `,
+ }),
})
if (error) {
console.error('[study-account] email change send error:', error)
@@ -5013,6 +5019,10 @@ app.post('/api/contact', contactRateLimit, async (req, res) => {
process.env.RESEND_WELCOME_IMAGE_URL ?? emailConfig.welcomeEmailImageUrl ?? '/images/podcast-art.jpeg',
)
+ const welcomeSpotifyBtnLabel = emailConfig.welcomeEmailSpotifyBtnLabel?.trim() || 'Listen on Spotify'
+ const welcomeAppleBtnLabel = emailConfig.welcomeEmailAppleBtnLabel?.trim() || 'Apple Podcasts'
+ const welcomeStartHereLinkLabel = emailConfig.welcomeEmailStartHereLinkLabel?.trim() || 'Open Start Here page'
+
const welcomeTemplate = buildContactWelcomeEmailTemplate({
greetingName,
welcomeIntro,
@@ -5032,6 +5042,9 @@ app.post('/api/contact', contactRateLimit, async (req, res) => {
welcomeWebsiteUrl,
welcomeEpisodeUrl,
welcomeImageUrl,
+ welcomeSpotifyBtnLabel,
+ welcomeAppleBtnLabel,
+ welcomeStartHereLinkLabel,
})
try {
diff --git a/server/helpers.js b/server/helpers.js
index 41388b2..8eaca09 100644
--- a/server/helpers.js
+++ b/server/helpers.js
@@ -339,6 +339,26 @@ export function sanitizeSiteContent(siteContent) {
redirects: sanitizeRedirectRules(siteContent.redirects),
podcastFeaturedLinks: sanitizeFeaturedLinks(siteContent.podcastFeaturedLinks ?? DEFAULT_PODCAST_FEATURED_LINKS),
episodesSeoIntro: typeof siteContent.episodesSeoIntro === 'string' && siteContent.episodesSeoIntro.trim() ? siteContent.episodesSeoIntro.trim().slice(0, 600) : '',
+ welcomeEmailSpotifyBtnLabel: typeof siteContent.welcomeEmailSpotifyBtnLabel === 'string' ? siteContent.welcomeEmailSpotifyBtnLabel.trim().slice(0, 80) : '',
+ welcomeEmailAppleBtnLabel: typeof siteContent.welcomeEmailAppleBtnLabel === 'string' ? siteContent.welcomeEmailAppleBtnLabel.trim().slice(0, 80) : '',
+ welcomeEmailStartHereLinkLabel: typeof siteContent.welcomeEmailStartHereLinkLabel === 'string' ? siteContent.welcomeEmailStartHereLinkLabel.trim().slice(0, 80) : '',
+ studyWelcomeEmailSubject: typeof siteContent.studyWelcomeEmailSubject === 'string' ? siteContent.studyWelcomeEmailSubject.trim().slice(0, 200) : '',
+ studyWelcomeEmailBody: typeof siteContent.studyWelcomeEmailBody === 'string' ? siteContent.studyWelcomeEmailBody.trim().slice(0, 1000) : '',
+ studyWelcomeEmailCtaLabel: typeof siteContent.studyWelcomeEmailCtaLabel === 'string' ? siteContent.studyWelcomeEmailCtaLabel.trim().slice(0, 80) : '',
+ studyWelcomeEmailCtaPath: typeof siteContent.studyWelcomeEmailCtaPath === 'string' ? siteContent.studyWelcomeEmailCtaPath.trim().slice(0, 200) : '',
+ studyWelcomeEmailSignoff: typeof siteContent.studyWelcomeEmailSignoff === 'string' ? siteContent.studyWelcomeEmailSignoff.trim().slice(0, 200) : '',
+ studyDeletedEmailSubject: typeof siteContent.studyDeletedEmailSubject === 'string' ? siteContent.studyDeletedEmailSubject.trim().slice(0, 200) : '',
+ studyDeletedEmailBody: typeof siteContent.studyDeletedEmailBody === 'string' ? siteContent.studyDeletedEmailBody.trim().slice(0, 1000) : '',
+ studyDeletedEmailCtaLabel: typeof siteContent.studyDeletedEmailCtaLabel === 'string' ? siteContent.studyDeletedEmailCtaLabel.trim().slice(0, 80) : '',
+ studyDeletedEmailCtaPath: typeof siteContent.studyDeletedEmailCtaPath === 'string' ? siteContent.studyDeletedEmailCtaPath.trim().slice(0, 200) : '',
+ studyDeletedEmailSignoff: typeof siteContent.studyDeletedEmailSignoff === 'string' ? siteContent.studyDeletedEmailSignoff.trim().slice(0, 200) : '',
+ studyReminderEmailSubjectPrefix: typeof siteContent.studyReminderEmailSubjectPrefix === 'string' ? siteContent.studyReminderEmailSubjectPrefix.trim().slice(0, 200) : '',
+ studyReminderEmailBody: typeof siteContent.studyReminderEmailBody === 'string' ? siteContent.studyReminderEmailBody.trim().slice(0, 1000) : '',
+ studyReminderEmailCtaLabel: typeof siteContent.studyReminderEmailCtaLabel === 'string' ? siteContent.studyReminderEmailCtaLabel.trim().slice(0, 80) : '',
+ studyReminderEmailSignoff: typeof siteContent.studyReminderEmailSignoff === 'string' ? siteContent.studyReminderEmailSignoff.trim().slice(0, 200) : '',
+ emailChangeSubject: typeof siteContent.emailChangeSubject === 'string' ? siteContent.emailChangeSubject.trim().slice(0, 200) : '',
+ emailChangeBody: typeof siteContent.emailChangeBody === 'string' ? siteContent.emailChangeBody.trim().slice(0, 500) : '',
+ emailChangeCtaLabel: typeof siteContent.emailChangeCtaLabel === 'string' ? siteContent.emailChangeCtaLabel.trim().slice(0, 80) : '',
seo: {
title: typeof seo.title === 'string' && seo.title.trim() ? seo.title.trim().slice(0, 120) : DEFAULT_SEO.title,
description: typeof seo.description === 'string' && seo.description.trim() ? seo.description.trim().slice(0, 240) : DEFAULT_SEO.description,
diff --git a/src/AdminPage.tsx b/src/AdminPage.tsx
index f3570fc..6ffbe37 100644
--- a/src/AdminPage.tsx
+++ b/src/AdminPage.tsx
@@ -358,6 +358,114 @@ function StudyUsersPanel({ studies }: { studies: StudyProgram[] }) {
)
}
+function EmailTemplatesPanel({
+ form,
+ handleChange,
+ renderSaveStatus,
+}: {
+ form: SiteContent
+ handleChange: (key: StringField, value: string) => void
+ renderSaveStatus: () => React.ReactNode
+}) {
+ const [expandedEmail, setExpandedEmail] = useState(null)
+
+ function EmailTemplateCard({
+ type, icon, title, trigger, autoVars, fieldKeys, openKey,
+ }: {
+ type: 'newsletter' | 'study' | 'transactional'
+ icon: string
+ title: string
+ trigger: string
+ autoVars?: string
+ fieldKeys: string[]
+ openKey: string
+ }) {
+ const isOpen = expandedEmail === openKey
+ return (
+
+
+ {isOpen && (
+
+ {autoVars && (
+
+ Auto-inserted: {autoVars}
+
+ )}
+ {fieldKeys.map(key => {
+ const field = FIELDS.find(f => f.key === key && f.section === 'email-templates')
+ if (!field) return null
+ return (
+
+
+ {field.multiline
+ ?
+ )
+ })}
+
+ )}
+
+ )
+ }
+
+ return (
+
+
+ Email Templates
+ Every automated email the site sends — edit subject lines, body copy, and sign-offs. Publish to apply changes.
+
+
+
+
+
+
+
+
+
+
+
+
+ {renderSaveStatus()}
+
+ )
+}
+
interface Props {
content: SiteContent
onSave: (c: SiteContent) => void
@@ -557,7 +665,7 @@ type AdminView =
| 'podcast' | 'current-series' | 'episode-highlights' | 'podcast-checklist' | 'archived-series'
| 'downloads' | 'custom-links' | 'content-blocks'
| 'questions' | 'analytics' | 'assets' | 'colossians-study'
- | 'emails' | 'subscribers' | 'contacts' | 'study-users'
+ | 'emails' | 'subscribers' | 'contacts' | 'study-users' | 'email-templates'
| 'seo' | 'legal' | 'security' | 'brand' | 'global'
interface AdminSectionLink {
@@ -597,6 +705,7 @@ const ADMIN_VIEW_OPTIONS: Array<{ group: string; options: Array<{ value: AdminVi
{ value: 'contacts', label: 'Contacts' },
{ value: 'subscribers', label: 'Subscribers' },
{ value: 'study-users', label: 'Study Users' },
+ { value: 'email-templates', label: 'Email Templates' },
{ value: 'analytics', label: 'Analytics' },
{ value: 'assets', label: 'Asset Manager' },
],
@@ -661,7 +770,7 @@ const ADMIN_SECTION_LINKS: Partial> = {
type PodcastTab = 'current-series' | 'episode-highlights' | 'podcast-checklist' | 'archived-series'
-type MainContentSection = 'hero' | 'start-here' | 'about' | 'contact' | 'series' | 'share' | 'prism' | 'global'
+type MainContentSection = 'hero' | 'start-here' | 'about' | 'contact' | 'series' | 'share' | 'prism' | 'global' | 'email-templates'
const BRAND_KIT_SWATCHES = [
{ name: 'Rich Black', hex: '#0a0a08', role: 'Primary background' },
@@ -792,7 +901,44 @@ const FIELDS: Array<{ key: StringField; label: string; multiline?: boolean; sect
{ key: 'welcomeEmailWhatToExpect3', label: 'Welcome Email — What to Expect 3', multiline: true, section: 'global' },
{ key: 'welcomeEmailScripture', label: 'Welcome Email — Scripture Text', multiline: true, section: 'global' },
{ key: 'welcomeEmailScriptureRef', label: 'Welcome Email — Scripture Reference', section: 'global' },
- { key: 'welcomeEmailSignoff', label: 'Welcome Email — Signoff HTML', multiline: true, section: 'global' },
+ { key: 'welcomeEmailSignoff', label: 'Signoff', multiline: true, section: 'global' },
+ // ── Email Templates section ──
+ { key: 'welcomeEmailSubject', label: 'Subject Line', section: 'email-templates' },
+ { key: 'welcomeEmailGreetingPrefix', label: 'Greeting Prefix (before subscriber name)', section: 'email-templates' },
+ { key: 'welcomeEmailIntro', label: 'Intro Paragraph', multiline: true, section: 'email-templates' },
+ { key: 'welcomeEmailCurrentSeries', label: 'Current Series Blurb', multiline: true, section: 'email-templates' },
+ { key: 'welcomeEmailStartHereTitle', label: 'Start Here Episode Title', section: 'email-templates' },
+ { key: 'welcomeEmailStartHereSummary', label: 'Start Here Episode Summary', multiline: true, section: 'email-templates' },
+ { key: 'welcomeEmailStartHereUrl', label: 'Start Here Episode URL', section: 'email-templates' },
+ { key: 'welcomeEmailWhatToExpect1', label: 'What to Expect — Line 1', multiline: true, section: 'email-templates' },
+ { key: 'welcomeEmailWhatToExpect2', label: 'What to Expect — Line 2', multiline: true, section: 'email-templates' },
+ { key: 'welcomeEmailWhatToExpect3', label: 'What to Expect — Line 3', multiline: true, section: 'email-templates' },
+ { key: 'welcomeEmailScripture', label: 'Closing Scripture Text', multiline: true, section: 'email-templates' },
+ { key: 'welcomeEmailScriptureRef', label: 'Closing Scripture Reference', section: 'email-templates' },
+ { key: 'welcomeEmailSignoff', label: 'Signoff', multiline: true, section: 'email-templates' },
+ { key: 'welcomeEmailSpotifyUrl', label: 'Spotify URL', section: 'email-templates' },
+ { key: 'welcomeEmailSpotifyBtnLabel', label: '"Listen on Spotify" button label', section: 'email-templates' },
+ { key: 'welcomeEmailAppleUrl', label: 'Apple Podcasts URL', section: 'email-templates' },
+ { key: 'welcomeEmailAppleBtnLabel', label: '"Apple Podcasts" button label', section: 'email-templates' },
+ { key: 'welcomeEmailAmazonUrl', label: 'Amazon Music URL', section: 'email-templates' },
+ { key: 'welcomeEmailStartHereLinkLabel', label: '"Start Here" link label', section: 'email-templates' },
+ { key: 'studyWelcomeEmailSubject', label: 'Subject Line', section: 'email-templates' },
+ { key: 'studyWelcomeEmailBody', label: 'Body Paragraph', multiline: true, section: 'email-templates' },
+ { key: 'studyWelcomeEmailCtaLabel', label: 'Button Label', section: 'email-templates' },
+ { key: 'studyWelcomeEmailCtaPath', label: 'Button Link (path or full URL)', section: 'email-templates' },
+ { key: 'studyWelcomeEmailSignoff', label: 'Signoff', multiline: true, section: 'email-templates' },
+ { key: 'studyDeletedEmailSubject', label: 'Subject Line', section: 'email-templates' },
+ { key: 'studyDeletedEmailBody', label: 'Body Paragraph', multiline: true, section: 'email-templates' },
+ { key: 'studyDeletedEmailCtaLabel', label: 'Button Label', section: 'email-templates' },
+ { key: 'studyDeletedEmailCtaPath', label: 'Button Link (path or full URL)', section: 'email-templates' },
+ { key: 'studyDeletedEmailSignoff', label: 'Signoff', multiline: true, section: 'email-templates' },
+ { key: 'studyReminderEmailSubjectPrefix', label: 'Subject Prefix (lesson title auto-appended)', section: 'email-templates' },
+ { key: 'studyReminderEmailBody', label: 'Body Paragraph', multiline: true, section: 'email-templates' },
+ { key: 'studyReminderEmailCtaLabel', label: 'Button Label', section: 'email-templates' },
+ { key: 'studyReminderEmailSignoff', label: 'Signoff', multiline: true, section: 'email-templates' },
+ { key: 'emailChangeSubject', label: 'Subject Line', section: 'email-templates' },
+ { key: 'emailChangeBody', label: 'Body Text', multiline: true, section: 'email-templates' },
+ { key: 'emailChangeCtaLabel', label: 'Button Label', section: 'email-templates' },
]
function normalizeStudies(siteContent: SiteContent): StudyProgram[] {
@@ -4480,6 +4626,11 @@ export default function AdminPage({ content, onSave, onLogout }: Props) {
)}
+ {/* EMAIL TEMPLATES */}
+ {adminView === 'email-templates' && (
+
+ )}
+
{/* QUESTIONS */}
{adminView === 'questions' && (
diff --git a/src/App.css b/src/App.css
index c20318a..1cde95b 100644
--- a/src/App.css
+++ b/src/App.css
@@ -6453,3 +6453,112 @@
background: rgba(201, 168, 76, 0.08);
border-radius: 6px;
}
+
+/* ── Admin: Email Templates ── */
+.admin-et-card {
+ border-radius: 10px;
+ margin-bottom: 0.75rem;
+ overflow: hidden;
+ border: 1px solid rgba(201, 168, 76, 0.12);
+ border-left-width: 4px;
+ background: #0b0b09;
+}
+
+.admin-et-card--newsletter { border-left-color: #c9a84c; }
+.admin-et-card--study { border-left-color: #7abf7a; }
+.admin-et-card--transactional { border-left-color: #7aaacf; }
+
+.admin-et-card--open {
+ border-color: rgba(201, 168, 76, 0.3);
+ border-left-width: 4px;
+}
+.admin-et-card--open.admin-et-card--newsletter { border-left-color: #c9a84c; }
+.admin-et-card--open.admin-et-card--study { border-left-color: #7abf7a; }
+.admin-et-card--open.admin-et-card--transactional { border-left-color: #7aaacf; }
+
+.admin-et-card-header {
+ width: 100%;
+ display: flex;
+ align-items: center;
+ gap: 1rem;
+ padding: 0.9rem 1.1rem;
+ background: none;
+ border: none;
+ color: inherit;
+ cursor: pointer;
+ text-align: left;
+}
+
+.admin-et-card-header:hover {
+ background: rgba(201, 168, 76, 0.04);
+}
+
+.admin-et-card-icon {
+ font-size: 1.4rem;
+ flex-shrink: 0;
+ line-height: 1;
+}
+
+.admin-et-card-meta {
+ flex: 1;
+ min-width: 0;
+}
+
+.admin-et-card-title-row {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ margin-bottom: 0.2rem;
+ flex-wrap: wrap;
+}
+
+.admin-et-card-title {
+ font-size: 0.95rem;
+ font-weight: 700;
+ color: #f0e9cc;
+}
+
+.admin-et-card-trigger {
+ font-size: 0.78rem;
+ color: #8a7f5a;
+ margin: 0;
+}
+
+.admin-et-card-chevron {
+ font-size: 0.75rem;
+ color: #5a5440;
+ flex-shrink: 0;
+}
+
+.admin-et-badge {
+ font-size: 0.62rem;
+ font-weight: 700;
+ text-transform: uppercase;
+ letter-spacing: 0.08em;
+ padding: 0.15rem 0.5rem;
+ border-radius: 3px;
+ flex-shrink: 0;
+}
+
+.admin-et-badge--newsletter { background: rgba(201,168,76,0.15); color: #c9a84c; }
+.admin-et-badge--study { background: rgba(100,160,100,0.15); color: #7abf7a; }
+.admin-et-badge--transactional { background: rgba(100,140,200,0.15); color: #7aaacf; }
+
+.admin-et-card-body {
+ padding: 0 1.25rem 1.25rem;
+ border-top: 1px solid rgba(201, 168, 76, 0.08);
+}
+
+.admin-et-card-vars {
+ font-size: 0.78rem;
+ color: #6a6040;
+ background: rgba(201, 168, 76, 0.05);
+ border-radius: 5px;
+ padding: 0.45rem 0.75rem;
+ margin: 0.75rem 0 1rem;
+}
+
+.admin-et-card-vars-label {
+ color: #8a7f5a;
+ font-weight: 600;
+}
diff --git a/src/content.ts b/src/content.ts
index c0b7092..d7915b3 100644
--- a/src/content.ts
+++ b/src/content.ts
@@ -223,6 +223,31 @@ export interface SiteContent {
welcomeEmailScripture: string
welcomeEmailScriptureRef: string
welcomeEmailSignoff: string
+ // ── Newsletter Welcome Email CTA labels ──
+ welcomeEmailSpotifyBtnLabel: string
+ welcomeEmailAppleBtnLabel: string
+ welcomeEmailStartHereLinkLabel: string
+ // ── Study Account Welcome Email ──
+ studyWelcomeEmailSubject: string
+ studyWelcomeEmailBody: string
+ studyWelcomeEmailCtaLabel: string
+ studyWelcomeEmailCtaPath: string
+ studyWelcomeEmailSignoff: string
+ // ── Study Account Deleted Email ──
+ studyDeletedEmailSubject: string
+ studyDeletedEmailBody: string
+ studyDeletedEmailCtaLabel: string
+ studyDeletedEmailCtaPath: string
+ studyDeletedEmailSignoff: string
+ // ── Study Lesson Reminder Email ──
+ studyReminderEmailSubjectPrefix: string
+ studyReminderEmailBody: string
+ studyReminderEmailCtaLabel: string
+ studyReminderEmailSignoff: string
+ // ── Email Change Confirmation ──
+ emailChangeSubject: string
+ emailChangeBody: string
+ emailChangeCtaLabel: string
// ── Header ──
headerFollowLabel: string
// ── Cookie banner ──
@@ -387,6 +412,26 @@ export const DEFAULTS: SiteContent = {
welcomeEmailScripture: 'For the grace of God has appeared, bringing salvation to all people.',
welcomeEmailScriptureRef: 'Titus 2:11 - BSB',
welcomeEmailSignoff: 'Grace and peace,\nNate',
+ welcomeEmailSpotifyBtnLabel: 'Listen on Spotify',
+ welcomeEmailAppleBtnLabel: 'Apple Podcasts',
+ welcomeEmailStartHereLinkLabel: 'Open Start Here page',
+ studyWelcomeEmailSubject: 'Welcome to the Study Community',
+ studyWelcomeEmailBody: 'Your student account is ready. Open your studies and continue learning, or manage your account details anytime.',
+ studyWelcomeEmailCtaLabel: 'Open Studies',
+ studyWelcomeEmailCtaPath: '/study',
+ studyWelcomeEmailSignoff: 'Grace and peace,\nVerse by Verse with Nate',
+ studyDeletedEmailSubject: 'Your study account was deleted',
+ studyDeletedEmailBody: 'This confirms your study account and saved notes were deleted. If this was not you, please contact us immediately.',
+ studyDeletedEmailCtaLabel: 'Create a New Account',
+ studyDeletedEmailCtaPath: '/study/signup',
+ studyDeletedEmailSignoff: 'Verse by Verse with Nate',
+ studyReminderEmailSubjectPrefix: 'New lesson available:',
+ studyReminderEmailBody: 'A new lesson has been unlocked in your study track. Open it below to continue where you left off.',
+ studyReminderEmailCtaLabel: 'Open the Lesson',
+ studyReminderEmailSignoff: 'Grace and peace,\nVerse by Verse with Nate',
+ emailChangeSubject: 'Confirm your new email address',
+ emailChangeBody: 'Click the link below to confirm your new account email. If you did not request this change, ignore this message.',
+ emailChangeCtaLabel: 'Confirm Email Change',
headerFollowLabel: 'Follow on Spotify',
cookieBannerText: 'We use optional analytics cookies to measure visits and location trends for site improvement.',
customLinks: [],
|