Project Liberal
System Architecture
Technical documentation for the backend infrastructure β Make.com scenarios, Notion automations, Zoho Flow, Claude prompts, Google Sheets data layer, Metricool publishing, and WordPress media integration.
Last updated: March 12, 2026
System Overview
The Project Liberal content system is an automation pipeline connecting Notion (content storage), Google Chat (user interface), Make.com (orchestration), Zoho Flow (scheduling delays), Metricool (social publishing), Claude API (AI processing), WordPress (media hosting), and Google Sheets (configuration and state).
There are two organizations β Project Liberal Action and Project Liberal Foundation β each with its own Notion content database, Metricool social accounts, and Google Chat spaces. The infrastructure is mirrored: same automations, same schemas, separate data.
A third organization, Liberal Memes (History is Over), exists in the database layer (Organization Settings, People in Content relations, Scheduling History) but is excluded from the user-facing documentation and chatbot routing.
Architecture Map
Six Make.com scenarios, three Notion automation sets, one Zoho Flow, and supporting services.
Services & Accounts
| Service | Role | Notes |
|---|---|---|
| Make.com | Orchestration | 6 scenarios. All webhooks on us1.make.com. |
| Notion | Content storage | 4 databases (2 content, People, Scheduling History). 5 automations across databases. |
| Google Chat | User interface | Bot receives messages via event trigger (not webhook). Spaces are isolated. |
| Google Sheets | Config & state | 9 sheets: Messages, Space Settings, User Settings, Org Settings, ChatGPT Prompts, Info for Make.com, Thumbnail Links, Database Sync, Time Interpreter. |
| Google Drive | File storage | All uploaded media (images, video) stored here. Thumbnails extracted from Drive. |
| Claude API | AI processing | All calls use claude-haiku-4-5-20251001. Used for intent routing, Notion queries, content creation, image description, transcript summarization, people matching. |
| Zoho Flow | Scheduling delay | Single flow with two Deluge functions. Holds content until scheduled time, then sends to Make.com. |
| Metricool | Social publishing | Per-network scheduleAPost calls. Separate Metricool accounts per org. |
| WordPress | Media hosting | Thumbnails uploaded to projectliberal.org via WP REST API (/wp-json/wp/v2/media). Used for content card images. |
| OpenAI (Whisper) | Transcription | Video audio transcribed via whisper-1. Called from New Content Submission. |
| CloudConvert | Video processing | Extracts audio from video files before Whisper transcription. |
| Serper | Web search | Used in Update Person in Content to find social media profiles. |
| YouTube API | Video upload | Direct video upload from Google Drive for Video Short content. |
Content Database Automations
Three Notion automations run on both PL Action Content and PL Foundation Content (6 total, mirrored). Each sends a webhook with all existing properties.
Type is set to Video Short, Graphic, or TweetFires webhook to Make.com New Content Submission scenario. Sends all properties. This is the content enrichment pipeline β generates Name, Tags, descriptions, thumbnails, transcripts, people tagging.
Status is editedFires webhook to Make.com Schedule from Status scenario. Sends all properties including Name - Description, Canva Link, Caption (public). This handles "Schedule -" status values β assigns time slots and updates the page.
Scheduled Date is editedFires webhook to Zoho Flow (not Make.com). Sends the page ID and scheduled date. No properties selected in the webhook content β Zoho only needs the ID and date to calculate the delay.
People in Content Automations
Two automations on the People in Content database.
Status is set to "Search for handles"Sends webhook to Make.com Update Person in Content scenario with Name, Liberal Memes DB relation, and Bio. The scenario searches the web for social profiles and writes URLs back to Notion.
Runs Notion formulas to extract handles from URL properties. Strips domains (handles both twitter.com and x.com) and formats as (@handle) for: Twitter, Threads, Bluesky, Facebook, Instagram, TikTok. The formula uses nested replaceAll with regex to strip the domain and trailing path.
// Twitter handle extraction formula (representative)
if(
empty( Trigger page . Twitter URL ),
"",
" (@" +
replaceAll(
replaceAll(
Trigger page . Twitter URL,
"https?://(www\\.)?(twitter\\.com|x\\.com)/",
""
),
"/.*",
""
) +
")"
)
Chatbot 1.1
Trigger: Google Chat event
The main scenario. Receives all Google Chat messages and routes them through the AI pipeline. This is the largest scenario with ~80 modules across 9+ workflow routes.
Top-Level Routes
| Route | Trigger Condition | What It Does |
|---|---|---|
| Route 0 | Message is a reply | Fetches original message via Google Chat API. Logs to Sheets history. |
| Route 1 | Message contains URL | Regex parses URL. Sub-routes: bystander (log only), webpage (fetch + HTML-to-text + Claude), image URL (upload to Drive + Claude describe + WordPress thumbnail). |
| Route 2 | Message has attachment | Sub-routes for non-Drive attachments (fetch via Chat API) vs Drive attachments (fetch metadata). Both: upload to Drive β Claude describe β WordPress thumbnail β log. |
| Route 3 | Default (all messages) | Main processing pipeline. Logs message, loads space config, loads permissions. |
Route 3 Sub-Routes (Workflow Engine)
| Sub-Route | Workflow | Key Modules |
|---|---|---|
| 3.0 | New user / new space | Provisions records in Sheets. Sends welcome message. Lists Notion users for initial sync. |
| 3.1.0 | Permissions | Claude Haiku parses request β Sheets lookup/update β Chat response. Manages all 6 permission types except Admin. |
| 3.1.1 | Button Manager | Claude Haiku processes request β Sheets config read/write β Chat response. |
| 3.1.2 | Editing | Claude Haiku generates edit JSON β Sheets lookup β Notion update β Chat response with card. |
| 3.1.3 | Search | Loads field data from Sheets β Claude Haiku builds Notion API query β Notion search β Chat card(s). |
| 3.1.4 | Database Sync | Fetches Notion DB schema via API β iterates fields β syncs to Sheets. No Claude call. |
| 3.1.5 | Upload | Claude Haiku generates upload JSON β Sheets/Notion creates page β Chat response with card. |
| 3.1.6 | Creation (3 sub-routes) | Each creation type has its own Claude Haiku call with dedicated prompt. Tweet (6a), Graphic (6b), Video Short (6c). Each β Chat response β history log. |
| 3.1.7 | Notion API execution | Executes the API call built by sub-routes 2/3/5. Deeply nested router handles card rendering: creates Chat message, then updates with cardsV2. Handles search results (multiple cards via feeder), edit confirmations, and upload confirmations. |
| 3.1.8 | Fallback | Error/unknown intent response. Chat message + history log. |
Intent Router β Sub-Route Mapping
The Intent Router (Claude Haiku, module 269) outputs a workflow value. The Make.com router's filter conditions map these to sub-routes:
"notion" β 3.1.3 (search), 3.1.2 (edit), 3.1.5 (upload) β determined by Notion workflow prompt "tweet creation" β 3.1.6a "graphic creation" β 3.1.6b "video short creation" β 3.1.6c "permissions" β 3.1.0 "button manager" β 3.1.1 "database sync" β 3.1.4 "notion user sync" β 3.0 (re-uses provisioning route) "timezone manager" β 3.1.0 (handled within permissions route) null + error_message β 3.1.8 (fallback β error sent as Chat message)
Chatbot Link Clicker
Trigger: Webhook (card button click)
Handles button clicks on Google Chat content cards. Receives: Notion page ID, action value, user secret code, database ID, message ID.
Flow
| Step | Module | Details |
|---|---|---|
| 1 | Webhook | Receives click payload from Google Chat card button. |
| 2 | Google Sheets | Looks up user permissions by secret user code. |
| 3 | Router (5 routes) | Routes by action value. |
Routes
| Route | Action | What It Does |
|---|---|---|
| 0 | approvecontent | Updates Notion page Status β Approved. Updates card in-place via cardsV2 updateMask. |
| 1 | rejectcontent | Updates Notion page Status β Rejected. Updates card in-place. |
| 2 | scheduleN | Updates Notion page with calculated schedule time (current time + N minutes). Updates card in-place. |
| 3 | suggestcontent | Fetches Notion page, sends it to the org's default Chat space. Space determined by database ID β space ID conditional mapping. |
| 4 | Fallback | Sends error message to Chat. |
google-chat:makeApiCall with URL /v1/{messageid}?updateMask=cardsV2 to update the existing card rather than posting a new message. The messageid is URL-decoded from the webhook payload.New Content Submission
Trigger: Webhook (Notion "Submit New Content" automation)
Processes new content when Type is set. Generates Name, Tags, descriptions, thumbnails, transcripts, and people tags. This is the content enrichment pipeline.
Flow
| Step | Module | Details |
|---|---|---|
| 1 | Webhook | Receives full page data from Notion. |
| 2β3 | Google Sheets (Γ2) | Loads space config and field mappings. |
| 4 | Set Variable | Stages shared data for routing. |
| 5 | Router (3 routes) | Routes by content type + file presence. |
Route 0 β File-Based Content (Graphics & Video)
Downloads file from File (public) β uploads to Google Drive β gets file metadata/thumbnail.
| Sub-Route | Type | Processing |
|---|---|---|
| 0a | Graphic | Claude Haiku describes image β downloads thumbnail β uploads to WordPress (/wp-json/wp/v2/media) β updates Notion page (Name, Tags, Internal Notes, Public Thumbnail, etc.). |
| 0b | Video Short | CloudConvert extracts audio β Whisper transcribes β Claude Haiku summarizes transcript β updates Notion page (Name, Tags, Internal Notes, Transcript, etc.). |
Route 1 β Tweets (Text Only)
Claude Haiku processes text content β generates Name, Tags, AI Summary β updates Notion page via API.
Route 2 β People Tagging (All Types)
Runs for all content after the type-specific route:
- Fetches the content page from Notion.
- Searches the People in Content database for matches.
- Aggregates results into text.
- Claude Haiku determines if any people match the content.
- If match found: updates existing People record with content relation. If new person: creates a new People in Content record, then updates the content page.
Schedule from Status
Trigger: Webhook (Notion "Schedule Automation")
Handles status changes that trigger scheduling. Receives when any status is changed on a content page. Processes "Schedule -" status values into actual Scheduled Date values.
Flow
| Step | Module | Details |
|---|---|---|
| 1 | Webhook | Receives full page data. |
| 2 | Google Sheets | Pulls scheduling config β time slot rules, timezone offsets, next available slots. |
| 3 | Router (2 routes) | Routes based on status type. |
Routes
| Route | Condition | What It Does |
|---|---|---|
| 0 | Direct schedule (simple status change) | Updates Notion page directly (e.g. Status β Approved with no scheduling needed). |
| 1 | "Schedule -" status detected | Updates Google Sheets scheduling queue row β looks up next available time slot from Sheets β updates Notion page with Scheduled Date + Status β Approved. |
Metricool Content Poster
Trigger: Webhook (Zoho Flow "Send_Webhook")
The final stage of the publishing pipeline. Receives a Notion page ID and Scheduled Date from Zoho Flow, fetches the full content, then publishes to all configured social networks. This is the most deeply nested scenario β ~50+ modules across multiple routers.
Flow
| Step | Module | Details |
|---|---|---|
| 1 | Webhook | Receives Data_ID (Notion page ID) and Scheduled_Date_Start. |
| 2 | Notion: Get Page | Fetches full page data. |
| 3 | Router | Top-level split by org/content type. |
Per-Network Publishing
The router fans out into deeply nested sub-routes. Each content type Γ organization combination has its own set of metricool:scheduleAPost calls because per-network field requirements differ (image URLs, video files, caption formats, alt text, hashtags).
After all Metricool calls complete, each branch:
- Creates a Scheduling History record in Notion (
notion:createDataSourceItem). - Updates the content page via Notion API (
/v1/pages/{id}) β sets Last Used date, updates status. - Sends confirmation to Google Chat via
google-chat:makeApiCallβ posts to the space ID looked up from Google Sheets.
YouTube Upload Branch
For Video Short content, dedicated branches fetch the video file from Google Drive (google-drive:getAFile) and upload directly to YouTube (youtube:uploadVideo). This runs alongside the Metricool posts, not instead of them.
Update Person in Content
Trigger: Webhook (Notion "Look for Social Media Handles")
Searches the web for a person's social media profiles and writes the URLs back to Notion.
Flow
| Step | Module | Details |
|---|---|---|
| 1 | Webhook | Receives person data (Name, Bio, relation IDs). |
| 2 | Serper (Γ2) | Two web searches β likely "[name] social media" and "[name] twitter/X". |
| 3 | Claude Haiku | Processes search results, extracts social URLs from results. |
| 4 | Notion Search | Finds the person's page. |
| 5 | Router | Updates existing record or creates new one with extracted URLs. |
Zoho Flow β Scheduling Delay System
Trigger: Webhook (Notion "Schedule Date From Status" automation)
Receives a Notion page ID when a Scheduled Date is set. Calculates the delay until the scheduled time, waits, then fires a webhook to the Metricool Content Poster scenario.
Flow Steps
Deluge Functions
Parses both timestamps, calculates difference in minutes. Enforces a minimum of 4 minutes β if the calculated delay is less than 4, it's set to 4. This prevents instant-fire and gives users a cancellation window.
string Minutes_till_Scheduled(string Current_Time, string Scheduled_Time)
{
cleanScheduled = Scheduled_Time.subString(0,19).replaceAll("T"," ");
cleanCurrent = Current_Time.subString(0,19).replaceAll("T"," ");
scheduledDT = cleanScheduled.toDateTime("yyyy-MM-dd HH:mm:ss");
currentDT = cleanCurrent.toDateTime("yyyy-MM-dd HH:mm:ss");
diffMs = scheduledDT.toLong() - currentDT.toLong();
diffMinutes = (diffMs / 60000).toLong();
if(diffMinutes < 4)
{
diffMinutes = 4;
}
return diffMinutes.toString() + " minutes";
}
Posts the Notion page ID and scheduled date to the Make.com Metricool Content Poster webhook.
string Send_Webhook(string NotionID, string ScheduledDateStart)
{
payload = Map();
payload.put("Data_ID", NotionID);
payload.put("Scheduled_Date_Start", ScheduledDateStart);
response = invokeurl
[
url: "https://hook.us1.make.com/i2bxdatn59m1b9beovm77p33elk90tcb"
type: POST
body: payload.toString()
headers: {"Content-Type":"application/json"}
];
return response;
}
Intent Router Prompt
Module 269 in Chatbot 1.1 claude-haiku-4-5-20251001
Determines which workflow handles each message. Outputs a JSON object with workflow, error_message_to_user, set_organization, thumbnail, and button state.
Key Logic
| Priority | Rule |
|---|---|
| 1 | Organization check β runs first. Auto-detects org from space name. Locks org in dedicated spaces. Allows switching in DMs. Blocks all processing if org is null. |
| 2 | Bystander detection β if message appears directed at humans rather than the bot (shared content, room-directed questions), logs it and responds neutrally. |
| 3 | Workflow continuation β if previous Claude response was tagged with a workflow prefix and user's reply is a short follow-up, re-routes to same workflow without re-evaluating intent. |
| 4 | Spelling/grammar check β if user uploads a graphic or video, extracts visible text and checks for errors. Blocks upload until user approves or fixes. |
| 5 | Workflow routing β matches intent to one of 9 workflows based on keywords and context. |
| 6 | Permission inline responses β "what are my permissions?" and "what's my timezone?" answered directly in error_message_to_user without routing to a workflow. |
| 7 | Button state extraction β always parses Space Button Settings JSON and populates all 8 button fields regardless of routing outcome. |
Workflow Values
"notion" Β· "tweet creation" Β· "video short creation" Β· "graphic creation" Β· "permissions" Β· "button manager" Β· "database sync" Β· "notion user sync" Β· "timezone manager" Β· null (with error message)
Routing Disambiguation
Graphic attached + no help request β notion (upload). Graphic attached + help request β graphic creation. "sync" + "user/users" β notion user sync. "sync" without "user" β database sync. Unclear content type β defaults to tweet creation.
Output Schema
{
"workflow": string | null,
"error_message_to_user": string | null,
"set_organization": string | null,
"Public_Thumbnail": string | null,
"Button1_Name": string | null,
"Button1_Action": string | null,
"Button2_Name": string | null,
"Button2_Action": string | null,
"Button3_Name": string | null,
"Button3_Action": string | null,
"Button4_Name": string | null,
"Button4_Action": string | null
}
Notion Workflow Prompt
Module 293 in Chatbot 1.1 claude-haiku-4-5-20251001
Handles search, edit, and upload actions against the Notion content database. Outputs a complete Notion API request body ready to execute.
Key Logic
| Area | Behavior |
|---|---|
| Permission enforcement | Checks Search/Editing/Uploading/Scheduling permissions. Blocks the entire action if required permission is missing. Scheduling permission only strips the date field (doesn't block the rest). |
| Action detection | Determines search vs edit vs upload from the most recent message. Extracts Notion page IDs from URLs (reformats to 8-4-4-4-12 dash pattern). |
| Confirmation | Edits require explicit confirmation. Uploads and searches proceed immediately. |
| Search filters | Default status filter: Approved. "And" logic = each keyword in its own OR block inside AND. "Or" logic = all keywords in single OR block. Scheduled date searches: is_not_empty + ascending sort + limit 100. |
| Auto-status | Setting Scheduled Date on edit β Status auto-set to Approved. Upload with Scheduled Date β Approved. Upload without β Needs Review. |
| Body construction | Outputs complete body_json string with Notion API property formats. Search: filter + page_size. Edit: properties object. Upload: parent + properties. |
Output Schema
{
"action": "search" | "edit" | "upload" | null,
"API_URL": string | null,
"Method": "POST" | "PATCH" | null,
"Scheduled Date": string | null,
"Public_Thumbnail": string | null,
"body_json": string,
"question_for_user": string | null
}
Tweet Creation Prompt
Module 450 in Chatbot 1.1 (Route 3.1.6a) claude-haiku-4-5-20251001
Writes tweets in Project Liberal's voice. Identifies task type (new/rewrite/alternatives/refine), content category, style, and tone.
Key Rules
Voice: Confident classical liberal. Uses "liberal" not progressive/centrist. Opponents labeled "illiberal"/"MAGA"/"authoritarian" not "Republican"/"GOP". No progressive buzzwords. No doom without confidence.
Length: Default under 100 chars. Max 200 unless style requires more. Median ~78 chars for punchy styles.
Formatting: Double line breaks for dramatic pause. ALL CAPS sparingly (single words). 0β2 emoji max. 0β1 hashtag max.
8 tweet styles: punchy_declarative, quote_attribution, contrast_hypocrisy, you_cant_be, question_led, to_be_is_to_be, historical_parallel, sarcastic_mockery.
Output Schema
{
"reasoning": string,
"identified_task": "new" | "rewrite" | "alternatives" | "refine" | "unclear",
"identified_style": string,
"identified_tone": string,
"identified_category": string,
"tweet_output": string | null,
"message_to_user": string
}
Graphic Creation Prompt
Module 453 in Chatbot 1.1 (Route 3.1.6b) claude-haiku-4-5-20251001
Generates graphic concepts: visual format, exact text, image-text relationship, branding notes. Identifies task type (new_concept/write_text/refine/alternatives/help_with_attached).
Key Rules
Every graphic must make a specific argument β not just express a mood. Must frame from classical liberal principles.
Primary text target: under 120 characters. Uses specific numbers/data over vague claims.
Meme template rules: Drake = preference contrasts, Distracted Boyfriend = misplaced priorities, Wojak = in-group dynamics, NPC = mindless talking points, etc. Format must match argument structure.
Each concept delivers 5 components: graphic concept, primary text, supporting text, branding note (@projectliberal), why it works.
Output Schema
{
"reasoning": string,
"identified_task": string,
"identified_style": string,
"identified_tone": string,
"identified_category": string,
"graphic_output": string | null,
"message_to_user": string
}
Video Short Creation Prompt
Module 456 in Chatbot 1.1 (Route 3.1.6c) claude-haiku-4-5-20251001
Writes spoken-word scripts. 3β5 beat structure: hook β core fact β context/evidence β escalation/stakes β closing punch. Identifies structure, tone, and target length.
Key Rules
Hook rules: First sentence must be bold declarative, provocative question, counterintuitive framing, or direct challenge. No preamble ever.
Closing rules: Warning, mic-drop, rhetorical question, principle affirmation, or callback. Never a CTA or summary.
8 script structures: breaking_news_analysis, hook_first_escalation, contrast_reversal, data_led_argument, rapid_fire_rhetorical, direct_address_response, storytelling_human_interest, philosophical_essay.
3 length brackets: Short (150β250 words), Standard (250β400), Long (400β600).
Visual directions: inline bracketed (e.g. [ON SCREEN: $330 billion / year]) only where they add value.
Output Schema
{
"reasoning": string,
"identified_task": string,
"identified_structure": string,
"identified_tone": string,
"identified_category": string,
"target_length": "short" | "standard" | "long",
"script_output": string | null,
"message_to_user": string
}
Permissions Prompt
Module 469 in Chatbot 1.1 (Route 3.1.0) claude-haiku-4-5-20251001
Manages user permissions. View is open to all. Modify requires Admin. Admin Permission itself cannot be changed via chatbot. All changes require confirmation. Outputs current permission values for the target user.
Managed Permissions
Search Β· Scheduling Β· Editing Β· Uploading Β· Creation (not yet implemented in routing)
Admin β read-only via this prompt. Cannot be modified.
Output Schema
{
"User ID": string | null,
"Admin Permission": boolean,
"Search Permission": boolean,
"Scheduling Permission": boolean,
"Editing Permission": boolean,
"Uploading Permission": boolean,
"Creation Permission": boolean,
"Success Message to User": string | null,
"Questions to User": string | null
}
Button Manager Prompt
Module 418 in Chatbot 1.1 (Route 3.1.1) claude-haiku-4-5-20251001
Manages the 4-button configuration and available actions list per space. Stores config as minified JSON in Google Sheets.
Key Rules
Buttons must have both name + action, or neither. Actions must exist in available_actions before assignment. Can't remove an action assigned to a button. At least one action must remain. Schedule actions require minutes value. Outputs validated JSON string.
Output Schema
{
"reasoning": string,
"button_settings": "{\"buttons\":[...],\"available_actions\":[...]}",
"error_message_to_user": string | null,
"success_message_to_user": string | null
}
Google Sheets β Schema Reference
9 sheets in the ChatBot workbook. Sheets serve as the configuration layer, state store, and history log for the chatbot system.
Chat history log. One row per message (user or bot). Used to load last 10 messages as context for each chatbot execution.
Columns: Date/Time, Message, Space ID, Message ID, Thread ID, Attachment ID, User ID
Per-space configuration. One row per Google Chat space. Stores org assignment, Notion database ID, button configuration, and blocked flag.
Columns: Date, Space ID, Name, Organization, Notion ID, Notes, Space Instructions, Organization Short Name, Blocked, temp2, temp3, Button Settings (JSON)
Per-user permission and config data. Users have separate rows per org β one for Action, one for Foundation. Permissions are org-scoped.
Columns: Date Added, User Name, Organization, User Google ID, Secret User Code, Notion Database ID, Admin Permission, Search Permission, Scheduling Permission, Editing Permission, Uploading Permission, Creation Permission, Email, User Timezone, Notion User ID, Chatbot DM Space
Org-level configuration. One row per org (Action, Foundation, Liberal Memes).
Columns: Organization Name, Notion ID, Organization Short Name, Default Chat Space, Content to Socials Alert (boolean), New Content Alert (boolean)
Legacy/older prompt versions for tweet and video short creation. Contains the full Tags list and Script Training Data corpus used by the v1 creation workflows. Note: The v1.2 prompts (currently active) are injected directly in Make.com module configs, not read from this sheet.
Columns: Pathway, Prompt/Result, Tags, Video Short Scripts, Defunct
Single-row reference data fed to Make.com scenarios. Contains the full Tags list and Script Training Data corpus.
Columns: Line Item, Tags, Script Training Data
Fallback thumbnail URLs used when content doesn't have its own thumbnail.
Columns: Phoenix (default PL logo), Twitter (Twitter/X icon), Video file Unavailable (video placeholder)
Field mapping table used by the Database Sync workflow. Maps Notion property names to types, sync flags, Claude permissions, and per-database property IDs.
Columns: Property Name, Types, Sync (boolean), Claude Permissions, pl_foundation_id, liberal_memes_id, pl_action_id
Scheduling time calculation sheet. Converts schedule status labels into actual datetime values. Used by the Schedule from Status scenario to determine target times relative to today's date and the user's timezone.
Columns: Time from Notion, Fields, Today's Date, Time, Date Output (+ additional calculation columns)
Content Databases
Two mirrored databases: PL Action Content (29 properties, ~2,100 rows) and PL Foundation Content (28 properties β same minus Created time).
Full Property List
| Property | Type | Source |
|---|---|---|
| Name - Description | Title | System (New Content Submission) |
| Type | Select | User (triggers automation) |
| Status | Select | User / System |
| Caption (public) | Rich text | User |
| Description (public) | Rich text | User |
| Video Title (public) | Rich text | User |
| Transcript | Rich text | System (Whisper via New Content Submission) |
| Tags | Multi-select | System (New Content Submission) |
| Full Tags list | Formula | Auto |
| File (public) | Files | User |
| Canva Link | URL | User |
| Public Thumbnail | URL | System (WordPress upload) |
| Internal Notes | Rich text | User + System |
| Twitter Image Alt Text | Rich text | User |
| Instagram Hashtags | Rich text | User |
| Scheduled Date | Date | User / System (Schedule from Status) |
| Submission Date | Date | System |
| Last Used | Date | System (Metricool Content Poster) |
| Created By | Text | System |
| Created time | Created time | Auto (Action only) |
| Evergreen Rating | Text | Manual |
| Temp | Rich text | User |
| TEMPORARY - Person In Content | Text | Legacy |
| Unique ID | Text | System |
| Google File ID | Text | System |
| Google Drive View Link | Formula | Auto |
| Google Drive Download Link | Formula | Auto |
| People in Content | Relation | User / System |
| Scheduling History | Relation | System |
People in Content Database
23 properties. Stores individuals referenced in content with per-platform social URLs and auto-extracted handles.
| Property | Type | Notes |
|---|---|---|
| Name | Title | Person's name. |
| Bio | Rich text | Brief bio. |
| Identifying info | Rich text | Orgs, titles, other identifying context. |
| Status | Select | "Search for handles" triggers lookup automation. |
| Wikipedia | URL | Wikipedia link if available. |
| Twitter URL / Handle | URL / Formula | Handle extracted via regex formula. |
| Threads URL / Handle | URL / Formula | Same pattern. |
| Bluesky URL / Handle | URL / Formula | Same pattern. |
| Facebook URL / Handle | URL / Formula | Same pattern. |
| Instagram URL / Handle | URL / Formula | Same pattern. |
| TikTok URL / Handle | URL / Formula | Same pattern. |
| YouTube URL / Handle | URL / Formula | Same pattern. |
| PL Action Content | Relation | Content referencing this person. |
| PL Foundation Content | Relation | Content referencing this person. |
Scheduling History Database
10 properties. One record per social media publication event, created by the Metricool Content Poster scenario.
| Property | Type | Notes |
|---|---|---|
| Name | Title | Auto-generated: date + org + content name. |
| Organization | Select | Which org published this. |
| Type | Select | Tweet, Graphic, or Video Short. |
| Scheduled Date | Date | When published. |
| Timestamp | Text | Exact timestamp. |
| 24 Twitter Performance | Text | Metrics after 24 hours. |
| 1 Week Twitter Performance | Text | Metrics after 1 week. |
| PL Action Content | Relation | Back-link to source content. |
| PL Foundation Content | Relation | Back-link to source content. |
Networks & Metricool Configuration
Connected Networks by Org
| Network | Action Account | Foundation Account |
|---|---|---|
| Twitter / X | @ProjectLiberal | @ProjectLiberalF |
| Project Liberal (Page) | Project Liberal Foundation (Page) | |
| @projectliberal (Pro) | @liberalfoundation (Pro) | |
| Threads | @projectliberal | @liberalfoundation |
| Bluesky | projectliberal.org | liberalfoundation.bsky.social |
| TikTok | @projectliberal (Business) | @liberalfoundation (Business) |
| YouTube | Project Liberal | Project Liberal Foundation |
| Project Liberal Action | β | |
| ProjectLiberal (Personal) | β | |
| Twitch | @projectliberal | β |
| Web/Blog | projectliberal.org + RSS | β |
Per-Network Publishing Notes
Each metricool:scheduleAPost call is configured per-network because field requirements differ: Instagram requires an image URL, YouTube requires video file + title, Twitter has character limits + alt text, TikTok only accepts video, etc. This is why the Metricool Content Poster has so many modules β each content type Γ network is a separate call.
WordPress Media Integration
Thumbnails are uploaded to projectliberal.org via the WordPress REST API. This provides stable public URLs for content card images in Google Chat.
How It Works
When a graphic is processed by New Content Submission or the Chatbot:
- Image is uploaded to Google Drive β thumbnail link extracted from Drive metadata.
- Thumbnail downloaded from Drive.
- Thumbnail uploaded to WordPress via
POST /wp-json/wp/v2/media. - WordPress returns a public URL (e.g.
https://projectliberal.org/wp-content/uploads/...). - URL stored in the Notion page's
Public Thumbnailproperty.
Fallback thumbnails (when no image exists) are stored in the Thumbnail Links sheet: a PL logo, a Twitter icon, and a video placeholder.