← back to Prestige Car Wash
reports/SCOPE-social-autopost-20260720.md
98 lines
# Scope — Prestige Marketing: real auto-posting scheduler
**Date:** 2026-07-20 · **Author:** Claude (Steve session) · **Status:** scoping only, nothing built
**Context:** the `/admin/#marketing` tab now has a Hootsuite-style composer + a **client-side** gated
queue (localStorage; posting is manual "copy & open"). This scopes turning that into a queue that
**actually publishes** at the scheduled time.
---
## TL;DR — the real long pole is account access, not code
The scheduler engine is a moderate, well-understood build (~1 dev session for the inert skeleton).
But **0 of 6 platforms can auto-post today.** Every one needs an account created/claimed **and** a
developer app **and** owner (John/Ohav) credentials **and**, for most, platform app-review/audit.
That path is owner-dependent and measured in **weeks of calendar**, not dev hours. Recommendation:
build the engine now, **dark and inert** (mirroring the existing Stripe "built but off until keyed"
pattern), and light up platforms one at a time as accounts come online — starting with Meta (IG+FB).
---
## Per-platform feasibility (from the live social registry)
| Platform | Exists now | Auto-post API | Blocking prerequisites |
|---|---|---|---|
| **Instagram** `@prestigecwash` | yes (owner-run) | IG Content Publishing (Graph) | must be a **Business/Creator** acct · **linked FB Page** · Meta app · owner grants access · long-lived token · **App Review** `instagram_content_publish` · media served from a public URL (✅ we already do) |
| **Facebook Page** | **no** (`create`) | Pages `/feed` `/photos` | **create the Page** · Meta app · `pages_manage_posts` · page token · App Review |
| **TikTok** | **no** (`create`) | Content Posting API | create acct · TikTok dev app · **content audit** (un-audited apps = self/private only) |
| **YouTube** | **no** (`create`) | Data API `videos.insert` | create channel · Google Cloud OAuth consent · refresh token · upload quota (~1600 units/upload) |
| **Google Business Profile** | claim (unverified) | Business Profile "local posts" | **verify/claim** listing · GBP API is **allow-listed** (request access) · OAuth |
| **Yelp** | claim | **none exists** | Yelp has **no** public posting API — manual forever |
**Practical near-term set (if owner cooperates):** IG + FB Page through **one Meta app**, then YouTube
via Google OAuth. TikTok needs an audit; GBP needs allow-listing; Yelp is manual permanently.
---
## Backend reality (server.js, 473 lines, owned by a parallel session)
- **Persistence:** flat JSON files in `data/*.json` via `readJSON`/`writeFileSync`; append-only
`reports/*.jsonl` for leads & tips. **No DB, no cron, no in-process scheduler.**
- **Social data** is read-only today: `GET /api/admin/social` → `credentials.json`. No write route.
- **The gold pattern to copy:** the **Stripe tip flow** — fully built but **inert until
`STRIPE_SECRET_KEY` is set** (`server.js:383`). We mirror this exactly per social platform.
- **HARD GATE:** changing `server.js` = the deploy class that broke prod on 2026-07-05. Any backend
work here **must be coordinated with the owning session** (a static-file frontend change is the
only class I deploy solo).
---
## Proposed architecture
1. **Server-side queue** (replaces localStorage): `data/schedule.json` + routes
`POST/GET/DELETE /api/admin/schedule`. Admin UI repoints from localStorage → these routes
(small **frontend** change I can deploy the safe way).
2. **Scheduler:** a standalone `scripts/publish-due.js` run every ~5 min by **launchd/cron on
Kamatera** (survives restarts; matches the existing canary/kravet cron pattern) — reads
`schedule.json`, finds `due && unsent`, calls the platform adapter, marks `sent|failed`, appends
to `reports/social-posts.jsonl`.
3. **Adapters, one per platform, each "inert until keyed":** no token → the item stays queued as
`needs-connection` and the UI keeps today's **manual copy & open** behavior. Flip a platform on by
dropping its token via the `secrets` skill — nothing else changes. (Same philosophy as Stripe.)
4. **Token longevity:** reuse `meta-token-canary` (already solves Meta's 60-day-expiry trap) +
`secrets` for storage; a Prestige **social-post canary** verifies due posts actually published.
---
## Phases & effort
| Phase | What | Effort | Gate |
|---|---|---|---|
| **0 — Accounts** (long pole) | Owner confirms IG is Business, **creates + links FB Page**, grants access; decide "own Meta app vs. managed" | ~0 dev · **days–weeks calendar** | **Owner (John/Ohav) + Steve** |
| **1 — Engine (dark)** | Server-side queue + routes + `schedule.json` + cron skeleton + ledger + repoint UI. All inert. | ~1 session (3–5h) | **Coordinate server.js deploy** w/ owning session |
| **2a — Meta adapter (IG+FB)** | `/photos` + Reels publish; App Review | ~1 session dev + **1–2 wk Meta review** | Per-platform **go-live memo** |
| **2b — YouTube adapter** | `videos.insert` Shorts + OAuth | ~½ session + OAuth setup | go-live memo |
| **2c — TikTok adapter** | Content Posting + audit | ~1 session + **audit wait** | go-live memo |
| **2d — GBP posts** | local posts + allow-list request | ~½ session + **allow-list wait** | go-live memo |
| **3 — Monitoring** | Prestige social-post canary | ~½ session | — |
**Costs:** posting APIs (Meta/YouTube/TikTok/GBP) are **$0**; YouTube quota is free within limits.
No metered spend. The cost is effort + calendar, not dollars.
---
## Hard gates (your standing rules)
- **Creating real social accounts** in the business's name → owner decision.
- **Auto-posting to a live customer-facing account** → textbook gated/customer-facing action; each
platform's go-live is a `pending-approval` memo, not a silent flip.
- **server.js deploy** → coordinate with the owning session (the July-break class).
- **Tokens** → only via the `secrets` skill; never in `credentials.json` or code.
## Recommendation
Do **Phase 1 now** (safe, dark, coordinated) so the queue becomes real/server-side and the scheduler
machinery exists. Hold **Phase 2+** until the owner supplies accounts + creds, and start with **Meta
(IG+FB)** since IG is the only existing account and the FB Page is a quick create. Ship every adapter
**inert-until-keyed**, so the whole system can land without posting a single thing until you say go.