🔒

Admin Help

This area is for ministry admins. Enter the team password to continue.

Admin Help & FAQ

Everything admins need to keep the site running. Use the tabs to filter by category, the alphabet rail to jump by starting letter, or the search box to find a topic.

For admins presenting to their church

📊 Seed the Word Ministry Presentation

A 10-slide deck built to take in front of your pastor and elders. Each slide has a "Copy slide" button so you can paste it straight into Google Slides as a new slide. Open the deck, walk through it with the team, then send the Slides link to leadership for review.

Open the presentation deck →

Heads up: this page has only a basic password gate on a public website. Don't paste real passwords, API keys, or member data here. For those, use a password manager or a private doc.

Quick routing

"Something needs updating" — where do I go?

📅 An event

Add or edit it in Google Calendar. Site updates within 1 minute.

🖼️ A photo or video

Open the ✏️ Editor tab and pick the matching image folder or the Videos entry. Direct GitHub uploads to assets/images/ still work as a fallback.

🎧 Spotify / YouTube / link

Open the ✏️ Editor tab, pick Recommendations. Fill the form, preview in place, commit. The legacy copy-paste flow is retired.

📖 Verse of the Day

Open the ✏️ Editor tab, pick Daily verses rotation. Add an entry and commit.

📣 Telegram bot schedule / quiet hours

Open the ✏️ Editor tab, pick Telegram bot config. Structured form for all three bots — announcements, Bible, prayer.

🧯 Something is broken

Jump to the Troubleshooting section below — hard-refresh first, then diagnose by symptom.

Site map

🗺️ What's where — page by page

A quick reference for which section on the live site is backed by what data file or folder. Sections marked (HTML only) are edited directly in the page's HTML — not yet admin-form-driven.

Homepage (index.html)

  • Hero + "Who is Jesus" CTA — (HTML only)
  • Featured Ministry Feed carousel — rotates live data from daily-verses.json, ministry-outreach.json, recommendations.json, instagram.json (scraper), Google Calendar, plus the FALLBACK_SLIDES / DAILY_CONTENT literals inside assets/js/showcase-carousel.js (editable from Editor → Homepage carousel fallback / daily)
  • Walking the Path of Light — Plant / Water / Gather waypoints — (HTML only)

About (about.html)

  • "Our S.E.E.D Story" — 9 stitch tiles. Images: Editor → Seed Stitch photos. Text: (HTML only).
  • "Meet our team" — 5 cards. Team photos: Editor → Team headshots. Text: (HTML only).
  • "How We S.E.E.D." — 5 practice cards. (HTML only)
  • "Contact us - @seedtheword" — submissions go to Formspree.

Community (community.html)

  • "This Week in God's Word" — reading plan powered by bible-spotify-map.json (Editor → Bible → Spotify chapter map).
  • "Our Ministry Feed" — Spotify show (hardcoded ID) + Study Saturday livestream. Weekly review: Editor → Study Saturday.
  • "Community Playlist" card — collaborative Spotify playlist. Listen/add songs from the page; config under Editor → Telegram bot config → Daily Bible bot → Playlist fields.
  • "Friends in Jesus" — recommendations + partners (Editor → Recommendations).
  • "Learn more about us online" 4-up platform row (Telegram / Spotify / Twitch / Instagram) — (HTML only).
  • Telegram quick-start cards + Instagram grid — (HTML only).

News (news.html)

  • Announcements + Ministry Initiatives — Google Calendar (add/edit events there).
  • Ministry Outreach cards — Editor → Ministry outreach events + photo uploads to Editor → Outreach event photos.
  • Share Your Story popup — Google Form embedded.
  • Ministry Highlights (3-tile grid: Worship/Prayer, Encountering the Word, See More Behind the Scenes) — poster images live in assets/images/ministry-highlights/, clips in assets/videos/ (Editor → Ministry highlights + Videos). Tile labels are hardcoded in news.html.
  • Calendar (full monthly view sits at the bottom of the page) — Google Calendar.

Store (store.html)

  • Bundle slideshows — Editor → Bundle Essentials / Life Group / Ministry. Photos: Editor → (matching bundle folder).

Background services

  • Telegram announcements bot — Editor → Telegram bot config → Announcements bot. Runs every 15 minutes, posts upcoming / reminder / live events into the Announcements topic (thread 553).
  • Telegram Bible bot — Editor → Telegram bot config → Daily Bible bot. Runs 08:00 PT Mon–Sat. Daily reading + optional Russian + Prayer & Thanksgiving block → General channel. Saturday posts a dedicated Study Saturday Live teaser.
  • Telegram weekly playlist digest — Editor → Telegram bot config → Daily Bible bot → Playlist fields. Runs 08:00 PT Saturday. Posts new tracks added to the community playlist into the Worship & Music topic (thread 1064).
  • Telegram prayer nudge bot — currently disabled; the Bible bot carries the Prayer & Thanksgiving block daily. Re-enable under Editor → Telegram bot config → Prayer nudge bot.
  • Media drop button — Editor → Media-drop upload button config.
  • Instagram scraper — runs every 6h via the Instagram scrape workflow.
  • Keep-alive heartbeat — hourly workflow that writes a timestamp to .github/heartbeat/last-tick.txt. Keeps the repo "active" in GitHub's eyes so scheduled workflows (bots) don't get throttled or paused on the free tier. Zero user-visible effect; skip-ci on its commits.

📋 Overview — what the site depends on

Your website is static (GitHub Pages) but pulls live data from a few services. If something on the site looks wrong, one of these is usually why.

Calendar & Events

Google Calendar

Events shown on news.html come from the ministry's Google Calendar (seedthewordministry@gmail.com). Add, edit, or delete events in Google Calendar and they appear on the site within a minute.

Dashboard: calendar.google.com

Instagram Feed

rss.app → GitHub Action → site

The site's Instagram feed (on community.html and the homepage Showcase) is fetched from a public RSS-style feed we configured at rss.app. Every 6 hours, a GitHub Action downloads new posts + images into the repo.

Feed source: rss.app

Run/check the sync: Repo → Actions tab → Scrape Instagram Feed

Livestream Status

Twitch

The "LIVE NOW / OFFLINE" card on community.html now checks live status in real time via decapi.me, a free community Twitch proxy that requires no credentials. Re-checks every 5 minutes. If decapi ever goes down, the card silently falls back to a schedule-based heuristic (Saturday 7-10 PM Pacific).

The weekly review block at the bottom of that card ("This Saturday's Review" — Old/New Testament passages) reads from assets/data/study-saturday.json. Edit it from Editor → Study Saturday.

Dashboard: dashboard.twitch.tv

Channel: twitch.tv/seedtheword

Podcast / Audio

Spotify for Podcasters

The Spotify embed on community.html pulls episodes from our Spotify show. New episodes appear automatically when published.

Dashboard: podcasters.spotify.com

Show page: Seed the Word on Spotify

Contact Form

Formspree

Form submissions on about.html land in the ministry Gmail inbox via Formspree. First submission each cycle may need an email confirmation click.

Dashboard: formspree.io/forms

Hosting

GitHub Pages

The site itself lives in a GitHub repo. Every commit to main auto-deploys in about 30 seconds. No server to manage.

Repo: github.com/seedtheword/seedtheword

Team Media Drop

Google Drive shared folder

Team members upload photos and videos through the encouragement call-out on the News page, which links to a shared Drive folder when assets/data/media-drop.json is configured. Admins curate from there. See Managing the team media drop below for setup.

Scheduling & Outreach

Meta Business Suite

Used to schedule Instagram posts, manage the IG inbox, and plan cross-platform content. Facebook Page not required for use beyond login.

Dashboard: business.facebook.com

Task & Reminder Board

Trello — Seed the Word Admin Board

Task tracker for daily posts, events, and upcoming projects.

Board: Seed the Word Admin Board

Monthly Calendar Design

Canva

Graphic template for the monthly calendar of youth life groups, events, and gatherings.

Template: Monthly calendar template

🗓️ Ministry Operations Schedule

This is the working rhythm for admin stewardship — who does what, when, and on which platform. It ensures consistency across every channel we serve.

Weekly rhythm

What happens each day of the week

1 📅

Mon–Fri · Daily

Encounter the Word chapter post, daily audio reading, prayer & thanksgiving prompt, #verse tag highlights.

2 🔁

Mid-week

Cross-platform outreach, announcements + IG highlights, calendar planning, youth content curation, inbox triage.

3 📚

Saturday · Study Day

Theme of the week, weekly summary in Discuss Scripture, resource & stewardship accountability.

4

Sunday · Rest

Minimal posting, honor in-person church attendance and the rhythm of rest.

📅 Daily (Monday–Friday)

Daily Post

Encounter the Word

  1. Open Meta Business Suite → schedule (or publish) today's chapter post to Instagram.
  2. Manually schedule the same post to the Telegram admin chat.
  3. Use the Trello admin board to check off today's reminder and queue future daily Bible-reading posts.

Daily Audio

Audio Rotation

  • Upload the daily recorded audio reading to the Telegram group.
  • Potential: re-share the audio as an Instagram Reel or Story.

Daily Prompt

Prayer & Thanksgiving

Post a dedicated message inviting members to share burdens or praises.

Template: Prayer & Thanksgiving template → (group members only)

Daily Engagement

Verse Highlights

Monitor the #verse tag for ✨ Today's Chapter Highlight — share standout verses or insights to IG Stories to encourage outside seekers.

2️⃣ Mid-Week (every 2–3 days)

Outreach

Cross-Platform Posts

In the Meta Business Suite Planner, schedule reminders inviting Instagram followers to join the Telegram group chat.

Event Updates

Announcements & Highlights

  • Update the 📣 Announcements Telegram topic and the Instagram Highlights covers. Tag noteworthy posts with #pin.
  • Post dedicated announcements for members to share on IG or Telegram. Announcement template → (group members only)

Planning

Future Event & Calendar Management

Content

Curated Youth Content

Share encouraging content from our youth groups (e.g., Edward, Sam Petrov).

Inbox

Message Triage

Check messages in Meta Business Suite and Telegram DMs. Help members with navigation or #help requests.

📚 Study Saturdays — Discipleship

Theme

Subject of the Week

Introduce the week's discussion theme and manage the vote/suggestions for the deep dive by monitoring the #theme tag.

Review

Weekly Summary

Facilitate the weekly summary in the 🎙 Discuss Scripture topic. Weekly summary template → (group members only)

Accountability

Resource, Accounting, Stewardship

  • Perform accounting for the donation account.
  • Update the group on resource utilization (Bibles, tools, etc.).
  • Admins tie in for the week for shared accountability.

⛪️ Sundays — Fellowship Focus

Rest & Rhythm

Minimize Digital Noise

We try not to post on Sundays — this encourages in-person church attendance and honors the rhythm of rest (Genesis 2:2).

🖼️ Updating images on the website

Some parts of the site display images that admins manage directly (team photos, ministry highlights, homepage featured slides, hero backgrounds). Replacing these is done through GitHub's web interface — no coding, no terminal. Here's the full walkthrough.

Two paths

Replace vs Add — pick your path

🔁 Replacing an existing photo

Match the old filename exactly (capitalization too). On GitHub, open the file → Replace this file → upload → commit → hard-refresh in ~30–60s.

➕ Adding a brand-new photo

Pick a short descriptive filename (lowercase, dashes, no spaces). Add file → Upload files to the matching folder → commit. If the site already references that filename (via a JSON manifest), it appears automatically. If not, ping the dev to wire it in.

Before you start: You need a free GitHub account, and you need to be added as a collaborator on the repo. Ping the dev on duty if you haven't been added yet.

Step 1 — Sign in to GitHub

  1. Open github.com/login in your browser.
  2. Enter your GitHub username/email and password. (If you use two-factor, enter the code too.)
  3. Once signed in, go to the repo: github.com/seedtheword/seedtheword

Step 2 — Know which folder to go to

Images are organized by how they're used on the site. Pick the folder that matches what you're updating:

Folder

assets/images/team/

Team member photos. One file per person. Filename should match the name on their card (e.g., David A..jpg, Vanessa.jpg).

Appears on: About page → "Meet the Team" section

Folder

assets/images/featured/

Images used in the homepage Showcase Carousel (fallback slides). Keep file names descriptive (e.g., bible-ministry-1.jpg).

Appears on: Homepage → "Featured From Our Ministry" rotating carousel

Folder

assets/images/ministry-highlights/

Photos/videos used in the 9-tile mixed grid on the News page.

Appears on: News page → "📸 Ministry Highlights" grid

Folder

assets/images/backgrounds/

Large hero-background images. These rarely change. If you swap one, keep the same filename so all pages automatically pick up the new version.

Appears on: Hero banners on every page

Folder

assets/images/bundles/

Product photos for the Store page bundle cards.

Appears on: Store page bundle cards

Folder

assets/images/seed-stitch/

The nine panels of the S-E-E-D ministry stitch logo. Don't rename these — they're numbered 1 through 9 in a specific order.

Appears on: About page → S.E.E.D. interactive grid

Folder

assets/images/instagram/

⚠️ Don't touch this folder. It's automatically managed by the Instagram sync every 6 hours. Files here are overwritten each run.

Step 3 — Replace an existing image (keeping the same filename)

Easiest case: you want to swap out a photo but keep everything on the page pointing to the same file.

  1. In the repo, navigate to the folder (e.g., assets/images/team/).
  2. Click the file you want to replace (e.g., Vanessa.jpg).
  3. Near the top right, click the pencil icon (🖊 Edit this file) — if the pencil is missing for image files, look for a button labeled "..." or "Replace".
  4. On the file page, there should be an option "Replace this file". Click it.
  5. Drag your new image into the upload box. The new file must have the same filename as the old one or the site won't find it.
  6. At the bottom of the page, add a short commit message like "Update Vanessa's photo" and click Commit changes.
  7. Wait about 30–60 seconds, then hard-refresh the live site (Ctrl+Shift+R / Cmd+Shift+R) to see the new image.
Filename capitalization matters. vanessa.jpg and Vanessa.jpg are different files on the web server. When you replace, use the exact same name and capitalization as the original.

Step 4 — Add a brand-new image to a folder

Use this when you want to add (not replace) a photo — for example, adding a new team member or a new ministry-highlights photo.

  1. Navigate to the correct folder in the repo (see Step 2).
  2. Near the top right, click "Add file""Upload files".
  3. Drag your image(s) into the upload area. Multiple files at once is fine.
  4. Before you drag: make sure the filename is descriptive and uses only letters, numbers, and dashes. Avoid spaces, emojis, and unusual characters. Example good names: pack-ship-april-2026.jpg, easter-worship.jpg, youth-outreach-may.jpg.
  5. Add a commit message like "Add April pack & ship photos" at the bottom.
  6. Click Commit changes.
Adding an image doesn't automatically make it appear on the site. The image file has to be referenced from somewhere in the HTML. If you want a new photo visible in, say, the Ministry Highlights grid, you also need to update news.html to point at it — that's a code change. Ping the dev on duty.

Step 5 — Update a specific spot on the site

Location

Homepage Showcase Carousel (fallback slides)

  1. Replace an image in assets/images/featured/ with the same filename.
  2. If you want different slide captions/titles, ping the dev. Captions live in the JavaScript and need a small code edit.

Location

Ministry Highlights grid (news page)

  1. Replace an image in assets/images/ministry-highlights/ with the same filename.
  2. Grid titles are in news.html. If you want to rename a tile, ping the dev.

Location

Team photos (about page)

  1. Navigate to assets/images/team/.
  2. Replace a photo with the same filename, or add a new one.
  3. If adding a new member (new filename), the about.html team section needs a code edit to include them. Ping the dev.

Step 6 — How to tell if your change went live

  1. After clicking Commit changes, you'll see a small progress indicator at the top of the page. Wait for it to finish.
  2. Go to the repo's Actions tab. There'll be a new workflow run named "pages build and deployment" — wait for its green check (usually 30–60 seconds).
  3. Open the live website and hard-refresh (Ctrl+Shift+R on Windows/Linux, Cmd+Shift+R on Mac). Normal refresh may serve a cached old version.
  4. If the change still isn't visible after 2 minutes + hard refresh, check the Actions tab for a red X — that means the build failed. Screenshot it and ping the dev.
Don't worry if you break something. GitHub tracks every change. If you commit a bad image or the site breaks after your edit, we can revert the change in under a minute. Nothing is permanent. Just ping the dev with the commit message you used and they'll sort it out.

🎧 Managing "What We're Listening To" & Partner Ministries

Both sections on the Community page read from a simple list in one JavaScript file. Adding or removing items is a small, guided edit — no images to upload, no styling to touch.

File to edit

assets/data/recommendations.json

Two arrays live here: "listening" (for "What We're Listening To") and "partners" (for Partner Ministries). Add or remove entries in those arrays — the builder tool below generates the JSON for you, so there's no JavaScript syntax to get wrong.

Interactive tool

🪄 Recommendations builder

Fill out the form. The live preview below uses the same Spotify/YouTube embed the site does — if it plays here, it'll play on the site. When it looks right, copy the JSON block and paste it into assets/data/recommendations.json. No JavaScript syntax to get wrong.

Live preview (this is exactly how it'll look on the site)

Fill out the form above to preview.
// Fill out the form above to generate a JSON block.

Paste this JSON block inside the "listening" array in assets/data/recommendations.json. Put it between [ and ], with a comma after the previous block's closing }. The last item in the array should not have a trailing comma.

Add a Spotify episode to "What We're Listening To"

4 steps

Add a Spotify episode

1 🎧

Copy the URL

Open the Spotify episode or show and copy the full URL from the address bar.

2 🪄

Use the builder

Scroll up to the Recommendations builder, pick the Spotify tab, paste the URL, fill in title/source/note.

3 📋

Copy + paste JSON

Click Copy JSON, then paste it into the "listening" array in recommendations.json.

4

Commit

The card appears on the Community page within 60 seconds.

4 steps

Add a YouTube channel

1 📺

Open the channel page

Copy the URL from the address bar — it should end in /channel/UC… or /@handle.

2 🪄

Use the builder

Pick the YouTube channel tab, paste the URL, and fill in the channel name and creator.

3 📋

Copy + paste JSON

Click Copy JSON, then paste it into the "listening" array in recommendations.json.

4

Commit

The card appears on the Community page within 60 seconds.

4 steps

Add an Instagram profile

1 📸

Open the profile

Copy the profile URL — https://instagram.com/<handle>/. Bare handles also work.

2 🪄

Use the builder

Pick the Instagram tab, paste the URL, and fill in the display name plus the optional avatar path.

3 📋

Copy + paste JSON

Click Copy JSON, then paste it into the "listening" array in recommendations.json.

4

Commit

The card appears on the Community page within 60 seconds.

4 steps

Add a Twitch channel

1 🎮

Open the channel

Copy the channel URL — https://twitch.tv/<channel>. Bare slugs also work.

2 🪄

Use the builder

Pick the Twitch tab, paste the URL, and fill in the display name.

3 📋

Copy + paste JSON

Click Copy JSON, then paste it into the "listening" array in recommendations.json.

4

Commit

The card appears on the Community page within 60 seconds.

  1. Open the Spotify episode or show in your browser. Copy the full URL from the address bar.
  2. Scroll up to the Recommendations builder tool above. Make sure the Spotify tab is selected.
  3. Paste the URL into the Spotify URL field. The ID and type (episode/show) are extracted automatically. Fill in the title, source, and note fields. Check the live preview — if it plays, it'll play on the site.
  4. Click 📋 Copy JSON.
  5. Open assets/data/recommendations.json on GitHub and click the pencil to edit.
  6. Paste the JSON block inside the "listening" array. Put a comma after the previous block's closing }. The last item in the array should not have a trailing comma.
  7. Commit. The card appears on the Community page within 60 seconds.

Add a YouTube video

Same workflow, but use kind: 'youtube' and use the video's ID (the string after v= in a YouTube URL).

{
  kind: 'youtube',
  id: 'dQw4w9WgXcQ',
  title: 'Video title',
  source: 'Channel name',
  note: "Why this stood out.",
},

Add a plain link (article, sermon on another site)

{
  kind: 'link',
  url: 'https://example.com/full-url',
  title: 'Article or sermon title',
  source: 'Author or ministry',
  note: "Why it matters.",
  image: 'assets/images/featured/some-image.jpg', // optional thumbnail
},

Remove an item

Delete its full block (the curly braces { ... } and the comma after) from the array. Commit. Card disappears.

Reorder items

Cut and paste the blocks into a new order. The order in the array is the order on the page.

Add a Partner Ministry

  1. If you have a partner logo, upload it first to assets/images/partners/ (create the folder if it doesn't exist — github.com's web UI lets you do this while uploading a file).
  2. Scroll up to the Recommendations builder tool and pick the 🤝 Partner Ministry tab. Fill in the name, website URL, optional logo path, and optional description.
  3. Click 📋 Copy JSON.
  4. Open assets/data/recommendations.json on GitHub and click the pencil to edit.
  5. Paste the JSON block inside the "partners" array, with a comma after the previous block's closing }. The last item in the array should not have a trailing comma.
  6. Commit. The partner card appears on the Community page within 60 seconds.
Common syntax mistake: every property inside a block needs a comma at the end, and every block needs a comma after its closing } (except optionally the last one). If you commit a broken file, the whole section will just disappear until it's fixed. When in doubt, ping the dev and copy-paste the example above exactly.

🖼️ Managing bundle slideshow images (store.html)

Each of the three bundle cards on the Store page has a rotating slideshow at the top of the card. The photos are pulled from three dedicated folders in the repo, and each folder has a tiny manifest file (images.json) that tells the site which photos to show and what caption to put on them. This is designed so you can keep the store feeling fresh without touching any HTML or CSS.

Folders to edit

assets/images/bundles/

Three subfolders, one per bundle card:

  • assets/images/bundles/essentials/ — Essentials card
  • assets/images/bundles/lifegroup/ — Life Group + Friends Starter card
  • assets/images/bundles/ministry/ — Jesus' Favorite Disciple / Ministry Needs card

Add a new photo to a bundle slideshow

Step by step

Add a photo to a bundle card

1 📐

Prep the photo

Resize to 1600×900, compress to under 300 KB. squoosh.app does it in one click.

2 🏷️

Name it with a number prefix

e.g. 03-group-study.jpg. Lowercase, dashes, no spaces or apostrophes.

3 📁

Upload to the right folder

Pick essentials/, lifegroup/, or ministry/ under assets/images/bundles/, drag the file in, commit.

4 📝

Edit images.json

Add a new { "file": "...", "caption": "..." } block to the images array in that folder.

5

Commit

Hard-refresh the store page. The new slide joins the rotation right away.

  1. Resize and compress your photo first. Aim for 1600×900 px (16:9) and under 300 KB. A free tool like squoosh.app handles this in one step.
  2. Rename the file with a numeric prefix so the order is obvious — for example 03-group-study.jpg. Use lowercase and dashes. No spaces, no apostrophes, no weird characters.
  3. In the GitHub repo, navigate into the right bundle folder (e.g. assets/images/bundles/lifegroup/) and click Add file → Upload files. Drag your photo in. Commit.
  4. In that same folder, open images.json and click the pencil icon to edit. Add a new block inside the "images" array:
    {
      "file": "03-group-study.jpg",
      "caption": "Friday night life group at Riverside"
    }
  5. Important: put a comma after the previous block's closing } so the JSON stays valid. The last block in the array should not have a trailing comma.
  6. Commit with a message like "Add group study photo to life group bundle." Hard-refresh the store page — the new slide rotates in automatically.

Replace a photo

Upload the replacement with the same filename, overwrite the existing one when GitHub asks, and commit. The caption in images.json stays the same. That's it.

Remove a photo

  1. Delete the image file from the bundle folder (click the file in GitHub → trash icon → commit).
  2. Open images.json in the same folder and delete the matching block (including its surrounding curly braces and the comma).
  3. Commit. The slide is gone.

Reorder the slides

The order of blocks in "images" is the order they appear. Cut and paste them into whatever order you want, commit, done. Renaming the file prefixes (01-, 02-, etc.) is a courtesy for future admins but not required for the rotation to work.

Change a caption

Open images.json, edit the "caption" field on the right block, keep the double quotes around the new text, commit. Captions can be empty ("caption": "") — the caption strip just won't show on that slide.

JSON syntax gotchas:
  • Every string needs double quotes. Don't use single quotes or smart quotes.
  • Every block (pair of {}) except the last one needs a comma after it.
  • Never put a comma after the last block in the array.
  • If the JSON is broken, the slideshow on that card just hides itself and logs a warning in the browser console. The rest of the page keeps working.

Test everything at once

  1. Go to store.html and hard-refresh (Ctrl+Shift+R / Cmd+Shift+R).
  2. You should see each bundle card rotating through its photos every ~5.5 seconds, with dots at the bottom of each slideshow.
  3. Hover over a card — the rotation pauses. Click a dot — it jumps to that slide.
  4. If a slideshow doesn't appear at all, open the console (F12) and look for a "could not load manifest" warning — usually it's a JSON typo.

📤 Team media drop (how the team shares photos/videos)

We don't have a separate upload page. Instead, we use an encouragement call-out inside the "Ministry Outreach & Highlights" section on news.html — the "Your story belongs here too" card. When assets/data/media-drop.json is configured, a 📤 Share Photos & Videos button appears next to "Submit a Testimony" and "Share on Telegram", and it opens our Google Form. Every submission lands in our ministry Google account with the uploader's name, caption, and the files themselves — plus a row in a Responses spreadsheet. Admins review from there and push the good stuff to the site.

The pipeline

Phone → Form → Drive → Site

1 📸

Capture

Team member takes photos/videos at an outreach or gathering.

2 📤

Share via form

Taps "Share Photos & Videos" on news.html → fills a short Google Form with name, caption, files.

3 📁

Lands in Drive

Files auto-upload to our team Google Drive folder. Metadata logged in a linked Sheet.

4 👀

Admin reviews

Scans new submissions in Drive or the Sheet. Picks what's site-worthy.

5

Publish

Commits good media to the repo's matching folder (outreach, highlights, team). Site updates in ~60s. Rest stays in Drive as an archive.

Config file

assets/data/media-drop.json

Controls whether the 📤 Share Photos & Videos button shows up on the news page, and where it points. Two options supported: formUrl (Google Form — recommended) or uploadUrl (direct Drive folder — less accountable). The button stays hidden on the site until one of these is set.

First-time setup — Google Form (recommended)

This is the cleanest path. Every upload arrives with who sent it, what caption they gave it, when, plus a row in a Responses spreadsheet. All files live in our own Google Drive.

  1. Sign in to forms.google.com with the ministry Google account (seedthewordministry@gmail.com).
  2. Click Blank form. Title it "Share Photos & Videos — Seed the Word".
  3. Add these questions (use the + button to add each one):
    • Your name — Short answer, required
    • Your email — Short answer, required, enable Response validation → Email
    • Where was this taken? — Short answer, optional (e.g. "UW Seattle outreach" or "Saturday study")
    • Tell us about it — Paragraph, optional
    • Upload photos & videos — File upload, required. Allow any file type. Set max file size to 10 MB (or higher if your storage allows) and max number of files to 10.
  4. Click the Settings tab:
    • Leave "Collect email addresses" unchecked (you already ask in a question, and this setting would force a Google login for verified email).
    • Under Responses, enable "Restrict to users in..."OFF so non-ministry-account users can submit.
    • Turn ON "Allow response editing" so uploaders can fix typos.
    Note: File upload questions require the uploader to sign in to any Google account. This is a Google policy we can't disable, but most phones are already signed in so it's invisible to them.
  5. Click the Responses tab → Link to Sheets → create a new spreadsheet. This is your review queue.
  6. Click the Send button (top right) → the link icon tab → check "Shorten URL"Copy.
  7. In the repo, open assets/data/media-drop.json and paste that URL into the "formUrl" field, replacing REPLACE_WITH_YOUR_FORM_ID.
  8. Commit. Hard-refresh news.html — the 📤 Share Photos & Videos button now appears on the "Your story belongs here too" call-out.

Where uploaded files live

Google Drive folder (auto-created by Forms)

Google Forms automatically creates a folder in our Drive named "Share Photos & Videos — Seed the Word (File responses)" (or similar) and drops every upload inside it. You can find the folder link inside the Form's Responses tab. Paste it into "adminReviewUrl" so you have a quick jump link bookmarked here.

Admin review flow

  1. Open the Form's Responses tab (or the linked Google Sheet) to see a table of every submission — uploader name, email, caption, links to files.
  2. Click a file link to preview it in Drive.
  3. For media you want to publish, download, resize/compress, and commit to the right folder in the repo (outreach, ministry-highlights, team, etc.). Flowcharts earlier in this page walk through each destination.
  4. Leave the rest in Drive as an archive. Nothing has to be deleted.

Alternative: direct Drive folder (less recommended)

If you'd rather skip the Google sign-in step and let anyone with the link drop files straight into a shared folder (no name, no caption, no accountability), you can use a plain Drive folder instead of a Form:

  1. Create a Drive folder. Right-click → Share"Anyone with the link"Editor.
  2. Paste the share URL into "uploadUrl" in media-drop.json (leave "formUrl" blank or as the placeholder).
  3. Commit.
Anyone-with-link Editor means anyone who gets the URL can see and add files to that folder. Less metadata, less oversight. The Form is usually the better call for a public-facing ministry page.

Turn the upload button off or swap targets

🙌 Managing Ministry Outreach cards (news.html)

The "Ministry Outreach & Highlights" section on the News page leads with up to 3 outreach cards, each with its own rotating slideshow of photos and videos. Each card represents one real outreach (a date, a location, a short write-up, an optional testimony). New events are added by dropping a new folder of media in and appending one entry to a master index — no HTML edits needed.

Master index (what the page shows)

assets/data/ministry-outreach.json

Contains an ordered events array. Only the top 3 are displayed on the News page. Keep the most-recent event first. When there are fewer than 3 real events, an automatic "Want to join the next one?" invitation card fills the empty slot.

Media folders (one per outreach)

assets/images/ministry-outreach/<folder>/

Each outreach gets its own subfolder. Pick a short kebab-case name that hints at the event (e.g. slavic-awakening-bellevue-may-2026). Inside, you drop the photos/videos and an images.json file listing them in play order.

Add a new outreach card

Step by step

Add a new outreach to the News page

1 🗂️

Name the folder

Kebab-case with a date, e.g. bellevue-park-july-2026.

2 📸

Upload media

Under assets/images/ministry-outreach/<folder>/ — photos 01.jpg, 02.jpg… and videos under 10 MB.

3 📝

Write images.json

List media in play order inside the same folder (see the template below).

4 📋

Register the event

Open ministry-outreach.json and add a new entry at the top with title, date, location, body. Leave testimony blank.

5

Commit

The card shows on the News page and the homepage's Latest Outreach right away. Add a testimony later when you have one.

  1. In the repo, open assets/images/ministry-outreach/. Click Add file → Create new file and type a folder path like bellevue-park-july-2026/images.json (GitHub creates the folder for you as you type the slash).
  2. Upload your photos and videos into that new folder. Prep guidelines:
    • Photos: 1600×1200 or similar 4:3, under ~300 KB each. Rename to 01.jpg, 02.jpg, 03.jpg … for predictable ordering.
    • Videos: MP4, vertical or horizontal both fine. Keep under ~10 MB each; compress on freeconvert.com if needed. Name them video-01.mp4, video-02.mp4.
  3. Open the images.json file you just created and paste this template (adjust filenames to match what you uploaded):
    {
      "media": [
        { "file": "01.jpg", "type": "photo", "caption": "" },
        { "file": "02.jpg", "type": "photo", "caption": "" },
        { "file": "video-01.mp4", "type": "video", "caption": "Moment from the outreach" },
        { "file": "03.jpg", "type": "photo", "caption": "" }
      ]
    }
    Order is play order. Photos hold for ~5.5 seconds; videos play their full length before advancing. Captions are optional — leave "" to hide the caption strip.
  4. Now open assets/data/ministry-outreach.json and add a new entry at the top of the events array (most-recent first):
    {
      "folder": "bellevue-park-july-2026",
      "title": "Summer Park Evangelism",
      "date": "July 12, 2026",
      "location": "Downtown Park, Bellevue",
      "body": "A short paragraph about the outreach — who we partnered with, what you handed out, how the day felt.",
      "testimony": ""
    },
    Put a comma after the closing } if there are other entries below it.
  5. Commit both changes with a message like "Add July 12 Bellevue park outreach card." Hard-refresh news.html and the new card appears at the front of the outreach row.

Add a testimony to an existing outreach

Open assets/data/ministry-outreach.json, find the right event, and fill in the "testimony" field with a 1-3 sentence first-person quote. Keep the double quotes around it. Example:

"testimony": "Aliosha handed me a pocket New Testament and said he'd pray for my studies. I hadn't opened a Bible in 10 years. Two weeks later, I'm still reading."

Commit. The quote appears in a gold-bordered block on the card.

Change the order of outreach cards

The order of the events array is the display order. Cut and paste the blocks to reorder. Only the top 3 appear on the page — the rest sit archived in the file for later.

Remove an outreach from the page

Two options:

Videos auto-play muted. Browsers block unmuted autoplay, so the slideshow plays videos with sound off. If you need audio, use Instagram or YouTube for that piece of content instead of an outreach card.

🏠 Managing the Homepage

The homepage is intentionally tight: Hero → Featured From Our Ministry (Showcase Carousel) → Walking the Path of Light → Footer. The Showcase Carousel does all the rotating-content heavy lifting, and the daily verse, tips, and featured highlights are integrated into it.

Daily verse rotation (used by the Showcase Carousel)

assets/data/daily-verses.json

A list of verses that rotates by day of year. Verse #N is picked using dayOfYear % verses.length, so the order matters only for "which verse lands on which day" — it doesn't cycle manually. Add more verses to expand the pool.

Add a new verse to the rotation

  1. Open assets/data/daily-verses.json in the repo and click the pencil icon to edit.
  2. Inside the "verses" array, add a new block at the end:
    { "text": "Full verse text here.", "ref": "Book 1:1", "version": "KJV" }
  3. Put a comma after the previous block's closing } so the JSON stays valid. The last block should NOT have a trailing comma.
  4. Commit. The new verse joins the rotation. It may not land on today's screen — that depends on the day-of-year math.

What the Showcase Carousel rotates

Edit the showcase slides or daily content

Both lists live at the top of assets/js/showcase-carousel.jsFALLBACK_SLIDES and DAILY_CONTENT. This is a JS file, so follow JSON-ish syntax carefully: single quotes, commas after every item, no trailing comma on the last one. If in doubt, ping the dev on duty.

🌱 Walking the Path of Light (homepage ministry map)

Ministry framing

Seed → Water & Grow → Harvest → Repeat

1 🌱

Seed

Evangelism & new believers. Grace first — action before theology. Tuesday meetings are where this begins.

2 💧

Water & Grow

Discipleship & deepening. Amen Prayer, Philippians 4:6–8, Saturday studies using the 5 pillars framework.

3 🌾

Harvest

Placement & maturation. Commission members into a local church body. Mentorship anchored in 1 Timothy 1:5.

🔄

Repeat

For the next person He brings us.

The homepage's second section, anchored in Psalm 119:105 ("Thy word is a lamp unto my feet, and a light unto my path"), draws the ministry's journey as a glowing path of waypoints on a dark canvas. Each waypoint card has a short paragraph, a tag line, and a call-to-action link. Changes here require an HTML edit in index.html (search for the-path). Keep paragraphs short — the cards are meant to be scannable. If the team's lifecycle framing evolves, update all three phase cards together so the loop still makes sense.

🔧 How to do common tasks

Add a new event to the calendar

  1. Sign in to calendar.google.com with seedthewordministry@gmail.com.
  2. Click anywhere in the calendar on the target date.
  3. Enter a title, pick a start/end time, add a description.
  4. Save. The event shows on the website within 1 minute.

Publish a new podcast episode

  1. Go to podcasters.spotify.com and sign in.
  2. Click New Episode → upload the audio file (MP3 or WAV).
  3. Add a short title and 1-2 sentence description.
  4. Click Publish. The episode appears in the site's Spotify embed automatically (may take a minute).

🎙️ Set up the Bible Audio pipeline

The Bible Audio pipeline captures volunteer-recorded chapter readings posted to the Today's Chapter Telegram topic, runs them through a conservative ffmpeg cleanup, and emails the team a per-chapter checklist for the manual Spotify upload above. Three stages: an Apps Script poller pulls voice memos from Telegram into Drive, a nightly GitHub Actions workflow cleans them with ffmpeg, and a second Apps Script trigger emails the team at 07:00 PT the next morning. The pipeline plugs into the existing bible.todayChapterTopic block in assets/data/telegram-bot.json — same chat, same topic id (13).

Until you finish all six steps below, the pipeline is a no-op: the committed config has bible.audio.enabled set to false, the Apps Script entry points bail at the top, and the cleanup workflow exits 0 immediately.

  1. Create the Google Cloud service account. Go to Cloud Console → IAM & Admin → Service Accounts while signed in as seedthewordministry@gmail.com. Pick the existing project (or create a new one named stw-ministry-automation). Click Create Service Account. Name it stw-bible-audio-cleanup. Skip the optional grant-access steps.
  2. Enable the Drive API in the same project: Cloud Console → APIs & Services → Drive APIEnable.
  3. Generate a JSON key. On the service account's row, Actions menu → Manage keys → Add key → Create new key → JSON. The browser downloads stw-bible-audio-cleanup-XXXXXXXX.json. Treat this file like a password — don't email it, don't commit it.
  4. Share both Drive folders with the service account's email. Create two folders in your Drive (any names — e.g. STW Bible Recordings - Raw and STW Bible Recordings - Cleaned). The service account has an email like stw-bible-audio-cleanup@<project>.iam.gserviceaccount.com. Open each folder → Share → paste that email → set role to Editor → uncheck Notify people (service accounts have no inbox) → Send. Copy each folder's ID from its URL (the long string after /folders/) — you'll paste them in step 6.
  5. Paste the JSON key into a GitHub Secret. Open the repo's Settings → Secrets and variables → Actions → New repository secret. Name: GDRIVE_SERVICE_ACCOUNT_JSON. Value: open the downloaded JSON file in a text editor, copy the entire contents (curly brace to curly brace), paste, Add secret. Then delete the local JSON copy from your downloads folder and empty the trash.
  6. Stand up the Apps Script project. Sign in to script.google.com as seedthewordministry@gmail.com. Click + New project, rename it to STW Bible Audio. Open the repo file docs/apps-script/bible-audio.gs, copy its full contents, paste over Code.gs, save. Open Project Settings (⚙️) → Script Properties → Add script property: name TELEGRAM_BIBLE_BOT_TOKEN, value the @seedtheword bot's token (same one used by the daily Bible bot). Save. Run registerBibleAudioTriggers once from the function dropdown — it installs 30 half-hourly poller triggers (07:00–21:30 PT) plus one daily 07:00 PT notifier trigger. Authorize when Google prompts (UrlFetch + Drive + Mail scopes).

Then in the editor: open the admin editor → Bible → Audio config, paste both Drive folder IDs from step 4, flip enabled to true, save & commit.

How it runs: after the morning post on Mon–Fri, the topic auto-renames to today's chapter ("Today's Chapter is Luke 7"); volunteers post voice memos into the topic; cleaned MP3s land in your inbox at 07:00 PT the next morning with a 5-step Path A checklist that mirrors the podcast publishing walkthrough above.

Key rotation (every 12 months, or any time the JSON key is suspected leaked): in Cloud Console → Manage keys on the service account, Add key → Create new key → JSON. Update the GDRIVE_SERVICE_ACCOUNT_JSON secret with the new value. Trigger Bible Audio Cleanup manually (Actions → workflow_dispatch → Run workflow) to confirm the new key works. Then back in Cloud Console delete the old key — that's the actual revocation step.

Change the livestream schedule

The "LIVE NOW / OFFLINE" logic is hardcoded in assets/js/twitch-integration.js. If the schedule changes (e.g., moves from Saturday 7 PM), the site needs a code edit. Ping the dev on duty.

Trigger a fresh Instagram sync

  1. Go to the repo's Actions tab.
  2. Pick Scrape Instagram Feed from the left sidebar.
  3. Click Run workflow → choose branch mainRun workflow.
  4. Wait ~1 minute. The site updates within a minute of completion.

⚙️ Set up the order email handler (Apps Script Web App)

The bundle-builder review form on bundle-builder.html can either submit to a free Formspree inbox (default) or to a Google Apps Script Web App you own. The Apps Script path adds three things Formspree's free tier can't: an automatic gifter receipt, a permanent Google Sheet ledger of every order, and an optional heads-up email to the giftee. Setup takes about 10 minutes the first time, no installs, no payments.

Heads-up: MailApp.sendEmail sends as the script owner via Google's session — there is no SMTP, no Gmail App Password, no 2FA prerequisite for this script to function. (2FA on the account is generally a good idea, but unrelated.)

  1. Sign in to script.google.com as seedthewordministry@gmail.com.
  2. Click + New project. Rename it to STW Order Handler (top left, click the title).
  3. In another tab, open sheets.new. Rename the spreadsheet to STW Order Ledger. Rename the first tab from Sheet1 to Orders (right-click the tab → Rename). (Already done — the existing Sheet ID is baked into order-handler.gs.)
  4. (Skipped — Sheet ID is already in the source.)
  5. Open the repo file docs/apps-script/order-handler.gs in another tab. Copy its full contents.
  6. Back in the Apps Script editor's Code.gs, select all (Ctrl/Cmd+A) and paste over.
  7. (Skipped — no placeholder to replace; LEDGER_SHEET_ID is already populated.)
  8. Click Save (disk icon, top toolbar). Then click DeployNew deployment. Set Type = Web app. Set Description = v1. Set Execute as = Me (seedthewordministry@gmail.com). Set Who has access = Anyone. Click Deploy.
  9. Authorize when prompted — Google will ask for permission to access Sheets and send Gmail. Click Allow. (You may see an "unverified app" warning — click AdvancedGo to STW Order Handler (unsafe). This is expected for personal scripts.)
  10. Copy the Web app URL at the end of the dialog (starts with https://script.google.com/macros/s/…/exec).
  11. Open the admin editor → pick Site config (cross-cutting) → paste the URL into orderHandlerUrlSave & commit.

Verify it works: hard-refresh bundle-builder.html in another tab. Place one test Essentials order to your own email, ticking the giftee opt-in box and entering a second email you can read. You should get three emails arrive (gifter receipt, team notification, giftee heads-up) and a new row in the Orders tab of the Sheet. For Ministry Calling you'll get two emails (the giftee path is intentionally never used for outreach orders).

If something looks off: open the Apps Script editor → Executions tab in the left sidebar — every doPost call logs there with full input/output. Most issues are a typo'd LEDGER_SHEET_ID or the deployment access set to Only myself instead of Anyone.

Updating the script later: when docs/apps-script/order-handler.gs changes in the repo, repeat steps 5–6 (paste new contents over Code.gs), then click Deploy → Manage deployments → ✏️ pencil → New version → Deploy. The Web app URL stays the same.

📖 Wire the Share-Your-Story Google Form to the team email

The "Share your story" modal on news.html iframes the same Google Form configured in assets/data/media-drop.json. To get the styled team-notification email and a row in the Stories tab of the STW Order Ledger every time someone submits, we install an On form submit trigger on onStoryFormSubmit. One-time setup, ~5 minutes.

Why a programmatic trigger install? The STW Order Handler is a standalone Apps Script project (created at script.google.com directly), not bound to a spreadsheet. The trigger UI's Event source dropdown only offers Time-driven and From calendar in standalone projects — From spreadsheet is hidden. We get around that by calling ScriptApp.newTrigger(...).forSpreadsheet(...).onFormSubmit().create() from inside the script itself, which Google honors regardless. The helper functions installStoryTrigger() and removeStoryTriggers() live at the bottom of order-handler.gs for exactly this purpose.

  1. Open the Form (Share Photos & Videos — Seed the Word) in the editor (click the pencil icon if you only have the public link, or open it from forms.google.com while signed in as the ministry account).
  2. Click the Responses tab → green Sheets icon → Select existing spreadsheet → pick STW Order Ledger (the same sheet your orders write to: 17j5TDDTZ-58MuZ7VO7c1ohPkyHw2LZ2GCWYMFb-CJ50). Click Select. Google will add a new tab to the spreadsheet automatically — leave it; our trigger writes to its own Stories tab and ignores Google's auto-tab.
  3. Open the Apps Script editor (script.google.comSTW Order Handler). Make sure the latest order-handler.gs from the repo is pasted into Code.gs and saved (the trigger-install helpers were added in the same commit that fixed onStoryFormSubmit).
  4. In the function dropdown at the top of the editor (just left of the ▶ Run button), pick installStoryTrigger.
  5. Click ▶ Run. Authorize the new Forms scope when Google prompts (you'll see "STW Order Handler wants to access… Manage your forms in Google Drive" — click Allow).
  6. Open the ⏰ Triggers tab (clock icon, left rail). Confirm there's now an entry for onStoryFormSubmit with event type On form submit. Done.

Verify it works: open the live news.html, click 📤 Share photos, video & testimony, fill in the form with your own name/email and any caption, and submit. Within ~30 seconds you should see a styled email at seedthewordministry@gmail.com with subject [STW Story] {your name}, and a new row in the Stories tab of the STW Order Ledger.

If no email arrives: open the Apps Script editor → Executions tab. Each form submission should log an onStoryFormSubmit entry with the trigger payload. Common failure modes:

To remove the trigger (e.g. switching forms, debugging duplicate emails): pick removeStoryTriggers from the function dropdown and click ▶ Run. It deletes every existing onStoryFormSubmit trigger.

If you'd rather skip the form entirely and have the modal POST directly to the same Apps Script Web App (the same path the contact form uses): change the Share-Your-Story modal in news.html from an iframe to a native form, and have it POST { type: 'story', name, email, story, mediaUrl, consentToPublish } to the URL in orderHandlerUrl. The handler already has a handleStory branch wired up for that path; you just lose the file-upload affordance Google Forms gives you for free.

✅ Verify the bundle customization tiers

The store-page journey now organizes every customization into three tiers: ⭐ Recommended (gold-bordered pills with a star prefix), Standard (normal pills), and 🛎 Special-order (collapsed talk-to-team card). Picking any special-order item changes the order flow: the CTA reads "Continue to share your special-order request →", the team email subject becomes [STW Special Order], and the Sheet's column O is marked yes. After any catalog edit (assets/js/bundle-item-catalog.js) or Apps Script redeploy, walk this checklist to confirm everything still lines up:

  1. P1 — tiers render correctly. Hard-refresh store.html, click Essentials → Start customizing → For myself. In step 2's "On the Bible" section, confirm Engraving, Custom painting, and Page-edge spray painting are gold-bordered ★ pills listed first. Confirm a 🛎 Special-order options card exists below — collapsed by default — containing items like Foil stamping and Edge gilding. Repeat for Life Group step 4 and Ministry step 3.
  2. P2 — special-order propagation. On any bundle, expand the special-order card and tick one item (e.g. Edge gilding). Advance to review and confirm: (a) a gold informational banner above the form lists the flagged item, (b) the submit button reads "Continue to share your special-order request →", (c) the team email subject after submit reads [STW Special Order] and the body has a "🛎 Special-order items — confirm before fulfilling" section, (d) the Sheet's column O for that row is yes.
  3. P3 — tooltip accessibility. Tab through any step. Each pill should be followed in tab order by a small (i) icon with a 3px gold focus outline. Press Enter or Space on the icon — a dark tooltip should appear with the item's plain-English description. On mobile (or DevTools 375px width), tap the (i) icon — the tooltip should appear inline below the pill row, not overlap the next pill.
  4. Sign-opt-out. On Essentials step 2 or Life Group step 4, scroll to the bottom and tick Skip the back-cover ministry signing. The review summary on bundle-builder.html should read "Back-cover signing: SKIPPED (per gifter request)" and the team email after submit should include a "🖋 Back-cover signing: SKIPPED per gifter request — do not sign." line.
  5. Backward-compat. Old ?c= URLs from before this change should still decode and render — the schema extensions are additive, with missing fields defaulting to [] or false.

If any of those don't behave as described, paste the screen state and we'll trace it from the live system. Most regressions show up in the Apps Script Executions tab — every doPost call logs the full payload + the script's reconciled isSpecialOrder decision.

🧯 Troubleshooting — if the site looks broken

Start here

Something looks wrong — what to check first

1 🔄

Hard-refresh

Ctrl+Shift+R / Cmd+Shift+R. Stale cache is the #1 cause. Fixed? Stop here.

2 🔍

Narrow it down

Scroll through the specific sections below: calendar, IG images, contact form, podcast, livestream, or something else.

3 🛠️

Try the fix

Most issues resolve by checking the service's dashboard or re-running the relevant GitHub Action.

4 📣

Still stuck? Ping the dev

Screenshot the browser console (F12) + Actions tab + what you tried. Send to the dev on duty.

Calendar is empty on news.html

Instagram images look broken

Contact form isn't sending

Podcast embed is blank or broken

Livestream says "LIVE" when we're not, or vice versa

Something else looks wrong

📣 Announcing events to Telegram, Instagram & beyond

Every event on the News page has a single 📣 Share Event button in its modal. Tapping it opens a dropdown with multiple destinations — Telegram, WhatsApp, Instagram, the device share sheet, or plain clipboard — and pre-fills each one with an announcement following our ministry format. No retyping, no copy-paste dance.

3 steps to share

Announce an event anywhere in under 30 seconds

1 📅

Open the event

On news.html, click the event on the calendar. The event modal opens.

2 📣

Tap Share Event

A dropdown appears with destination options.

3 🚀

Pick your destination

Device share sheet, Telegram, WhatsApp, Instagram (copies caption), plain copy, or event-link-only. Each one is pre-filled with the ministry announcement template.

The announcement format

The Share button uses our ministry template. Every event auto-generates something like this:

❕! MAY 25, 10AM ! ❕
🌱Seed The Word! 🌱
@ Lincoln Rock State Park!

Address:
📍Lincoln Rock State Park, 13253 US-2, East Wenatchee, WA 98802, USA

🙏🏻🤍Hello youth!! 🎉
Memorial Day is coming up soon!

Sign ups are open now!

Date & time 📆 : May 25th, 10am
Cost💲: 20$

Sign up with : @Alina Matyuk-Pavlun
(contact @seedtheword admins for details)

We can\u2019t wait to see you there! \u2728

More Details (https://seedtheword.github.io/seedtheword/news.html#event=abc123)

How each field is generated (so Calendar edits drive the post)

Share destinations in the dropdown

Shareable event links

Every event has a direct URL: news.html#event=<event-id>. Clicking it opens the News page with that event's modal already expanded. The URL in your address bar updates automatically when you open an event, so you can grab it from there if you prefer. Closing the modal cleans up the hash.

Pro tip: edit the event in Google Calendar to control the post

Since everything in the announcement comes from the Calendar event, you can tune the wording by editing the Calendar entry:

Want fully-automated posting (no tap required)?

Done — see the next section for how the Telegram bot handles scheduled announcements automatically.

🤖 Telegram bots — announcements, Bible, prayer

We run three separate Telegram bots so members see clear senders in the chat and each bot can be paused independently. All three post to the @seedtheword supergroup, each into a different topic.

Shared config file

assets/data/telegram-bot.json

One file, three sections — "announcements", "bible", "prayer". Each has its own enabled, chatId, messageThreadId, and settings. The bot tokens live only in GitHub Secrets.

📸 How to attach a photo to a calendar event

Both the Telegram announcements bot and the News-page event cards pull photos from whatever image URLs appear in a calendar event's description. This is the one folder for everything pattern — set sharing once on a single Drive folder, and every file you drop in there inherits the correct sharing automatically.

Use the calendar paperclip icon

📎 Paperclip your photo right into the event. Apps Script mirrors it to the shared folder within 5 min, hands the URL to the bot, and removes the paperclip. No copy-paste.

📂 Open folder now →

Quick link

📂 Shared "STW Calendar Photos" Drive folder

Loading…

One-time setup (do this once, ever)

🗂 Create the shared "STW Calendar Photos" Drive folder

  1. Open drive.google.com, sign in as the ministry account.
  2. Click New → Folder. Name it STW Calendar Photos (or pick any name and note it in the config below).
  3. Right-click the new folder → Share.
  4. Under "General access", change Restricted to "Anyone with the link", then confirm role is Viewer. Click Done.
  5. Right-click the folder again → ShareCopy link. Save that URL somewhere the team can find it.

Why this matters: anything you drop into this folder from now on inherits the folder's "Anyone with the link: Viewer" access. No per-file sharing. No sign-in wall when Telegram or the website fetches the image.

Workspace account gotcha: if your ministry Google account is on a Workspace domain with "external sharing blocked", step 4 will fail. In that case either (a) ask your workspace admin to allow external link sharing on this one folder, or (b) use a personal Gmail account for the photos folder.

Everyday workflow

📅 Attach a photo to an event (the easy way)

The simplest path now: open the calendar event, click the 📎 paperclip icon, upload your image. Save. That's it. The Apps Script mirrorCalendarPaperclipAttachments_ trigger runs every 5 minutes — within that window it copies the attachment into the STW Calendar Photos folder, pastes the public URL into the event description, and removes the original paperclip. You walk away; it stages itself.

The classic upload-to-Drive-then-paste-URL workflow still works (described below) and is useful for events you want to attach photos from the public web to. But for everyday paperclip uploads, the mirror does the work.

Manual workflow (if you prefer)

📂 Drop in Drive, paste URL into description (4 steps)

  1. Upload the image into the STW Calendar Photos Drive folder (drag-and-drop works).
  2. Right-click the new file → ShareCopy link. The URL looks like https://drive.google.com/file/d/<id>/view?usp=sharing.
  3. Open the Google Calendar event. In the Description field, paste the URL on its own line. You can paste multiple URLs (up to 10) to send an album.
  4. Save. The next announcement tick (within 15 min for announcements, 8 AM next day for Bible-bot) picks it up automatically.

Also supported (no setup): any public .jpg/.jpeg/.png/.webp/.gif URL, or an <img src="..."> tag inside the description. Instagram and Facebook Page cover images work well (right-click the image in your browser → Copy image address).

Verify before going live

🧪 Test that your photo will actually attach

Before relying on the next scheduled run to surface a problem, fire a one-shot preview that pulls from the next future event with attached photos and posts to the announcements channel with a clear "(test)" header. Two ways to trigger:

  • From Apps Script (recommended for admins): open the Apps Script editor → function dropdown → run kickAnnouncementBotTest. The execution log returns immediately; the test post lands in Telegram within ~1 minute.
  • From GitHub: Actions tab → Telegram AnnouncementsRun workflow → set test_run = true.

The test posts the actual caption shape members will see (only the header is replaced). If the photo doesn't show up in the test, the workflow log explains why — most often "URL not usable: served HTML, not image bytes" (Drive file isn't shared "Anyone with the link") or "no future events with reachable photo URLs found" (no upcoming event has a URL pasted in its description yet). Test runs do NOT touch the dedup log, so you can re-fire as many times as you want while iterating.

Nudge template

💬 Reminder message to paste in the group chat

Most of the time the paperclip "just works" because the Apps Script mirror handles it. But when a teammate uses a private Drive link or the mirror's upstream service is having a bad day, the photo silently won't appear. If that happens, paste this in the admin chat:

Hey — for event photos, the easiest way is the 📎 paperclip icon in the calendar event itself. The mirror handles the rest within 5 minutes.

If that doesn't work for some reason, the manual fallback is to upload to our shared STW Calendar Photos folder first:

{{FOLDER_URL}}

Then paste the file's share link into the calendar event's description. Thanks! 🙏

What happens under the hood

🔧 From Drive link to Telegram photo

  1. The script scans the event's description for image URLs and <img src> tags.
  2. Drive share links like /file/d/<id>/view are auto-rewritten to lh3.googleusercontent.com/d/<id>=w2000, Google's actual image CDN. The old drive.google.com/uc endpoint is unreliable and has been deprecated — the script skips it.
  3. Before posting, the Python script does a GET pre-flight to confirm the URL serves image bytes (not an HTML login page). If the pre-flight fails (file not public, 404, etc.), the script logs the reason and falls back to a text-only post so the announcement still fires.
  4. On the website, the same image URLs render as a hero banner on the event modal and as thumbnails on the compact announcement cards.
  5. The URLs themselves get stripped from the visible caption/description so they don't appear as literal text.

Troubleshooting

🔒 I posted but no image appeared

Most common cause: the Drive file inherited restricted sharing (e.g., you uploaded to a folder that isn't public, or your Workspace domain blocks external sharing).

How to check:

  1. Open the Drive file → Share button.
  2. Under "General access" it should say "Anyone with the link" · Viewer. If it says "Restricted" or "Only people with access", that's the problem.
  3. Fix by clicking the access selector → Anyone with the linkDone.
  4. Also check: open the file's share URL in a private/incognito window. You should see the image preview page without being asked to sign in. If it asks for a login, Google still considers it restricted.

Other causes:

  • File over 25 MB: Google inserts a virus-scan interstitial. Downsize the photo to under 5 MB before uploading.
  • Telegram caption over 1024 chars: the caption gets trimmed. No action needed.
  • Red "🔒 Photo not public" banner on the website: same problem — fix Drive sharing.

The script logs its pre-flight decision into the workflow run output — look for lines like Image URL not usable for event <id>: <url> — content-type=text/html. That content-type string tells you Drive returned an HTML page instead of an image.

Bot 1 — Announcement bot

Bot 2 — Daily Bible bot

Bot 4 — Weekly playlist digest

One-time setup

🔑 Add Spotify API credentials

The digest needs a (free) Spotify developer app to read the playlist. Setup takes 5 minutes:

  1. Open developer.spotify.com/dashboard and sign in with the ministry Spotify account.
  2. Click Create app. Name: Seed the Word Digest. Description: whatever. Redirect URI: http://localhost (required but unused for us). APIs used: Web API. Accept the terms → Save.
  3. In the app's dashboard, click Settings. Copy the Client ID and click View client secret → copy that too.
  4. Open GitHub repo → Settings → Secrets and variables → Actions. Add two secrets:
    • SPOTIFY_CLIENT_ID → paste the Client ID
    • SPOTIFY_CLIENT_SECRET → paste the Client Secret
  5. Run the workflow once manually with dry_run=true to verify. When the Saturday run fires, newly-added tracks will post automatically.

Safety note: App-only credentials can only read public playlist data — they cannot add, remove, or modify tracks. Zero risk of the bot doing something to your playlist.

Paste & pin

📌 Pinned intro for the Worship & Music topic

Copy this message, paste it into the Worship & Music topic (https://t.me/seedtheword/1064), then tap the message → Pin. Members see it as the first thing when they open the topic.

🎵 STW Community Playlist

Drop in songs that worship God, minister to your soul, or remind you who He is. We all listen together.

▶️ Listen to the playlist
https://open.spotify.com/playlist/18QMpMTYrFt2KxW1Q9NsvX

➕ Join as a collaborator (one tap)
https://open.spotify.com/playlist/18QMpMTYrFt2KxW1Q9NsvX?si=69aaa882a74d40c8&pt=3cf27aeb2c1ffec6bf48b7345e141c26

How to add: tap the green link above once (Spotify opens, accept the invite). After that, any song you're playing in Spotify has Add to playlist → STW Community Playlist in the three-dot menu — on phone or desktop.

Every Saturday at 8 AM PT, a digest of the week's new additions lands in this topic automatically. 🎧

Telegram uses a simpler markup than the bots — just type **bold** and it bolds on send, or use the formatting menu on mobile. The URLs auto-preview the playlist cover so you don't need to attach an image.

Bot 3 — Prayer & Thanksgiving nudge bot (disabled)

Bot 5 — Weekly prayer digest (disabled by default)

One-time setup

🔑 Disable BotFather privacy mode for the prayer bot

Without this, the bot only sees its own commands in the supergroup — non-command prayer messages are invisible to it and the digest stays empty. This is a one-time toggle:

  1. Open @BotFather in Telegram.
  2. Send /mybots → tap the prayer bot → Bot SettingsGroup Privacy.
  3. Tap Turn off. BotFather confirms with "Privacy mode is disabled for [bot]".
  4. Confirm the bot is in the @seedtheword supergroup as an admin with Manage Topics + Send Messages permissions on thread 21.

If you skip this, the Poller will run every 15 minutes and silently capture nothing. The fix is to come back to this card and flip privacy mode off — no code changes needed.

Optional setup

✨ Wire up Gemini for prettier digest summaries

This is optional. If you skip it, the digest still posts — it just uses the rule-based sentence-boundary truncator. With Gemini wired in, each bullet becomes a 1-2 sentence summary in the human's voice ("David asks for prayer over ministry direction.") instead of a truncation ("Members, what a pleasure it has…").

  1. Visit aistudio.google.com while signed in to the Google account that owns the ministry workspace.
  2. Click Create API key. Copy the key — you won't see it again. Keep it out of chats and never paste it into the codebase.
  3. In GitHub: Repo → Settings → Secrets and variables → Actions → New repository secret. Name it GEMINI_API_KEY. Paste the key. Save.
  4. Confirm prayer.digest.summarizer is set to "llm" in assets/data/telegram-bot.json (the default after May 2026).
  5. Run the next digest manually to verify: Actions → Weekly Prayer Digest → Run workflow → dry_run = false. Check thread 21 for the result.

What gets sent to Google: only the body text of messages that have already been posted to the public Prayer & Thanksgiving topic. [private]-tagged messages are filtered out at capture time and never leave our pipeline. [anon]-tagged messages have their FROM-field resolved to "Anonymous" before anything reaches Gemini.

Volume: at our pace (~3 digests/week × 15 entries) we use under 6% of Gemini's daily free quota. There is no expected scenario where this costs money.

If you ever want to disable it: flip prayer.digest.summarizer back to "rule-based" in telegram-bot.json and commit. The next digest reverts immediately, and the GEMINI_API_KEY Secret can stay in place dormant.

Per-member privacy

🔒 What members can do without asking an admin

Three opt-out paths are baked into the digest. None of them require admin action — they're discoverable in every digest's footer.

  • /skipdigest — any member can DM the prayer bot this command and they're permanently excluded from future digests. The bot replies once to confirm. To re-enable: /optindigest.
  • [private] — prefix any single message in the topic with this tag and that one message is silently skipped (never makes it into the log, never appears in any digest). The member's other messages still flow normally.
  • [anon] — prefix a message with this tag and it's still included in the digest, but rendered as Anonymous instead of the member's display name. The original sender is recorded in the log for audit only and never rendered.

Members joining the supergroup will see these in the footer of every digest, so the privacy paths are always one tap away.

Going live

🟢 How to enable the digest

Default state on a fresh fork is disabled. To turn it on:

  1. Confirm the BotFather privacy step above is done.
  2. Confirm the sibling website prayer-intake form is also deployed (or accept that website-form submissions won't flow into the topic until it is — the digest still works for messages members post directly into Telegram).
  3. Open Editor → Telegram bot config → Prayer nudge bot → digest. Flip enabled to true. Save → Commit & push.
  4. Within ~15 minutes the Poller starts capturing. The first scheduled Mon/Thu/Sat morning after that, the digest posts.

To pause: flip the same flag back to false. Both the Poller and the Digest_Poster exit immediately at the enabled-flag short-circuit. Already-captured entries stay in the log; they'll be pruned by the 14-day cutoff if the system stays paused that long.

Recovery

🛟 If a digest didn't post on time

  1. Check Actions → Weekly Prayer Digest for a failed run. The error message is usually self-explanatory (most often a Telegram MarkdownV2 parse error from an unexpected character — rare but possible).
  2. Fix the cause. If you can't tell, replay with dry_run = true to see the rendered message Telegram rejected.
  3. Click Run workflow with dry_run = false to fire the digest now. The script computes the current Schedule_Slot in PT and is idempotent — if the slot is already marked complete, it logs "already posted this slot" and exits.
  4. If the digest posted but the log commit failed (rare race): manually edit assets/data/telegram-prayer-log.json and add the slot to completedSlots so the next run doesn't double-post. The slot ID format is YYYY-WNN-{mon|thu|sat} in PT (e.g. 2026-W22-mon).

What to look for

📓 The on-disk Prayer_Log

Lives at assets/data/telegram-prayer-log.json. A healthy file looks like:

{
  "schemaVersion": 1,
  "lastUpdateId": 84321,
  "lastModified": "2026-05-23T15:00:09Z",
  "optOut": [],
  "completedSlots": {
    "2026-W21-thu": { "since": "...", "messageId": 4731, "entryCount": 4, "postedAt": "..." }
  },
  "entries": [
    { "chatId": -1001234567890, "messageId": 4521, "userId": 987654321,
      "displayName": "Maria K", "kind": "telegram", "anonymous": false,
      "text": "stripped body for summarizer", "ts": "2026-05-21T18:42:00Z" }
  ]
}

Don't edit by hand unless recovering from a stuck slot — the file is committed back by both workflows on every successful run, and any concurrent edit will lose to whichever workflow runs next.

Bot 7 — Saturday Discussion Icebreaker

If the icebreaker reads off this week

🧭 The walks driving the question

The icebreaker reads from the same three streams the Bible bot's daily footer renders: NT (anchored at bible-plan.js's 2026-04-30 = Mark 11), OT history (bible.layeredPlan.streams.otHistory.anchor), and Poetry & Prophecy (streams.poetryProphecy.anchor). If a stream's enabled flag is false, the icebreaker silently omits it from the prompt — no error, just narrower context for Gemini. Re-anchor any stream and the next icebreaker will reflect the new path the same Saturday.

Bot 8 — Bible Donate / Receive (disabled by default)

One-time setup

🚀 Deploy the donate / receive flow

Four steps. The handler reuses the existing web app — no new deployment URL.

  1. Open script.google.com → STW Order Handler. Copy the latest docs/apps-script/order-handler.gs from the repo and paste over Code.gs. Save.
  2. Project SettingsScript PropertiesAdd script property: name BIBLE_REQUEST_REVIEW_SECRET, value the output of openssl rand -base64 32 (any 32+ random characters work). Save.
  3. Run installAllTimeTriggers from the function dropdown to register the new processBibleReviewReminders_ 30-minute trigger. Confirm the log line says "installed 14 triggers".
  4. In the admin editor: Telegram bot config → Bible donate / receive. Paste the deployed web app URL into endpointUrl. Flip enabled to true. Save & commit. The CTA buttons on donate.html go live on the next deploy.

The first submission will auto-create the Bibles tab on the ministry Sheet via the openTab helper — no manual tab creation needed.

Reviewing a request

📖 What the admin team sees when a request lands

Every receive submission emails seedthewordministry@gmail.com with the full story body in a blockquote and two big buttons: Approve and email handoff form (green) and Decline silently (white). Click Approve and the requester gets an email with a link to the handoff form. Click Decline and the row is marked declined; the requester is not notified.

If you need to add a private decline reason for the audit trail, append &reason=<short note> to the decline link before clicking. The reason is stored in the Bibles row's decline_reason column (admin-eyes-only).

If a request sits unreviewed for 48 hours, the cron re-emails the admin team once with the same buttons. Only one reminder per request.

Where the signs are

🧭 Active sign placements

Loading…

Edit the list in the admin editor → Bible donate signs. After editing, run python scripts/generate_sign_qr_codes.py to (re)generate the QR SVGs in assets/images/sign-qr/. Print the SVG into the physical sign Sam Petrov designs.

Bot 6 — Prayer Request Intake (disabled by default)

One-time setup

🚀 Deploy the prayer-intake action

Three steps; mostly Apps Script work. The installPrayerIntake bootstrap function does most of the heavy lifting — Sheet tab creation, headers, data-validation dropdown, the unsubscribe HMAC secret, and the every-30-min drip trigger — in a single run.

  1. Paste the updated order-handler.gs into Apps Script. Open script.google.com as seedthewordministry@gmail.com → open the existing STW Order Handler project → copy the full contents of docs/apps-script/order-handler.gs from the repo, paste over Code.gs, save.
  2. Run installPrayerIntake from the editor. In the function dropdown at the top of the editor (next to the ▶ Run button), pick installPrayerIntake → click ▶ Run. Authorize the Sheets + Triggers scopes when prompted. The execution log will show six green check lines and two deep-link URLs to the new tabs. This is the one step that creates the Prayers + PrayerDrip tabs with their column headers, generates and stores PRAYER_INTAKE_UNSUBSCRIBE_SECRET, and installs the every-30-min drip trigger. Idempotent — safe to re-run; will not rotate the secret if one already exists.
  3. Click Deploy → Manage deployments → ✏️ pencil → New version → Deploy. The web-app URL stays the same — no config edit needed. The form on community.html is already enabled and pointed at the existing web-app URL, so the round-trip works as soon as the new version is published.

Verify it works: open the live community.html, click 🙏 Submit a Prayer Request, fill in the form with your own name + a short body, submit. Within ~30 seconds you should see (a) a new row in the Prayers tab with telegram_status='sent', (b) a Telegram message in thread 21 with the 💌 New prayer request from … (via the website): … shape.

To enable the four-step encouragement drip emails: in the in-browser admin editor → Telegram bot config → Prayer → Intake → flip dripEnabled to true. Save → Commit & push. New submissions with an email will start receiving the Day 0 confirmation immediately and Days 3 / 7 / 14 follow-ups via the cron. Submissions made while dripEnabled = false do not retroactively get a drip — the audit row records drip_status='disabled-by-config' and that's final.

The marker contract

🔗 prayer.intake.markerprayer.digest.websiteSubmissionMarker

These two values form one contract. The intake produces messages containing the literal substring; the digest's regex matches it. Editing one without editing the other to the identical value breaks recognition. Always edit them together and commit in the same change. The default (via the website) works as-is; only change it if you have a reason to and you are touching both files.

Where the audit lives

📓 The Prayers Sheet tab

On the existing ministry Sheet (same one orders / contact / stories use). Append-only and admin-only — never surfaced on the website, never in Telegram, never in any drip email. The real submitter_name and submitter_email are recorded even when the submitter checked the anonymous box (per Requirement 5.5 — admins reviewing pastoral follow-up need this backstop). The anonymous column is TRUE in those rows so an admin can see the submitter chose the public-anonymity path.

No admin dashboard is built — pivot tables and filters live in the Sheet itself.

Recovery

🛟 An audit row shows telegram_status: failed — what now?

  1. Open the Prayers tab. Find the row.
  2. Copy the body and submitter_name values. (If anonymous=TRUE, attribute the manual repost as Anonymous rather than the real name.)
  3. From the prayer bot's account, post into the Prayer & Thanksgiving topic (thread 21) using one of the four marker shapes:
    • 💌 New prayer request from <name> (via the website): <body>
    • 💌 New prayer request from Anonymous (via the website): <body>
    • 💌 New thanksgiving announcement from <name> (via the website): <body>
    • 💌 New thanksgiving announcement from Anonymous (via the website): <body>
  4. Leave the audit row alone — keep it as the historical record of what happened. Do not edit telegram_status manually; the next failure would look like the same row.

If a PrayerDrip row is stuck in pending past its timestamp: check the Apps Script execution log for processPrayerDrip_. The row will retry on the next 30-min run. If it consistently fails, fix the underlying cause (template fetch error, mail quota, etc.) and the row will catch up on its own.

Privacy contract

🔒 What unsubscribe does and does not do

Every drip email carries a one-click unsubscribe link of the shape <web-app-url>?action=prayer-unsubscribe&token=<hmac>. The token is an HMAC of the Submission's UUID signed with PRAYER_INTAKE_UNSUBSCRIBE_SECRET, so a leaked link cannot be forged into a different Submission's unsubscribe.

  • It DOES: flip every PrayerDrip row for that submission_id to unsubscribed=TRUE. Pending rows additionally have status changed to unsubscribed so the cron skips them. Future drip cron runs observe the flag and skip the row.
  • It DOES NOT: remove or rescind the Telegram message that was already posted to the prayer topic at submission time. The Telegram post is irreversible from the intake side; an admin can manually delete the message via the prayer bot for pastoral reasons.
  • It DOES NOT: auto-unsubscribe future Submissions from the same email address. Each Submission has its own token. A submitter who returns later with a new prayer can choose their privacy posture again.

The unsubscribe handler is idempotent — clicking the same link twice is a no-op the second time, and forged tokens render the "invalid or expired" page without modifying any row.

The drip cron

processPrayerDrip_ runs every 30 minutes

The Day 0 confirmation email fires inline at submission time (not via the cron). Days 3, 7, 14 are written as pending rows in the PrayerDrip tab with their scheduled fire times (received_at + N days). The cron is a one-pass scan of the tab:

  1. If prayer.intake.dripEnabled is false, exit immediately.
  2. For each row where status='pending' AND unsubscribed!=TRUE AND timestamp<=now: take a per-row script lock (15-second wait), re-read the row inside the lock to confirm still pending, render the email, send via MailApp, mark sent (or failed with the error).
  3. Always release the lock in a finally.

The lock prevents two overlapping trigger invocations from double-sending a row. MailApp.sendEmail shares the existing daily-quota budget with order emails, contact replies, and the weekly digest — at the four-emails-per-Submission rate this is fine.

The 7-bot system at a glance

Each bot handles its own topic, on its own schedule

1 📣

Announce bot

Calendar events → Announcements topic (thread 553). Runs every 15 min Mon–Sat, inside quiet-hours 07:00–21:00 PT. LIVE events bypass quiet hours.

2 📖

Bible bot

Daily NT reading (Mon–Fri) + Prayer & Thanksgiving block + Saturday Study Saturday Live teaser → General channel. Runs 8 AM Mon–Sat.

3 🎵

Playlist digest

Weekly "what's new in the community playlist" → Worship & Music topic (thread 1064). Runs 8 AM Saturday. Skipped when nothing was added that week.

4 🙏🏻

Prayer nudge (disabled)

Dormant — the Bible bot now carries the daily Prayer & Thanksgiving block. Re-enable anytime from the editor.

5 🙏

Prayer digest

Mon / Thu / Sat 8 AM PT, posts a short summary of recent prayers & thanksgivings into the Prayer topic (thread 21). Disabled by default; flip on after the BotFather privacy step.

6 💌

Prayer Request Intake

Two cards on community.html → quiet modal → audit Sheet → relay to Prayer topic + four-step encouragement drip with one-click unsubscribe. Disabled by default; flip on after Bot 5 is live and the Prayers + PrayerDrip tabs exist.

7 🌿

Layered reading plan

Quiet "Going deeper today" row on community.html plus a matching footer on the Mon–Fri Bible Telegram post. Four optional companion streams (OT walk, Poetry & Prophecy, Psalm, Proverbs). Mon–Fri only — silent on Sat/Sun. Ships enabled.

Bot 7 — Layered Bible Reading Plan (ships enabled)

Daily configuration knobs

🎛️ bible.layeredPlan in telegram-bot.json

One config block, three consumers (browser renderer + Python Telegram bot + Apps Script port). Edits flow through to all three on the next render or the next scheduled post — no redeploy needed.

  • enabled (default true) — master switch. false hides the row on the website AND drops the Telegram footer entirely, regardless of includeInTelegram.
  • includeInTelegram (default true) — Telegram-only switch. false drops the footer from the Mon–Fri post; the website row keeps rendering normally.
  • timezone (default "America/Los_Angeles") — IANA tz used for weekday detection and the Psalm/Proverb day-of-year / day-of-month math. Pin this and forget it.
  • streams.otHistory.enabled (default true) — toggles just the Genesis–Esther pill. The other three streams stay rendered.
  • streams.otHistory.anchor (default {date: "2026-04-30", book: "Genesis", chapter: 1}) — the calendar date paired with the chapter that should show on that date. Re-anchoring shifts every future day's reading by the date delta.
  • streams.poetryProphecy.enabled + .anchor — same pattern, default anchor is Job 1 on 2026-04-30 so all three weekday walks advance together.
  • streams.psalm.enabled / streams.proverbs.enabled (default true) — Psalm of the day uses ((dayOfYear − 1) mod 150) + 1; Proverb of the day uses min(dayOfMonth, 31). Both compute every day; rendering on weekends is suppressed at the row/footer level.

Operator playbook

🔧 Quiet a stream, re-anchor a walk, replace the QR card

  1. Disable a single companion stream — Editor → Telegram bot config → biblelayeredPlanstreams → pick otHistory / poetryProphecy / psalm / proverbs → flip enabled to false → commit. The website drops that pill and the Telegram footer drops that line on the next render / next scheduled post.
  2. Disable the whole feature — flip bible.layeredPlan.enabled to false. Both surfaces go silent immediately.
  3. Disable just the Telegram footer — flip bible.layeredPlan.includeInTelegram to false. Website row keeps rendering normally.
  4. Re-anchor a weekday walk — edit streams.otHistory.anchor or streams.poetryProphecy.anchor. Re-anchoring shifts every future date's reading by the date delta — confirm with the team before changing.
  5. Where the QR card points — the guide-newcomer-qr bundle item points at start-here.html on the live site. The 30-entry plan is in assets/data/newcomer-30day.json; intermediate note text can be edited freely, the day-1 / day-30 labels (John 1 / 2 Timothy 3) and the four week themes are locked.
  6. Regenerate the QR PNG/SVG — encode the URL https://seedtheword.github.io/seedtheword/start-here.html with any QR generator (qrencode -o newcomer.png "https://…" from the command line, or any free online generator). Test-scan it from a phone before sending bundles to print.
  7. Adding audio recordings later (Phase 2) — open assets/data/bible-spotify-map.json. Four optional buckets (oldTestamentChapters, poetryProphecyChapters, psalmChapters, proverbsChapters) ship empty. Adding a key like "Genesis 5" or "Psalm 23" lights up the 🎧 affordance on the matching pill on the next page load — no code change needed. Silence is honest while no recording exists.

Out of scope (don't ask the row to do these)

🚫 What this feature deliberately doesn't do

  • No Russian-language pairing for the four companion streams. Russian deferred to a future community-ru.html.
  • No per-user reading-progress tracking. The plan is reader-relative — you read it at your pace.
  • No audio recordings for the four new streams in v1. Phase 2.
  • No multi-post-per-day Telegram channel. The footer is one short block at the end of the existing Mon–Fri post.
  • No admin-editor UI for the OT-walk weekday selections. The two walks are deterministic from their flat sequence and their anchor.

Adding a Spotify episode for a specific chapter

  1. Open assets/data/bible-spotify-map.json in the repo.
  2. Inside "chapters", add an entry keyed by the exact reading label from bible-plan.js. Examples: "Mark 16", "1 Corinthians 13", "John 3".
  3. Value is the full Spotify URL (episode preferred, show is fine too).
  4. Remove the __example_Mark 16 key that's there as a placeholder.
  5. Commit. The next Bible post that matches that chapter will use your link; chapters you haven't mapped still post with the default show URL.

Troubleshooting — the Telegram workflow failed, what now?

Common situation

⏰ A scheduled bot didn't fire on time (morning post missing)

Most common cause: GitHub Actions free-tier cron triggers are "best-effort" — GitHub delays or silently drops scheduled runs during peak hours when runner capacity is saturated. Low-activity repos get deprioritized first.

Our mitigation: an hourly heartbeat workflow commits a timestamp to .github/heartbeat/last-tick.txt so the repo looks active. This greatly reduces (but doesn't eliminate) skipped runs.

If a post is missing:

  1. Open Actions.
  2. Pick the workflow that should have run (e.g. Daily Bible Reading).
  3. Click Run workflowdry_run: false → Run. The post fires immediately.
  4. If the cron has been dropping runs for more than a day, open Actions → Heartbeat workflow and verify recent runs are green.

Manual Run workflow always executes right away — it bypasses the cron scheduler entirely.

Common error

403 Forbidden: bot is not a member of the supergroup chat

The bot account itself hasn't been added to (or was removed from) the target group yet. The bot token is fine; the bot just doesn't have a seat at the table.

Fix:

  1. Find the bot's @username. Open Telegram → BotFather → /mybots → pick the bot → its public handle is listed there.
  2. Open the @seedtheword supergroup in Telegram.
  3. Tap the group title → Members (or Subscribers) → Add Member.
  4. Search for the bot's @username and add it.
  5. Tap the bot in the member list → Promote to Admin. Toggle Post Messages ON, and (for forum-style groups with topics) Manage Topics ON.
  6. Re-run the workflow. The 403 will clear.

The announcements bot, Bible bot, and Prayer bot are three separate bot accounts with three separate tokens — each one needs to be added to the group on its own. Adding one doesn't cover the others.

Common error

400 Bad Request: message thread not found

The messageThreadId in telegram-bot.json points to a topic that no longer exists. Find the real id by opening the target topic in Telegram — the URL looks like https://t.me/<chat>/<threadId>/<messageId>. Copy the middle number. Update it from the ✏️ Editor under Telegram bot config, commit.

Common error

400 Bad Request: can't parse entities

A MarkdownV2 character got through unescaped — usually inside a link label or italic run. Re-run the workflow with dry_run=true to see the raw payload, then look at the line Telegram's error message points to. If the content came from a calendar event description, the fix is to remove the offending character from the event description.

Turn a bot off, change its topic, or change a schedule

Adding a new bot to the system

  1. Create it with @BotFather/newbot. Save the token.
  2. Add the bot to @seedtheword and promote it to admin with "Post Messages" permission.
  3. Add the token to repo Settings → Secrets and variables → Actions with a new name like TELEGRAM_XYZ_BOT_TOKEN.
  4. Ping the dev on duty to add a new section to telegram-bot.json and a new workflow under .github/workflows/.
If a bot goes quiet unexpectedly: check its Actions run for red errors. 90% of the time it's (1) an expired token (401 Unauthorized) or (2) the bot lost admin permission. Fix the permission or regenerate the token via BotFather /revoke, then re-run manually.

🔐 Where secrets live (NOT here)

We intentionally don't store API keys, passwords, or tokens on this page. Secrets live in:

Never paste a real password or API key here or anywhere on the public website. Even on this admin page — anyone who knows the team password can read everything here.

📝 Recent changes

Big-picture history of what's on the site and when it was set up:

🆘 If everything is on fire

Done here?