Two-layer structure: Sources (raw notes) + Wiki (compile output) Four domains: Dev (40), Venture (3), Homelab (23), Reference (0) Includes CLAUDE.md spec, index pages at all levels, compile log Co-Authored-By: Lovebug <lovebug@herbylab.dev>
7.6 KiB
| created | path | project | status | tags | type | updated | |||||
|---|---|---|---|---|---|---|---|---|---|---|---|
| 2026-03-17 | Sources/Dev | instagram-reel-sync-phase4 | active |
|
project-plan | 2026-03-17 |
Phase 4 Handoff — Instagram Reel Detection & Sync
Context for New Chat Session
This is a handoff document from the previous chat session. Paste this into the new chat along with the standard project context to get up to speed quickly.
Current State — What's Already Built
MySQL Schema (pbs_automation database)
pbs_recipes:
CREATE TABLE IF NOT EXISTS pbs_recipes (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
post_id BIGINT UNSIGNED NOT NULL,
recipe_title VARCHAR(500) NOT NULL,
recipe_url VARCHAR(500) NOT NULL,
keyword VARCHAR(100) NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY (post_id)
);
instagram_posts:
CREATE TABLE IF NOT EXISTS instagram_posts (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
reel_id VARCHAR(50) NOT NULL,
pbs_post_id BIGINT UNSIGNED NULL,
instragram_caption TEXT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY (reel_id),
FOREIGN KEY (post_id) REFERENCES pbs_recipes(post_id)
);
Key schema decisions:
post_idis nullable ininstagram_posts— NULL means unmatched reelreel_idis VARCHAR not BIGINT (Instagram ID integer overflow issue)- Normalized — keyword and URL live in
pbs_recipesonly, JOIN at query time - Foreign key constraint means
pbs_recipesrecord must exist beforeinstagram_postsinsert
WordPress → pbs_recipes Sync (Complete ✅)
- WPCode Lite PHP snippet fires on every post save (admin only)
- Payload: post_id, title, url, tags, categories
- HMAC signature via X-PBS-Signature header (raw body via X-PBS-Body base64)
- n8n workflow verifies signature, upserts into pbs_recipes
- Keyword populated from first WordPress tag if present
- COALESCE logic: only sets keyword if currently NULL (preserves manual edits)
Instagram Comment Reply Workflow (Complete ✅)
- Handles incoming Instagram comment webhooks
- Hash verification → subscribe check → PBS message filter
- Looks up reel in
instagram_poststable - Joins to
pbs_recipesfor keyword and URL - Sends DM + public reply on keyword match
- All dead ends notify Travis via Google Chat
Notify Travis Workflow (Complete ✅)
- Webhook trigger at: https://n8n.plantbasedsoutherner.com/webhook/notify-pbschat
- Routes all alerts to Google Chat
- Payload format:
{
"title": "Alert title",
"message": "Detail message",
"timestamp": "={{ $now.toFormat('MMM dd, HH:mm:ss') }}"
}
Phase 4 Goal — Automate Instagram Reel Detection
The Problem
When Jenny publishes a new Instagram Reel, someone currently has to manually:
- Get the WordPress post ID
- Add it to the reel caption
- Add the reel ID to the
instagram_poststable
The Solution — Three-Tier Detection System
Tier 1 — Comment Triggered (real time)
When a comment comes in and instagram_posts lookup returns empty:
- Instead of just notifying Travis, trigger the "Fetch Reel Data" workflow
- Pass enough context to reply to the original commenter if reel is found
- First commenter may experience a few seconds delay — acceptable
Tier 2 — Scheduled Poll (every 12 hours)
- n8n Schedule Trigger polls Instagram media API
- Catches reels that haven't received a comment yet
- Proactively keeps table up to date
Tier 3 — Manual Trigger (Content Hub)
- "Sync Reels" button in PBS Content Hub Flask app
- Jenny or assistant can trigger on demand after publishing
Queue and Retry Pattern (Option B)
When Tier 1 triggers:
- Comment comes in → no record found → store original comment data
- Trigger "Fetch Reel Data" workflow with comment context
- If reel found → insert into
instagram_posts→ retry reply to original commenter - If reel not found → Notify Travis with details
Phase 4 Architecture
New Workflow — "Fetch Reel Data"
Triggers:
- Called from comment reply workflow (Tier 1) with single reel ID
- Called from Schedule Trigger (Tier 2) with last 20 media items
- Called from Content Hub webhook (Tier 3) manually
Core loop logic (Loop Over Items node):
[Get reels from Instagram API]
→ [Code Node: extract post_id from caption]
→ [Loop Over Items]
→ [MySQL SELECT: pbs_recipes WHERE post_id = extracted_id]
→ [IF: record found?]
→ TRUE → [MySQL UPSERT: instagram_posts]
→ FALSE → [Notify Travis: Bad/missing post ID in caption]
→ [After loop: if Tier 1 triggered → retry comment reply]
Handoff payload from comment reply workflow (Tier 1):
{
"resolved_media_id": "the reel ID",
"comment_text": "original comment text",
"from_username": "commenter username",
"comment_id": "needed to post public reply"
}
Instagram Media API Details
No webhook exists for new reel publications — confirmed via Meta docs research. Polling is the only option.
API endpoint:
GET https://graph.facebook.com/v25.0/{ig-user-id}/media
?fields=id,caption,media_type,media_product_type,permalink,timestamp
&limit=20
&access_token={token}
Key field: media_product_type === "REELS" — do NOT use media_type,
it returns "VIDEO" for both regular videos and Reels.
Rate limits: Generous — 4,800 × account impressions/24hrs. Polling every 12 hours is negligible.
Token: Long-lived user access token (60-day expiry). Build monitoring for token expiry — biggest operational risk.
Key n8n Patterns Established
- Always Output Data on MySQL nodes so IF nodes can evaluate empty results
- Check field exists (
id exists) notlength > 0when Always Output Data is on - Google Chat HTTP Request must use "Using Fields Below" not raw JSON
- Message strings built as single
={{ }}expression with+concatenation $now.toFormat()works in test and live contexts (not$execution.startedAt)- MySQL query parameters — do NOT use
=prefix, plain comma separated values - Loop Over Items — use explicit
$('Node Name').first().jsonnot$jsoninside loops since$jsononly references immediate previous node - Set node before loops — consolidate upstream data needed inside loop
so it's always accessible as
$json.field reel_idalways VARCHAR — Instagram IDs exceed JS safe integer limit
Remaining Work After Phase 4
- Refactor comment reply workflow to split at
Check if we sentinto separate "Instagram Reply Handler" workflow (cleaner logs) - PBS Content Hub — Phase 5 planning session required before building
- Authelia SSO for admin tools (Portainer, n8n, Uptime Kuma, phpMyAdmin)
- Deploy all staging changes to production
Infrastructure Quick Reference
- Staging: staging.plantbasedsoutherner.com
- n8n: n8n.plantbasedsoutherner.com
- phpMyAdmin: phpmyadmin.staging.plantbasedsoutherner.com
- PBS-API: internal Docker service at http://pbs-api:5000
- MySQL database: pbs_automation
- Google Chat alerts: via Notify Travis workflow webhook
Last Updated: March 17, 2026 Maintained by: Travis Project: Plant Based Southerner — Instagram Automation Phase 4