Site Documentation & Handoff Guide
Everything a new administrator needs to know to run bhs1977reunion.com
1. What This Site Is
bhs1977reunion.com is the official website for the Bettendorf High School Class of 1977 50th Reunion, scheduled for October 15–17, 2027 at the Hotel Blackhawk in Davenport, Iowa.
The site serves two audiences:
- Classmates — can check in, browse the directory, post memories, play the yearbook photo game, register as veterans, and view the In Memoriam page.
- Committee members — have access to these admin pages for managing classmate records, sending bulk emails, moderating content, tracking analytics, and coordinating reunion planning.
Tech Stack in Plain English
| Term | Plain English |
|---|---|
| Astro | The framework that builds the website's HTML pages. You don't need to touch this unless you're adding new pages. |
| Netlify | The company that hosts and serves the website. It also runs the server-side logic ("functions") that power things like check-in and email. |
| Supabase | The database where all classmate records, photos URLs, memory posts, and analytics live. Think of it as the filing cabinet. |
| Cloudflare R2 | Cloud storage for photos (yearbook portraits, memory wall images). Like a shared hard drive in the cloud. |
| Resend | The email delivery service used for bulk broadcasts to classmates. |
| GitHub | Where the website's source code lives. Every change is tracked here. Pushing to GitHub triggers a new deploy. |
2. Services & Accounts
The site depends on six external services. Each has its own login. See the Passwords Annex for credentials.
Netlify
app.netlify.com → Sites → bhs1977reunionWhat it does: Hosts and deploys the website. Every time code is pushed to GitHub, Netlify automatically rebuilds and re-deploys the site within 2–3 minutes.
What you'll do there:
- Check deploy status (did the latest build succeed?)
- View/edit environment variables (the secret keys that power every feature)
- Manage Netlify Identity users (classmate accounts)
- View function logs when something breaks
How to check if the site is working
- Go to app.netlify.com and sign in.
- Click the site "bhs1977reunion".
- The "Production deploys" list should show a green "Published" badge with a recent timestamp.
- If it shows "Failed", click on the failed deploy to read the build log and find the error.
How to add or change an environment variable
- In Netlify, go to Site configuration → Environment variables.
- Click "Add a variable" or find the existing one and edit it.
- You must trigger a new deploy for the change to take effect: go to Deploys → "Trigger deploy".
GitHub
github.com/ZoomMimi/bhs77-reunionWhat it does: Stores the website source code and tracks every change ever made. Pushing new code here is what triggers a Netlify deploy.
What you'll do there:
- Browse the code if you need to understand how something works
- Pull changes if you're doing local development
- Push updates to deploy a change to the live site
The deploy workflow
- Make a code change locally on your computer.
- Run
git add <file>, thengit commit -m "describe what changed". - Run
git push origin main. - Netlify detects the push and starts building within 30 seconds. Watch status at app.netlify.com.
Supabase
supabase.com → Project: bhs1977reunionWhat it does: This is the database — the single source of truth for every classmate record, photo URL, memory post, game score, and analytics event.
What you'll do there:
- Browse/edit classmate records directly if the admin UI can't handle an edge case
- Run SQL queries to answer one-off questions ("how many people checked in this week?")
- Download a database backup (do this monthly)
- Monitor database size and API usage
How to back up the database
- Go to supabase.com and open the project.
- Click "Database" in the left sidebar, then "Backups".
- Supabase creates automatic daily backups on paid plans. You can also download a manual export.
- For a full export: go to the SQL Editor and run
pg_dump, or use the Supabase CLI.
Key tables to know
| Table | Contains |
|---|---|
classmates | Every classmate — name, email, phone, photo URL, check-in status, attendance interest |
memory_posts | Memory Wall posts |
veteran_profiles | Veterans honor roll submissions |
email_log | History of every broadcast email sent |
game_best_scores | Yearbook photo game leaderboard |
app_settings | Global config including the Gmail OAuth refresh token |
Cloudflare R2
dash.cloudflare.com → R2 Object StorageWhat it does: Stores all photos — yearbook portraits, colorized portraits, and memory wall images. Photos are served directly from Cloudflare's global CDN, which means they load fast worldwide.
Two buckets:
- bhs1977-yearbook-photos — all yearbook portraits (black & white originals, colorized versions, thumbnail variants)
- bhs1977-memory-photos — photos attached to Memory Wall posts
What you'll do there (rarely):
- Check how much storage is being used (free tier: 10 GB)
- Manually delete orphaned files if needed
- Regenerate API keys if they're compromised (then update Netlify env vars)
Resend
resend.com → API Keys + BroadcastsWhat it does: Sends bulk email to classmates. When you use the "Broadcast" admin page to send a newsletter or announcement, Resend is the engine that delivers it.
What you'll do there:
- Monitor delivery rates and bounce counts after a broadcast
- Check if emails are landing in spam (look at the engagement rate)
- Regenerate the API key if needed (then update Netlify env vars)
The sending address is committee@bhs1977reunion.com, which is verified with Resend via DNS records. Don't change these DNS records or email will break.
Google / Gmail
50thbhs1977reunion@gmail.comWhat it does: The committee Gmail account is connected to the site via OAuth so the admin Gmail page can read the inbox, reply to classmates, and send personalized outreach emails. It's also where classmates email the committee directly.
Important: The Gmail connection uses an OAuth "refresh token" stored in the database. If the Google account password changes or someone revokes the app's access, the Gmail admin page will stop working. To reconnect:
- Go to /admin-gmail and click "Connect Gmail".
- Sign in with the committee Google account.
- Approve the permissions. The new refresh token is saved automatically.
Domain Name (bhs1977reunion.com)
See Passwords Annex for registrarWhat it does: The domain name is the website's address. It's registered with a domain registrar and pointed at Netlify via DNS records.
DNS records to never delete:
- A / CNAME records pointing to Netlify — these serve the website
- TXT records for email (SPF, DKIM, DMARC) — these prevent emails from going to spam
3. How It All Fits Together
The request lifecycle (what happens when someone visits the site)
- Browser → Netlify CDN: The visitor's browser requests a page. Netlify serves the pre-built HTML from its CDN — this is fast because no server needs to "think".
- Client-side check-in gate: The page checks localStorage for a
bhs-checkin-donekey. If missing, the visitor is redirected to the check-in page. - Check-in flow: Classmate enters their email → site looks them up in Supabase → sends a 6-digit code via Resend → classmate enters code → they're in.
- Dynamic data: Once inside, the page calls Netlify Functions (server-side code) to fetch live data from Supabase — classmate directory, memory posts, leaderboard, etc.
- Photos: Images are loaded directly from Cloudflare R2's CDN. The database stores the URL; the browser fetches the actual file from R2.
Authentication: two tiers
| Tier | Who | How it works | What they can do |
|---|---|---|---|
| Public | Anyone | No login required | Homepage, In Memoriam, Contact form — always accessible with no barrier |
| Classmate | Any class member | Check-in form + 6-digit email code → bhs-checkin-done in browser storage | Browse directory, post memories, play game, view veterans |
| Committee | Committee members only | Full Netlify Identity account (email + password) with "committee" role | All classmate access + all admin pages |
Committee accounts are created by an existing committee member going to Netlify → Identity → Invite user. The COMMITTEE_EMAILS environment variable controls which email addresses get the "committee" role automatically on signup.
How code changes reach the live site
You can watch this happen in real time at app.netlify.com → Deploys.
4. Admin Pages Guide
All admin pages require a committee-role Netlify Identity login.
The main dashboard. Search and filter all classmates, edit records, review pending check-in requests, find duplicates, and manage committee assignments. This is where you'll spend most of your time.
Dashboard of key metrics: roster health, email status breakdown, attendance interest, engagement over time, geographic distribution. Use this to guide outreach priorities.
Compose and send bulk emails to classmates. You can filter by audience (everyone, attending only, checked-in only, etc.), preview the email, send a test to yourself, and then send for real. Merge fields like {{FirstName}} personalize each message automatically.
Access the committee Gmail inbox (50thbhs1977reunion@gmail.com) directly in the browser without leaving the admin area. Read threads, reply, and send outreach emails to classmates. The "Outreach" tab lets you find classmates with no email confirmed and send personalized probes via Gmail.
Manage yearbook portraits. Upload a photo and match it to a classmate. Also contains the Scan Processor for extracting crops from full yearbook page scans. Most portrait work was completed in 2025–2026 — you may only need this occasionally.
Moderate Memory Wall posts. Pin important ones to the top, delete inappropriate content, and view reports from classmates who flagged a post.
Review and manage the Veterans Honor Roll. Toggle a profile's public visibility, edit service details, and approve submitted profiles.
Download a CSV of classmate data for use in spreadsheets, mail merges, or sharing with a caterer/venue. Filter by audience and choose which columns to include.
Manage committee member accounts and assign classmates to specific committee members for personal outreach. The status board shows each assignment's progress.
Countdown to the event, survey result snapshot, and a task checklist for reunion logistics.
5. Common Tasks
How do I add a new committee member?
- Go to app.netlify.com → your site → Identity → Invite users.
- Enter their email address and click Invite. They'll receive a sign-up link.
- Add their email to the
COMMITTEE_EMAILSenvironment variable (comma-separated list). Without this, they'll sign up as a regular member, not a committee member. - After adding to
COMMITTEE_EMAILS, trigger a new Netlify deploy (Deploys → "Trigger deploy"). - They can now log in and access all admin pages.
How do I send an email blast to classmates?
- Go to Broadcast.
- Choose your audience from the filter dropdown (e.g., "All with email confirmed").
- Type your subject and compose your message. Use
{{FirstName}}to personalize. - Click Count — verify the number of recipients looks right.
- Click Test — a copy goes to your own email. Review it carefully.
- Click Send. Done. Check Resend.com afterward for delivery stats.
A classmate can't log in / isn't in the system — how do I add them?
- Go to Classmates and search for their name.
- If they're there but with the wrong email, click their record → edit the email field.
- If they're not there at all, click Add Classmate and fill in their details.
- Once their email is in the system, they can use the check-in form at bhs1977reunion.com/checkin to verify their account.
How do I handle a deceased classmate?
- Go to Classmates and find their record.
- Open their record and set Is Deceased to true.
- They will automatically move to the In Memoriam page and be hidden from the directory.
- Optionally upload an obituary image via the memorial admin tools.
A classmate has opted out of all contact but may still want to check the website — what do I do?
Use the Do Not Contact (public access OK) flag. This is different from a full block — it stops all outreach while leaving the website quietly available to them.
- Find the classmate in Classmates.
- Open their record and check Do Not Contact (public access OK).
- Save. They will now be excluded from all broadcasts and committee contact lists.
- The website's public pages (homepage, In Memoriam, contact form) remain accessible to them with no login required — they can quietly check back any time.
A classmate's email is bouncing — what do I do?
- Go to Gmail → Bounces tab. The system periodically scans for bounce replies and flags those addresses.
- Find the classmate in Classmates.
- Update their email address to a correct one if you have it, or clear the email field and note it in their record.
- The "Mark Corrected" button in the Bounces tab clears the bounce flag once the email is fixed.
The Gmail connection stopped working — how do I reconnect?
- Go to Gmail. You'll see a "Connect Gmail" button if the token is expired.
- Click it and sign in with 50thbhs1977reunion@gmail.com.
- Approve all the requested permissions.
- The new refresh token saves automatically. The Gmail tab should now show the inbox.
This can happen if: the Google account password changed, someone revoked the app's access in Google Account settings, or the token simply expired after a very long idle period.
The site is down — how do I diagnose it?
- Check Netlify first: app.netlify.com → your site → Deploys. Is the latest deploy green ("Published") or red ("Failed")?
- If Failed: click the deploy → read the build log → find the error. Usually it's a missing package or a syntax error in recently changed code.
- If Published but site is broken: open browser DevTools (F12) → Console tab → look for red errors. Then check the Network tab for failed API calls.
- If it's a specific feature broken (not the whole site): check Netlify → Functions → look for errors in the relevant function's log.
- If in doubt: roll back. In Netlify Deploys, find the last known-good deploy and click "Publish deploy" to instantly revert.
How do I make a code change without knowing how to code?
For simple text/content changes, you can use the GitHub web editor:
- Go to github.com/ZoomMimi/bhs77-reunion.
- Navigate to the file you want to edit (e.g.,
src/pages/event.astrofor event details). - Click the pencil icon to edit.
- Make your change, scroll down, and click "Commit changes".
- Netlify will automatically deploy within a few minutes.
For anything beyond simple text changes, work with a developer. The codebase is well-organized and a developer familiar with JavaScript/Astro can get up to speed quickly.
6. Environment Variables Reference
These are the secret keys configured in Netlify → Site configuration → Environment variables. Without them, the site's server-side features (check-in, email, photos, database) won't work. Never put these in code or commit them to GitHub.
| Variable | What it's for | Where to get a new one |
|---|---|---|
| Database (Supabase) | ||
SUPABASE_URL | The web address of the Supabase database. Starts with https:// and ends with .supabase.co | Supabase dashboard → Project Settings → API |
SUPABASE_SERVICE_ROLE_KEY | The master admin key for the database. Never expose to the browser — server-side only. | Supabase dashboard → Project Settings → API → service_role key |
| Photo Storage (Cloudflare R2) | ||
R2_ACCOUNT_ID | Your Cloudflare account identifier | Cloudflare dashboard → R2 → "Account ID" in the right sidebar |
R2_ACCESS_KEY_ID | API username for R2 | Cloudflare → R2 → Manage R2 API tokens → Create token |
R2_SECRET_ACCESS_KEY | API password for R2 — only shown once at creation | Same as above — copy it immediately, can't be retrieved later |
R2_YEARBOOK_BUCKET | Name of the yearbook photo bucket. Value: bhs1977-yearbook-photos | Static — only changes if the bucket is renamed |
R2_MEMORY_BUCKET | Name of the memory wall photo bucket. Value: bhs1977-memory-photos | Static — only changes if the bucket is renamed |
R2_YEARBOOK_URL | Public CDN URL for yearbook photos. Value: https://pub-6bd15a0a68834b6099281409988528ea.r2.dev | Cloudflare → R2 → click the bucket → "Public access" tab |
R2_MEMORY_URL | Public CDN URL for memory wall photos. Value: https://pub-a80d21e2dfa34e36a91a041b8c6fd32a.r2.dev | Same as above for the memory bucket |
| Email Delivery (Resend) | ||
RESEND_API_KEY | API key for sending bulk email. Looks like re_xxxxxxxxxx | resend.com → API Keys → Create API key |
| Gmail Integration (Google OAuth2) | ||
GOOGLE_CLIENT_ID | The app's Google identity — ends with .apps.googleusercontent.com | console.cloud.google.com → Credentials → OAuth 2.0 Client IDs |
GOOGLE_CLIENT_SECRET | The app's Google secret key — short base64 string | Same place as Client ID |
| AI / Claude (Anthropic) | ||
ANTHROPIC_API_KEY | Used for yearbook photo extraction via Claude's vision AI. Looks like sk-ant-v4-... | console.anthropic.com → API Keys |
| Committee Configuration | ||
COMMITTEE_EMAILS | Comma-separated list of email addresses that get committee admin access on sign-up. E.g., alice@example.com,bob@example.com | You maintain this list manually in Netlify env vars |
COMMITTEE_EMAIL | The primary committee contact address. Defaults to committee@bhs1977reunion.com | Set to whatever the active committee email address is |
7. Bus-Factor Recommendations
These are specific changes recommended to make handing off this site as painless as possible if the primary maintainer is suddenly unavailable.
1. Fill in the Passwords Annex on this page and print it
The Passwords Annex below is a template. Fill in every credential, print a copy, and store it in a sealed envelope with a trusted person (attorney, spouse, or another committee member). A digital copy should also be in a password manager that at least one other person has access to (e.g., 1Password, Bitwarden — both have family/team plans).
2. Add a second committee member with full access to all services
Right now, if the primary admin loses access to their email or laptop, everything stalls. Designate a backup person and give them:
- Committee-role Netlify Identity account
- Access to the Netlify team (app.netlify.com → Team settings)
- Access to the Supabase project (Supabase → Project Settings → Team)
- Access to the Cloudflare account (Cloudflare → Manage account → Members)
- Access to the GitHub repo (GitHub → Settings → Collaborators)
3. Transfer domain registration to a shared account
The domain bhs1977reunion.com is the most fragile dependency. If the registrar account's email goes dark, domain renewal fails, and the site disappears. Actions:
- Move the domain to an account with a committee group email (e.g., 50thbhs1977reunion@gmail.com) rather than one person's personal email.
- Enable auto-renewal with a credit card that won't expire before October 2027.
- Note the renewal date in the Passwords Annex.
4. Move Netlify billing to a shared payment method
If the Netlify account is under one person's personal credit card, a lapsed payment could take the site offline. Transfer billing to the committee's credit card or a card held by multiple people.
5. Schedule a monthly database backup
Supabase keeps automatic backups on paid plans, but you should also export a manual backup monthly and store it somewhere independent (Google Drive or Dropbox). This protects against the worst-case scenario of the Supabase project being accidentally deleted.
To export: Supabase dashboard → Database → Backups → Download, or use the SQL editor to export the classmates table as CSV.
6. Use Gmail delegation instead of sharing the password
Sharing the Gmail password works but creates two risks: Google can lock the account if it detects logins from multiple locations, and there's no audit trail of who sent what. The better approach is Gmail delegation — it's free and built into Gmail.
How to set it up:
- Sign in to 50thbhs1977reunion@gmail.com.
- Go to Settings (gear icon) → See all settings → Accounts tab.
- Under "Grant access to your account," click Add another account.
- Enter each committee member's personal Gmail address. They'll get a confirmation email.
- Once they accept, they can switch to the committee inbox from their own Gmail account (top-right account switcher) without ever knowing the password.
If a committee member leaves, just remove their delegation. No password change required. The account owner should be whoever is most permanent — ideally tied to the committee's own Gmail, not a personal address.
7. Create a HANDOFF.md file in GitHub
Add a brief text file to the repository root that lists: the names and emails of people with access to each service, the domain renewal date, and a link to the password document location. A future developer can find this in 30 seconds without knowing the site's history.
8. Consider migrating to a paid Supabase plan
The free Supabase plan pauses projects after 1 week of inactivity. If the site goes quiet for a week (say, right after the reunion), the database could pause and the site would appear broken. A paid plan ($25/month) removes this risk. Alternatively, set up a weekly cron job that makes a simple API call to keep the project active.
8. Passwords & Credentials Annex
Print this section, fill in the blanks, and store it securely. Update whenever credentials change.