~/Downloads. The Resend API key and Supabase service key are referenced by location (in coast.env), not printed in full.Total Party Planner (TPP) is Coast's booking source of truth, but it has no API and no built-in alerting. This system quietly watches TPP, mirrors each event's staffing needs into the Coast staffing app, and emails the office the moment anything important changes — plus reminds staff two days before a shift.
It replaces the manual habit of logging into TPP repeatedly to check "did anything move?" Three things run automatically on a schedule:
TPP's "Classic Staff Hiring Manager" page renders the full staffing table server-side, per date:
event_StaffHiringManager.aspx?PartyDte=<date>. Each position needed = one row (a Title). A plain fetch can't get through TPP's login + Cloudflare handshake, so the script drives a real headless browser (Playwright / Chromium): it logs in, walks each date in the window, and parses the DOM for every event's positions, venue, guest count, status and times.
Chef titles are deliberately kept distinct so the app can show the on-site lead separately. Rule enforced: every on-site event has exactly one Executive Chef; the rest of the kitchen are Event Chefs; drop-off events use a Drop-off Chef.
| TPP title | Coast role |
|---|---|
Event Server / Server | Server |
Executive Chef | Executive Chef |
Event Chef / bare Chef | Event Chef (on-site) |
Drop Off Chef | Drop-off Chef |
Bartender | Bartender |
Captain / On-Site Event Manager | Captain |
Tasting, Prep, Driving, Delivery | ignored (not event-day serving staff) |
needs (headcount by role) on each matching eventEach run diffs the live TPP 4-week window against the last stored snapshot (tpp_event_state). It is idempotent — state lives in the DB, so it never double-notifies. The very first run on an empty snapshot "primes" silently (no email). It detects seven change types:
New event Cancelled Guest count Date moved Time change Venue change Staffing needs
When there are changes, it sends one branded digest email to the office, logs each change to tpp_change_log, records the run in tpp_run_log (including quiet runs), then refreshes the snapshot.
Requested by Adrianna (email 2026-08-21). Two days before an event, each staffer who has accepted an assignment (not merely been invited) gets a branded reminder with the details, crew-call time and a maps pin. A reminder_log makes it idempotent so re-runs never double-send.
| Layer | Service | Role in the system |
|---|---|---|
| Source of truth | Total Party Planner (TPP) | Where the office books events & defines staffing needs. No API — scraped via browser. |
| Scraper | Playwright + Chromium | Real headless browser that logs into TPP and reads the staffing pages. |
| Automation host | Mac Mini (Tailscale) | Runs the Python scripts on a cron schedule. Dir: ~/coast-tpp-sync/. |
| Database | Supabase (hosted Postgres) | Holds events, profiles, assignments + the sync's snapshot/log tables. RLS on. |
| Email delivery | Resend | Sends every office alert + staffer reminder. See §5. |
| Hosting | Vercel (×3 projects) | Staff/manager app, the demo brief, and the ops watcher dashboard. |
| Language / runtime | Python 3 (venv) | Two scripts: tpp_sync.py, event_reminders.py. Stdlib + Playwright only. |
~/coast-tpp-sync/coast.env on the Mac Mini and shown here masked only.| System | Login | Secret |
|---|---|---|
| TPP | coastcatering_rizza | Coast445! |
| Mac Mini (SSH) | ssh aiforroi@100.88.190.93 | Tailscale key / local login (sonnys-mac-mini.local) |
| Supabase project | ref najmdkbbxtslbhpuqbbpURL https://najmdkbbxtslbhpuqbbp.supabase.co | anon + service keys in coast.env & app .env |
| Supabase (direct SQL) | pooler postgres.najmdkbbxtslbhpuqbbp @ aws-0-us-west-2.pooler.supabase.com:5432 | Cst-…-Zx9 (in notes) |
| Resend | account that owns the emberprousa.com sending domain | RESEND_API_KEY = re_g4…(in coast.env) |
| Vercel | Sonny's Vercel account | — |
App logins (default password Coast2026) | ||
| Manager (admin) | manager@coast.com | Coast2026 |
| Demo staffer | astaffer@coast.com ("Alex Staffer") | Coast2026 |
| Change tracker / watcher | coast-tpp-monitor dashboard | CoastWatch2026 |
| Any staff account | self-registered email | default Coast2026 |
Every automated email (office alerts + staffer reminders) goes out through Resend's API. Key facts about how it's wired today:
| Setting | Value |
|---|---|
| API endpoint | POST https://api.resend.com/emails |
| Auth | Authorization: Bearer <RESEND_API_KEY> (re_g4…, in coast.env) |
| From address | Coast Catering <coaststaffing@emberprousa.com> |
| Sending domain | emberprousa.com — already verified in an existing Resend account |
| Change-alert recipients | barry@, chelseasilvia@, rizza@ @coastcatering.com |
| Reminder recipients | each individual staffer's email (per accepted assignment) |
| Test address | --test-to sonny@aiforroi.co (verification runs never hit real inboxes) |
emberprousa.com Resend domain and API key. It is not on a Coast-owned Resend account. Recommended fix in §12.All three run on the Mac Mini. Each wrapper sources coast.env then runs the matching Python script in the venv.
| When | Wrapper | Command | Purpose |
|---|---|---|---|
| 6:30 am daily | run.sh | tpp_sync.py --days 120 | Full sync — all events & needs into the app |
| Hourly 9am–5pm | run-notify.sh | tpp_sync.py --days 28 --notify | Change-watch → emails office |
| 8:00 am daily | run-reminders.sh | event_reminders.py --days 2 | 2-day-out shift reminders |
# Live crontab (currently PAUSED — lines commented out):
30 6 * * * ~/coast-tpp-sync/run.sh >> ~/coast-tpp-sync/logs/sync.log 2>&1
0 9-17 * * * ~/coast-tpp-sync/run-notify.sh >> ~/coast-tpp-sync/logs/notify.log 2>&1
0 8 * * * ~/coast-tpp-sync/run-reminders.sh >> ~/coast-tpp-sync/logs/reminders.log 2>&1
--days N — how many days ahead to read.--notify — run change detection + email on changes.--no-email — detect & refresh the snapshot but send nothing (used to prime a baseline without an email storm).--dry — read only; write nothing, email nothing.--test-to EMAIL / --test-send — preview the digest to a test address using real events.| Table | Owned by | What it holds |
|---|---|---|
events | app + sync | Event rows; sync updates the needs JSONB, venue, guests, times. |
profiles | app | Staff accounts (name, role, active flag, admin flag). |
assignments | app | Invitations + accepted signups (drives reminders). |
tpp_event_state | watcher | Snapshot of each event's last-seen state (the diff baseline). |
tpp_change_log | watcher | One row per detected change (feeds the tracker dashboard). |
tpp_run_log | watcher | One row per run — including quiet ones — for a rolling history. |
reminder_log | reminders | Which assignments were already reminded (idempotency). |
| App | URL | Project dir / Vercel | Who uses it |
|---|---|---|---|
| Staff + Manager app | schedule.coastcatering.com | coast-staffing-v3 · Vercel project dist | Office (manage) + staff (availability/signups) |
| Sales / demo brief | brief.coastcatering.com | coast-staffing-brief | Demo / walkthrough |
| Ops watcher dashboard | monitor.coastcatering.com | coast-tpp-monitor | "Coast TPP Watcher — Status": run history & changes (login CoastWatch2026) |
npx vercel deploy --prod --yes you MUST re-alias:
npx vercel alias set <deployment-host> schedule.coastcatering.com — prod deploys auto-assign a random domain. The brief & monitor projects auto-alias correctly on their own.astaffer@coast.com demo account created.logistics@coastcatering.com removed from alert recipients (Aug 22, 2026).RECIPIENTS (lines ~47–51) in both copies: repo coast-staffing-v3/scripts/tpp_sync.py and live ~/coast-tpp-sync/tpp_sync.py../.venv/bin/python3 tpp_sync.py --days 28 --notify --test-to you@email.com.cd ~/coast-tpp-sync && set -a && source ./coast.env && set +a ./.venv/bin/python3 tpp_sync.py --test-send --test-to sonny@aiforroi.co
cd ~/coast-tpp-sync && ./run.sh # --days 120, no emails
./.venv/bin/python3 tpp_sync.py --days 120 --notify --no-email
tail -f ~/coast-tpp-sync/logs/notify.log # change-watch tail -f ~/coast-tpp-sync/logs/sync.log # daily sync tail -f ~/coast-tpp-sync/logs/reminders.log # reminders
The three coast lines in crontab -l are prefixed with # PAUSED. A backup of the original crontab is at ~/coast-tpp-sync/crontab.backup-20260822-124833.txt.
ssh aiforroi@100.88.190.93 \ "crontab -l | sed -E 's/^# PAUSED (.*coast-tpp-sync.*)/\1/' | crontab - && crontab -l | grep coast"
Or restore the exact backup: crontab ~/coast-tpp-sync/crontab.backup-20260822-124833.txt.
--notify --no-email pass first so any changes that happened while paused don't all fire as one big digest.~/coast-tpp-sync/coast.env as RESEND_API_KEY.FROM_EMAIL to a Coast address, e.g. Coast Catering <staffing@coastcatering.com>, in coast.env (or the two script defaults).--test-to preview and confirm it lands (check spam / DMARC alignment) before flipping the office back on.Why it matters: today the system sends as coaststaffing@emberprousa.com on a borrowed (Ember Pro) Resend domain. That works, but it isn't Coast-branded in the From line and it ties Coast's deliverability to an unrelated account. Moving to a verified coastcatering.com domain makes alerts come from Coast itself and keeps the two businesses independent.
coast.env.| Symptom | Likely cause / fix |
|---|---|
| No emails at all | Cron paused (§11), or RESEND_API_KEY missing/blank in coast.env, or Mac Mini asleep / Kingston unmounted. |
| Email 403 / Cloudflare 1010 | Browser User-Agent header was removed from the Resend request — restore it. |
| Sudden flood of alerts | Snapshot was wiped or role logic changed. Re-prime with --notify --no-email. |
| Sync reads 0 events | TPP login failed (Playwright) or TPP changed the page. Check sync.log; re-run --dry. |
| Staff app shows old data after deploy | Forgot the alias step — re-run vercel alias set (§8). |
| A change didn't email | It was outside the 4-week window, was the baseline prime, or ran with --no-email. |