diff --git a/data/study-reminders.json b/data/study-reminders.json
index 419e498..6974f84 100644
--- a/data/study-reminders.json
+++ b/data/study-reminders.json
@@ -1,4 +1,4 @@
{
"users": {},
- "updatedAt": "2026-06-03T20:07:22.259Z"
+ "updatedAt": "2026-06-04T13:16:50.352Z"
}
\ No newline at end of file
diff --git a/server/email.js b/server/email.js
index d980b00..72b9300 100644
--- a/server/email.js
+++ b/server/email.js
@@ -142,7 +142,7 @@ export function buildContactWelcomeEmailTemplate({
welcomeScripture,
welcomeScriptureRef,
welcomeSignoff,
- welcomeHeading,
+ welcomeGreetingPrefix = "Glad you're here",
welcomeSpotifyUrl,
welcomeAppleUrl,
welcomeAmazonUrl,
@@ -153,89 +153,159 @@ export function buildContactWelcomeEmailTemplate({
welcomeAppleBtnLabel = 'Apple Podcasts',
welcomeStartHereLinkLabel = 'Open Start Here page',
}) {
- const welcomeSignoffHtml = escapeHtml(welcomeSignoff).replace(/\n/g, '
')
+ const heading = greetingName
+ ? `${escapeHtml(welcomeGreetingPrefix)}, ${escapeHtml(greetingName)}.`
+ : `${escapeHtml(welcomeGreetingPrefix)}.`
return {
text:
`Welcome to Verse by Verse with Nate!\n\n` +
- `${greetingName ? `Glad you're here, ${greetingName}.` : "Glad you're here."}\n\n` +
+ `${heading}\n\n` +
`${welcomeIntro}\n\n` +
`${welcomeCurrentSeries}\n\n` +
`Start here: ${welcomeEpisodeUrl}\n` +
`${welcomeStartHereTitle}\n` +
`${welcomeStartHereSummary}\n` +
- `Spotify: ${welcomeSpotifyUrl}\n` +
- `Apple Podcasts: ${welcomeAppleUrl}\n` +
+ `${welcomeSpotifyBtnLabel}: ${welcomeSpotifyUrl}\n` +
+ `${welcomeAppleBtnLabel}: ${welcomeAppleUrl}\n` +
`Amazon Music: ${welcomeAmazonUrl}\n` +
`Website: ${welcomeWebsiteUrl}\n\n` +
`What to expect:\n` +
`- ${welcomeExpect1}\n` +
`- ${welcomeExpect2}\n` +
`- ${welcomeExpect3}\n\n` +
- `${welcomeScripture}\n${welcomeScriptureRef}\n\n` +
+ `"${welcomeScripture}"\n${welcomeScriptureRef}\n\n` +
`${welcomeSignoff}`,
- html:
- `
` +
- `
` +
- `` +
- `` +
- `` +
- ` ` +
- `Verse by Verse with Nate ` +
- `Verse by verse. Nugget by nugget. ` +
- ` | ` +
- `| ` +
- ` Welcome ` +
- `${welcomeHeading}` +
- `` +
- ` | ` +
- `| ` +
- ` ${escapeHtml(welcomeIntro)} ` +
- `${escapeHtml(welcomeCurrentSeries)} ` +
- `If you’re just joining us, the best place to start is Episode 1. It sets the table for everything that follows. ` +
- ` | ` +
- ` | ` +
- `| ` +
- ` Start here ` +
- `${escapeHtml(welcomeStartHereTitle)} ` +
- `${escapeHtml(welcomeStartHereSummary)} ` +
- `` +
- `${escapeHtml(welcomeStartHereLinkLabel)} ` +
- ` | ` +
- ` | ` +
- `| ` +
- ` What to expect ` +
- `${escapeHtml(welcomeExpect1)} ` +
- `${escapeHtml(welcomeExpect2)} ` +
- `${escapeHtml(welcomeExpect3)} ` +
- ` | ` +
- ` | ` +
- `` +
- `| ` +
- ` “${escapeHtml(welcomeScripture)}” ` +
- `${escapeHtml(welcomeScriptureRef)} ` +
- ` | ` +
- ` | ` +
- `| ` +
- ` Find the podcast on ` +
- `` +
- `You’re receiving this because you subscribed to Verse by Verse with Nate. ` +
- `${welcomeSignoffHtml} ` +
- ` | ` +
- ` ` +
- ` |
` +
- `
`,
+ html: `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Verse by Verse with Nate
+ Verse by verse. Nugget by nugget.
+ |
+
+
+ |
+ Welcome
+ ${heading}
+
+ |
+
+
+ |
+ ${escapeHtml(welcomeIntro)}
+ ${escapeHtml(welcomeCurrentSeries)}
+ If you’re just joining us, the best place to start is Episode 1. It sets the table for everything that follows.
+ |
+
+ |
+
+ |
+ Start here
+ ${escapeHtml(welcomeStartHereTitle)}
+ ${escapeHtml(welcomeStartHereSummary)}
+
+ |
+
+ |
+
+ |
+ What to expect
+
+
+ |
+ ${escapeHtml(welcomeExpect1)} |
+
+
+
+
+ |
+ ${escapeHtml(welcomeExpect2)} |
+
+
+
+
+ |
+ ${escapeHtml(welcomeExpect3)} |
+
+
+ |
+
+ |
+
+
+
+
+ |
+ “${escapeHtml(welcomeScripture)}”
+ ${escapeHtml(welcomeScriptureRef)}
+ |
+
+
+ |
+
+
+ |
+ Find the podcast on
+
+ You’re receiving this because you subscribed to Verse by Verse with Nate.
+ Unsubscribe
+ “Your word is a lamp to my feet and a light to my path.” — Psalm 119:105
+ |
+
+
+
+ |
+
+
+
+ |
+
+
+
+ |
+
+
+
+
+`,
}
}
diff --git a/server/routes/contact.js b/server/routes/contact.js
index b87d7fe..41d7111 100644
--- a/server/routes/contact.js
+++ b/server/routes/contact.js
@@ -191,12 +191,10 @@ export function register(app) {
: DEFAULT_SEO.canonicalUrl
const welcomeSubject = process.env.RESEND_WELCOME_SUBJECT ?? emailConfig.welcomeEmailSubject ?? 'Welcome to Verse by Verse with Nate'
const welcomeGreetingPrefix = emailConfig.welcomeEmailGreetingPrefix?.trim() || "Glad you're here"
- const welcomeHeading = greetingName
- ? `${escapeHtml(welcomeGreetingPrefix)}, ${escapeHtml(greetingName)}.`
- : `${escapeHtml(welcomeGreetingPrefix)}.`
const { buildAbsoluteUrl } = await import('../helpers.js')
const welcomeTemplate = buildContactWelcomeEmailTemplate({
greetingName,
+ welcomeGreetingPrefix,
welcomeIntro: emailConfig.welcomeEmailIntro?.trim() || 'Thanks for subscribing to Verse by Verse with Nate - a Bible teaching podcast where we slow down, dig into the text, and pull out the nuggets God has for us word by word.',
welcomeCurrentSeries: emailConfig.welcomeEmailCurrentSeries?.trim() || "Right now we're working through the book of Titus - a short letter packed with practical wisdom about grace, godliness, and what the Christian life looks like when it's rooted in sound doctrine.",
welcomeStartHereTitle: emailConfig.welcomeEmailStartHereTitle?.trim() || 'Episode 1 - Introduction to Titus',
@@ -207,7 +205,6 @@ export function register(app) {
welcomeScripture: emailConfig.welcomeEmailScripture?.trim() || 'For the grace of God has appeared, bringing salvation to all people.',
welcomeScriptureRef: emailConfig.welcomeEmailScriptureRef?.trim() || 'Titus 2:11 - BSB',
welcomeSignoff: emailConfig.welcomeEmailSignoff?.trim() || 'Grace and peace,\nNate',
- welcomeHeading,
welcomeSpotifyUrl: buildAbsoluteUrl(welcomeBaseUrl, process.env.RESEND_WELCOME_SPOTIFY_URL ?? emailConfig.welcomeEmailSpotifyUrl ?? '/spotify'),
welcomeAppleUrl: buildAbsoluteUrl(welcomeBaseUrl, process.env.RESEND_WELCOME_APPLE_URL ?? emailConfig.welcomeEmailAppleUrl ?? '/apple'),
welcomeAmazonUrl: buildAbsoluteUrl(welcomeBaseUrl, process.env.RESEND_WELCOME_AMAZON_URL ?? emailConfig.welcomeEmailAmazonUrl ?? '/amazon'),
diff --git a/src/App.css b/src/App.css
index 4aae5cf..187227f 100644
--- a/src/App.css
+++ b/src/App.css
@@ -1112,6 +1112,41 @@
}
/* ── Home Jump Section ── */
+/* ── Truth For Life Widget ── */
+.section-tfl {
+ background: #080808;
+ border-top: 1px solid rgba(201, 168, 76, 0.18);
+ border-bottom: 1px solid rgba(201, 168, 76, 0.18);
+ padding: 5rem 0;
+ text-align: center;
+}
+
+.tfl-attribution {
+ color: #8a7f5a;
+ font-size: 0.9rem;
+ margin: -0.5rem 0 2.5rem;
+}
+
+.tfl-link {
+ color: #c9a84c;
+ text-decoration: none;
+}
+
+.tfl-link:hover {
+ text-decoration: underline;
+}
+
+.tfl-widget-wrap {
+ max-width: 760px;
+ margin: 0 auto;
+ text-align: left;
+}
+
+/* Scope TFL widget styles to avoid bleed */
+.tfl-widget-wrap * {
+ box-sizing: border-box;
+}
+
.section-share {
background: #080808;
border-top: 1px solid rgba(201, 168, 76, 0.18);
diff --git a/src/App.tsx b/src/App.tsx
index 200c41e..f197cf9 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1056,6 +1056,57 @@ function HomepageNewsletterSection() {
)
}
+type TflWidgetType = 'audiodevo' | 'abdevotional'
+
+function TruthForLifeWidget({ type = 'audiodevo' }: { type?: TflWidgetType }) {
+ const containerId = `tfl-syndicate-container-${type}`
+
+ useEffect(() => {
+ // Remove any previous instance of this script to allow re-init on re-mount
+ const existingScript = document.getElementById('tfl-syn-script-' + type)
+ if (existingScript) existingScript.remove()
+
+ // Clear the container so the widget can repopulate it
+ const container = document.getElementById(containerId)
+ if (container) container.innerHTML = ''
+
+ const script = document.createElement('script')
+ script.id = 'tfl-syn-script-' + type
+ script.src = `https://www.truthforlife.org/static/js/responsive/lib/syndicate.js?type=${type}&id=458890`
+ script.type = 'text/javascript'
+ // The TFL script looks for #tfl-syndicate-container by ID — temporarily
+ // rename our container to match, then rename back after the script fires
+ if (container) container.id = 'tfl-syndicate-container'
+ document.body.appendChild(script)
+
+ // Restore the namespaced id after a tick so React doesn't lose track
+ const restore = setTimeout(() => {
+ const el = document.getElementById('tfl-syndicate-container')
+ if (el) el.id = containerId
+ }, 2000)
+
+ return () => {
+ clearTimeout(restore)
+ script.remove()
+ }
+ }, [type])
+
+ return (
+
+
+
Daily Devotional
+
+ ✦ Truth For Life ✦
+
+
+ Teaching from Truth For Life with Alistair Begg
+
+
+
+
+ )
+}
+
function HomepageQACallout() {
const [questions, setQuestions] = useState<{ id: string; firstName: string; question: string; answer: string }[]>([])
@@ -1213,6 +1264,8 @@ function LandingPage({ content }: { content: SiteContent }) {
+
+