🔒

Verity AI Internal Docs

Enter password to view implementation plan

Wrong password. Try again.

⚙️ Technical Spec · Internal Only

Tristan Hamm AI Sponsorship Engine

8-stage pipeline spec: architecture, data schema, week-by-week build plan, and stage-by-stage technical detail. Built to execute from.

Verity Agentic · June 2026 · Builder reference document

Architecture Overview

The 8 stages map to 4 application systems sitting on top of a single data layer. Tristan sees Airtable. The systems run as Python processes on Annette's machine (or a VPS if uptime matters).

System A
Intelligence

Stages 01 + 02. Finds brand prospects, scores them through a Claude rubric, writes qualified brands to Airtable.

System B
Outreach

Stages 03 + 04. Sends DMs and emails, logs every touch to Airtable, runs the nurture sequence via APScheduler.

System C
Analysis

Stage 05. Watches a folder for Zoom recordings, transcribes with Whisper, extracts deal intel with Claude, creates Deal records.

System D
Routing + Delivery

Stages 06 + 08. Routes sub-$15k deals to the boxer network, calculates splits, tracks fulfillment deliverables.

Data Layer
Airtable + Sheets

6-table Airtable base. Python writes to it via pyairtable. Nightly gspread export to Tristan's existing Google Sheet.

Data flow sketch

# Intelligence (A) Brand seed list → Zendriver enrichment → Claude qualifier → Airtable.Brands # Outreach (B) Airtable.Brands[status=outreach_ready] → DeepSeek personalize → Instagram Messenger API → Airtable.Outreach_Log → Gmail API → Airtable.Outreach_Log APScheduler polls → nurture follow-ups → same send infra # Analysis (C) watchdog folder → Whisper transcription → Claude JSON extraction → Airtable.Deals (brand, amount, next_action, objections, follow_up_date) # Routing + Delivery (D) Airtable.Deals[amount<15000, status=negotiating] → boxer matching → Gmail offer → Airtable.Boxer_Deals Airtable.Deals[status=won] → fulfillment checklist → Airtable.Fulfillment → weekly summary email to Tristan # Google Sheets sync (nightly) gspread export: Brands + Outreach_Log + Deals → Tristan's sheet

Humans in the loop

Stage 07 (Close) is fully human. Tristan takes the call, negotiates, and marks the deal Won in Airtable. Everything before and after that is automated. Stage 06 routing may be semi-automated depending on Tristan's preference (confirm at discovery call).


Discovery Call Agenda (90 minutes)

Run this as a structured intake. Take notes in a shared Google Doc or record the call (get permission). Every bolded item is something you need before you can build.

Block 1: Current Pipeline (20 min)

  • "Walk me through how you land a sponsorship deal today, start to finish. Who does what?"
  • "How many brands do you reach out to in a typical month?"
  • "What's your current close rate? How many outreach attempts to one signed deal?"
  • Get the Google Sheet URL. Ask them to share it with [email protected]. Look at it live: columns, statuses, how they've been tracking.
  • "Which of these deals are you tracking actively vs. dead?"
  • "What does a deal look like when it falls through? What's the most common reason?"

Block 2: Brand Categories (20 min)

  • "Let's go category by category. Combat sports brands: who are your dream sponsors? Who have you already talked to?"
  • "Equestrian: same question. Who's shown interest, who haven't you touched?"
  • "Outdoor / lifestyle: who's on your radar?"
  • "Any categories that are off the table? Anything you've tried that didn't fit your audience?"
  • "Tell me about your best deal ever. What brand, what amount, how'd it happen?"
  • "Tell me about a deal you thought was going to close and it didn't. What happened?"
  • Collect: a list of 10-20 brands per category (even half-formed is fine). These seed the prospecting database.
  • "What does a bad-fit brand look like? Anything you'd be embarrassed to promote?"

Block 3: Boxer Network (15 min)

  • "Walk me through how you use the boxer network today. How many fighters are in it?"
  • "How do you offer a deal to them currently? Email, DM, text?"
  • Get: current split percentage (Tristan's cut vs. the boxer's cut).
  • "Is the split the same for every deal, or does it vary?"
  • "What's the threshold where you'd rather close it yourself vs. hand it off?"
  • "Have any fighters turned down a deal? Why?"
  • "Do boxers have content deliverables, or is it mostly appearance/endorsement?"
  • Collect: boxer name, Instagram handle, email, and specialty category for each fighter in the network.

Block 4: Voice Samples (10 min)

  • "Can you share 2-3 DMs you've sent to brands that you felt good about? Even screenshotted."
  • "Do you have any outreach emails you've sent? Could be from Gmail sent folder."
  • "How would you describe your tone when you're pitching? Casual, professional, hype-y?"
  • "Any phrases or sign-offs you always use?"
  • Collect: at least 3-5 real outreach samples. These train the voice model for DeepSeek.

Block 5: Zoom Setup (10 min)

  • "Do you have a paid Zoom plan?" (Free plan doesn't have cloud recordings.)
  • "Are you currently recording your sales calls? Cloud or local?"
  • "If cloud: we can pull via Zoom API automatically. If local: we'll set up a folder you drop files into."
  • Decision: cloud API vs. watchdog folder. Get Zoom API credentials if cloud.
  • "How long are your typical first-call brand conversations? 20 min? 45?"

Block 6: Tech Access Checklist (10 min)

Go through these one by one. Note what's ready vs. needs setup.

WhatWhy neededStatus to confirm
Instagram Business AccountRequired for Messenger APIBusiness or Creator? Linked to a Facebook Page?
Facebook Page (linked to IG)Required for Messenger API app approvalDoes it exist? Is Tristan the admin?
Gmail / Google WorkspaceEmail outreach + Sheets syncGet OAuth consent; confirm which Gmail address for outreach
Existing Google Sheet URLNightly sync targetGet during call
Zoom accountRecording accessPaid plan? Cloud recordings enabled?
Airtable accountCRM frontendDoes he have one? Or create new?

Block 7: Deliverable Preferences (5 min)

  • "What do you want to see in the CRM every morning? What's the daily dashboard view for you?"
  • "Do you want email summaries, or will you check Airtable directly?"
  • "When a deal gets routed to a boxer, do you want to approve it first, or should the system send automatically?"
  • "Who else on your team needs access? Manager, assistant?"
  • "Any deal categories where you'd want a human flag before outreach goes out?"

Tech Stack Decisions

ComponentToolWhyGotcha
CRM / database Airtable Visual frontend Tristan can use without training. Python-writable via pyairtable. Free tier covers 1,000 records/base; pro tier at $20/mo if needed. NOT Cloudflare D1 (no frontend). NOT a raw Postgres db. Tristan needs to see his pipeline without asking Annette.
Brand prospecting Manual seed list + Claude expansion + Zendriver LinkedIn search Instagram graph API won't give brand discovery. Industry sources (SponsorUnited, SportSponsor.com) have curated lists. Claude expands per category. Zendriver scrapes LinkedIn company pages for contact details. Build from industry lists first. Use Instagram scraping only for contact lookup on known brands.
AI qualifier Claude Sonnet 4.6 via OpenRouter 4-dimension scoring rubric needs reasoning, not just classification. Sonnet is faster and cheaper than Opus for this volume (90-150 brands, runs once). Include Tristan's audience demographics in every system prompt. Without them the scores are generic.
Instagram outreach Instagram Messenger API (official). Fallback: Phantombuster. Official API avoids account bans. Requires Business account + Facebook App + approval. Phantombuster is the bridge while approval processes. Approval takes 1-5 business days. Apply Day 5 of Week 1. Confirm Tristan has a Business account (not personal or Creator) at discovery call.
Email outreach Gmail API via OAuth (google-auth, googleapiclient) Sends from Tristan's actual Gmail, so replies land in his inbox naturally. OAuth flow is one-time setup. Gmail daily send limit: 500 emails/day for personal Gmail, 2,000/day for Workspace. Start at 20/day.
Outreach personalization DeepSeek V3.1 via OpenRouter (deepseek/deepseek-chat) Bulk generation task. DeepSeek is the cost-optimal choice here ($0.27/1M input). Voice guide in system prompt.
Nurture sequencer APScheduler (Python) Runs inside the Outreach system process. Twice-daily poll for brands due a follow-up. Same send infra as Stage 03. Check reply status before sending any nurture. A brand that replied (even "not interested") must be removed from the sequence immediately.
Zoom transcription Whisper local (openai-whisper, model: medium). Fallback: OpenAI Whisper API. Local Whisper costs nothing per call and keeps recordings private. "medium" model: good accuracy, ~5-8 min per 30-min call on CPU. If Tristan has Zoom cloud recordings, pull Zoom's own transcript via API first (faster, free). Local Whisper "medium" needs ~2GB RAM. Use "base" for speed if CPU is slow. Zoom cloud transcript is preferred if available.
Zoom recording intake watchdog library (folder monitor). Alternate: Zoom API polling. Watchdog triggers immediately when Tristan drops a file in a designated folder. Zoom API polling works if cloud recordings are enabled (poll every 15 min post-call). Decision made at discovery call based on Tristan's Zoom plan.
AI analysis of Zoom Claude Sonnet 4.6 via Anthropic direct with JSON mode Extraction needs structured output and long-context handling for full transcripts. JSON mode ensures clean field parsing into Airtable.
Deal router Python logic + Claude scoring + Gmail API for notification Pure Python logic for the threshold check and split math. Claude scores boxer-deal fit. Gmail sends the offer to the boxer. NEEDConfirm at discovery: fully automatic routing, or Tristan approves each assignment first?
Google Sheets sync gspread library One-way nightly export: Airtable data → Tristan's existing Sheet. Keeps his team in the tool they already use without migrating. Sync is one-way (Airtable is the source of truth). Edits in Sheets will be overwritten.
Scheduler / cron APScheduler (in-process) + Windows Task Scheduler (for process restarts) APScheduler handles all in-app scheduling (nurture, sync). Task Scheduler ensures processes restart after machine reboots. If this moves to a VPS, replace Task Scheduler with systemd services.

Airtable Data Schema

6 tables. Create these in order (linked fields require the target table to exist first).

Table 1: Brands

FieldTypeNotes
brand_idAuto numberPrimary key
nameSingle line textBrand name
categorySingle selectOptions: combat_sports, equestrian, outdoor
instagram_handleSingle line textWithout @
contact_emailEmailPrimary outreach email
qualifier_scoreNumber (decimal)Composite score 1-10 from Stage 02
category_fitNumberSub-score 1-10
audience_alignmentNumberSub-score 1-10
deal_size_potentialNumberSub-score 1-10
brand_marketing_activityNumberSub-score 1-10
statusSingle selectOptions: uncontacted, outreach_ready, outreached, nurturing, responded, deal_opened, won, lost, cold
last_contactedDateDate of most recent outreach touch
websiteURLOptional
notesLong textAny research notes, context from Tristan
do_not_contactCheckboxHard exclude from outreach

Table 2: Outreach_Log

FieldTypeNotes
log_idAuto numberPrimary key
brand_idLink to BrandsRequired
channelSingle selectinstagram_dm, email
touch_numberNumber1 = initial, 2 = day-3 follow-up, 3 = day-7, 4 = day-14
sent_atDate/timeTimestamp of send
message_previewLong textFirst 500 chars of what was sent
response_statusSingle selectsent, delivered, read, replied, bounced, opted_out
follow_up_dateDateNext scheduled touch (null if sequence complete)
reply_previewLong textFirst 500 chars of their reply, if any

Table 3: Deals

FieldTypeNotes
deal_idAuto numberPrimary key
brand_idLink to BrandsRequired
amount_discussedCurrencyDollar amount from Zoom call
statusSingle selectprospect, negotiating, won, lost
closer_notesLong textTristan fills this manually post-call
zoom_transcriptLong textFull Whisper transcript (may truncate in Airtable UI; full version in file)
zoom_recording_pathSingle line textLocal file path to the recording
objections_raisedLong textExtracted from Zoom by Claude
next_actionSingle line textClaude-extracted: e.g. "Send media kit"
next_action_dateDateClaude-extracted follow-up date
signed_dateDateFilled on Won
deal_value_finalCurrencyActual contracted amount (may differ from discussed)
routed_to_boxerCheckboxTrue if sent to boxer network

Table 4: Boxer_Network

FieldTypeNotes
boxer_idAuto numberPrimary key
nameSingle line textFull name
categoriesMultiple selectSame options as Brands.category
instagram_handleSingle line textWithout @
emailEmailPrimary contact for deal offers
split_pctNumber (decimal)Tristan's cut as a decimal (e.g., 0.20 = Tristan keeps 20%)
activeCheckboxUnchecked = not currently accepting deals
follower_countNumberApprox Instagram followers
notesLong textCategory strengths, audience notes

Table 5: Boxer_Deals

FieldTypeNotes
assignment_idAuto numberPrimary key
deal_idLink to DealsRequired
boxer_idLink to Boxer_NetworkRequired
split_amountCurrencyTristan's cut in dollars (deal_value × boxer.split_pct)
boxer_earningsCurrencyBoxer's cut in dollars
statusSingle selectoffered, accepted, rejected, completed
assigned_atDate/timeWhen offer was sent
offer_expires_atDate/time48-hour expiry, then route to next boxer
offer_messageLong textText of the offer email sent to the boxer

Table 6: Fulfillment

FieldTypeNotes
fulfillment_idAuto numberPrimary key
deal_idLink to DealsRequired
deliverable_nameSingle line texte.g. "1x Instagram Reel", "3x Stories", "1x Event Appearance"
deliverable_typeSingle selectreel, story, post, event, podcast, other
due_dateDateContractual deadline
statusSingle selectpending, in_progress, delivered, approved, revision_requested
brand_requirementsLong textBrand's content guidelines, hashtags, mentions required
delivered_urlURLLink to submitted content
brand_feedbackLong textBrand approval notes or revision requests

Recommended Airtable Views

  • Brands: Pipeline Kanban- grouped by status. Tristan's daily view.
  • Brands: Outreach Queue- filtered to status = outreach_ready, sorted by qualifier_score desc.
  • Outreach_Log: Today's Activity- filtered to sent_at = today.
  • Deals: Active Deals- filtered to status IN (prospect, negotiating), sorted by next_action_date.
  • Fulfillment: This Week's Deadlines- filtered to due_date <= today + 7, sorted by due_date asc.

Week-by-Week Build Plan

Week 1

Data Layer + Intelligence System (System A)

Day 1
Airtable setup. Create all 6 tables with the schema above. Set up the Pipeline Kanban and Outreach Queue views. Share with Tristan (viewer access initially). Create a test record in each table to verify linked fields.
Day 2
Brand seed list. Manually research 30-50 brands per category (90-150 total). Sources: SponsorUnited.com, SportSponsor.com, Tristan's existing brand contacts from his Google Sheet, and competitor analysis (who sponsors other combat sports creators at his follower tier). Enter all into Airtable with status = uncontacted.
Day 3
AI qualifier script. Python script: pull all brands with status = uncontacted, send each to Claude Sonnet via OpenRouter with the 4-dimension rubric, parse JSON response, write scores back to Airtable. Set status = outreach_ready for any brand scoring above 6.0 composite. Run it. Review first 20 results manually.
Day 4
Google Sheets sync. Build gspread export script. OAuth with Tristan's Google account. Export: Brands table + Outreach_Log + Deals to three tabs in his existing Sheet. Schedule nightly at 11pm. Test it live.
Day 5-7
Instagram Messenger API setup. Day 5: Create Facebook App in Meta Developer Console, link Tristan's Instagram Business Account, submit for Basic Display API + Messenger API access. Approval can take 1-5 days, so start immediately. While waiting: set up Phantombuster as the bridge. Day 6-7: Zendriver brand enrichment script, for each brand in Airtable, look up their Instagram handle if missing, and scrape their bio for contact email if not already populated.
Week 2

Outreach System (System B)

Day 8-9
Voice profiling. Take all outreach samples collected at the discovery call. Run them through Claude with this prompt: "Analyze these outreach messages from a creator. Extract: (1) typical opener style, (2) tone adjectives (3 words), (3) how they position their value, (4) sign-off pattern, (5) emoji usage level, (6) average message length, (7) 5 things this writer never does." Output becomes the voice guide stored in voice_guide.md.
Day 9-10
Outreach templates. Generate with DeepSeek using the voice guide in the system prompt. Create: 3 DM templates (intro, day-3 follow-up, day-7 follow-up) per category (9 DM templates total), and 3 email templates per category (9 email templates total). Each template uses {{brand_name}}, {{specific_brand_detail}}, and {{tristan_relevant_tie_in}} as variable slots. Review every template manually before going live.
Day 10-11
Instagram DM automation. Wire up Messenger API if approved. If not, configure Phantombuster: "Instagram DM" phantom, CSV input from Airtable export, max 20 DMs/day, 60-180s random delay between sends. Log every send to Outreach_Log via Phantombuster webhook or post-run CSV parse.
Day 11-12
Gmail API setup. OAuth flow with Tristan's Gmail. Test send from his account. Wire personalization: DeepSeek fills template variables from Brands table fields. Build the send function with retry logic (3 attempts, exponential backoff). Log every send to Outreach_Log.
Day 12-14
Nurture sequencer. APScheduler job: runs at 9am and 3pm daily. Queries Outreach_Log for records where response_status = delivered AND follow_up_date <= today AND touch_number < 4. For each: generate the appropriate follow-up (day-3 = check-in, day-7 = audience stat value-add, day-14 = "last touch"). Send, log, update follow_up_date to null after touch 4.
Day 12-14 cont.
Small batch test. Send to 10 brands across all 3 categories using both channels. Verify: Airtable log entries created correctly, brand status updated, follow-up dates set. Fix anything that doesn't look right before scaling.
Week 3

Analysis + Routing + Go-Live (Systems C + D)

Day 15-16
Zoom transcription pipeline. Set up watchdog on C:/TristanZoom/ (or equivalent). On new M4A/MP4 file: run Whisper medium, save transcript to .txt. Send transcript to Claude Sonnet with JSON extraction prompt. Parse JSON. Write to Deals table: brand_id (match by brand name), amount_discussed, objections_raised, next_action, next_action_date, zoom_transcript. If Zoom cloud API: add polling job (every 15 min) to check for new cloud recordings.
Day 16-17
Deal router. Python script: poll Airtable.Deals every hour for records where amount_discussed < 15000 AND status = negotiating AND routed_to_boxer = false. For each: query active boxers whose categories match the deal's brand category. Run Claude scoring: "Rank these boxers for this deal. Consider category fit and follower count." Top match gets the offer email via Gmail API. Create Boxer_Deals record. Set 48-hour expiry. If no acceptance after 48h: offer to next ranked boxer.
Day 17-18
Fulfillment tracker. On new Deals record with status = won: create a fulfillment checklist using the deal's deliverable type. APScheduler job (Monday 8am): query Fulfillment records due in next 7 days, compile summary, email to Tristan via Gmail API.
Day 18-19
End-to-end test. Simulate a full pipeline run using a test brand: add brand → run qualifier → set outreach_ready → trigger outreach → create fake Zoom file → watch transcription → confirm Deal record created → manually set amount < $15k + status = negotiating → confirm routing offer sent to a boxer → manually set status = won → confirm fulfillment checklist created and weekly reminder fires.
Day 19-21
Fix + Handoff. Fix any issues from the end-to-end test. Write the one-page handoff doc for Tristan: what the system does daily, what he needs to do (mark deals Won, drop Zoom files, check Airtable), who to contact if something breaks. Configure Windows Task Scheduler to restart all Python processes on system boot. Go-live.

Stage-by-Stage Technical Detail

Stage 01 (AI)

Find Prospects

Input
3 brand category definitions + Tristan's existing brand list from Google Sheet
Process
(1) Manual seed list: 30-50 brands per category from SponsorUnited, SportSponsor.com, competitor analysis.
(2) Claude expansion prompt: "Given this creator's categories [combat sports / equestrian / outdoor] and existing partners [list], what other brand types belong on this list? Return 20 specific brand names with Instagram handles."
(3) Zendriver: for each brand without a contact email, search LinkedIn company page, extract email from About section or contact info.
Output
Brands table populated with 90-150 brands, all at status = uncontacted
Libraries
pyairtable, openrouter SDK (or requests), zendriver
Gotcha: Instagram's Graph API won't surface brand accounts for discovery. Build the list from industry directories first. Use Zendriver on Instagram only to confirm handles and pull bio contact info for brands already identified.
Stage 02 (AI)

Qualify

Input
All brands with status = uncontacted
Process
For each brand, call Claude Sonnet via OpenRouter with:
System prompt: "You are scoring sponsorship prospects for Tristan Hamm, a combat sports / equestrian / outdoor creator with 2M followers. His audience is [demographics from discovery call]. Score each brand 1-10 on four dimensions."
User prompt: brand name, category, Instagram handle, any bio info.
Rubric (JSON output):
  • category_fit: How naturally does this brand fit combat sports, equestrian, or outdoor? (1 = zero fit, 10 = perfect match)
  • audience_alignment: How well does this brand's target demo match Tristan's audience? (1 = mismatch, 10 = ideal overlap)
  • deal_size_potential: Based on brand size and category, how large is a realistic deal? (1 = under $1k, 10 = over $50k)
  • brand_marketing_activity: Does this brand actively run influencer programs? (1 = no evidence, 10 = clearly active)
  • composite: Average of the four.
  • rationale: One sentence.
Write all scores to Airtable. Set status = outreach_ready where composite > 6.0.
Output
All four sub-scores + composite written to Brands table. Outreach-ready brands flagged.
Libraries
pyairtable, openrouter SDK
Gotcha: Include Tristan's actual audience demographics (age, gender split, location mix) in the system prompt. Without it Claude scores generically and the audience_alignment dimension is meaningless. Collect this from his Instagram Insights at the discovery call.
Stage 03 (AI)

Outreach

Input
Brands where status = outreach_ready and last_contacted is null or > 30 days ago
Process
(1) Pull brand from Airtable.
(2) DeepSeek generates personalized message: system prompt = voice guide, user prompt = "Write an outreach [DM / email] to {{brand_name}} in the {{category}} space. One specific brand detail to mention: {{brand_detail}}. Tristan tie-in: {{relevant_content}}."
(3) If Instagram DM channel: POST to Messenger API endpoint https://graph.facebook.com/v20.0/me/messages with recipient.id = brand's Instagram PSID.
(4) If email channel: Gmail API users.messages.send with HTML body.
(5) Log to Outreach_Log: channel, sent_at, message_preview, response_status = "sent", follow_up_date = today + 3.
Output
Messages sent, Outreach_Log entries created, Brands.status updated to "outreached", Brands.last_contacted = today
Libraries
requests (Messenger API), google-auth, googleapiclient (Gmail), openrouter (DeepSeek)
Rate limits
Instagram Messenger API: 1,000 unique recipients/day (at scale). Gmail: 500/day personal, 2,000/day Workspace. Week 1: max 20/day total. Ramp up.
Gotcha: Messenger API requires the brand's Instagram PSID (Page-Scoped User ID), not their handle. You get this by having them initiate contact OR by running a brand awareness campaign. Alternative for initial outreach: use the IG Business Discovery API to get their IG user ID, then send via Messenger API if they've interacted with Tristan's page before. Brands that haven't interacted can only receive DMs via Phantombuster (or manual) until the opt-in is established. Factor this into the fallback plan.
Stage 04 (AI)

Nurture

Input
Outreach_Log records where response_status = delivered AND follow_up_date <= today
Process
APScheduler job runs at 9am and 3pm daily.
Per record: check touch_number.
Touch 2 (day 3): "Hey [brand], just checking in, curious if this caught your eye."
Touch 3 (day 7): Value-add message including one specific audience stat (e.g., "My audience is 68% male, 25-34, active in combat sports weekly, sharing this in case it's useful for your targeting."). DeepSeek generates.
Touch 4 (day 14): "Last touch from me, happy to chat if timing's better down the road." Then sequence ends.
Send via same channel as touch 1. Log to Outreach_Log. Set follow_up_date = null after touch 4. Update brand status = cold if no reply after touch 4.
Output
Follow-up sent, Outreach_Log updated with new entry, follow_up_date set to next touch date (or null if sequence complete)
Libraries
apscheduler, same send infra as Stage 03
Gotcha: Query response_status before every send. If a brand replied (any status including "not interested"), they must NOT receive any nurture touch. Add this as a hard check before every send in the nurture loop. Also check do_not_contact flag.
Stage 05 (AI)

Zoom Intelligence

Input
Zoom recording file (M4A or MP4) dropped into C:/TristanZoom/ or downloaded from Zoom cloud API
Process
(1) Watchdog FileCreatedEvent fires on new file.
(2) Whisper transcription:
import whisper model = whisper.load_model("medium") result = model.transcribe(filepath) transcript = result["text"]
(3) Claude extraction prompt (JSON mode):
Extract from this sales call transcript: { "brand_name": "string", "contact_person": "string", "deal_amount_discussed": "number or null", "deal_structure": "string (e.g. flat fee, rev share)", "objections_raised": ["string"], "next_action": "string", "next_action_date": "YYYY-MM-DD or null", "sentiment": "positive | neutral | negative", "recommended_status": "prospect | negotiating | lost" }
(4) Match brand_name to existing Brands record (fuzzy match with thefuzz library).
(5) Write to Deals table: brand_id, amount_discussed, status = recommended_status, objections_raised, next_action, next_action_date, zoom_transcript (full text), zoom_recording_path.
Output
Deal record in Airtable, Brands.status updated to "deal_opened"
Libraries
watchdog, openai-whisper, openrouter (claude-sonnet-4-6 with JSON mode), pyairtable, thefuzz
Gotcha: Whisper "medium" needs ~2GB RAM, takes 5-8 min per 30-min call on CPU. Use "base" model for faster turnaround if needed (slightly lower accuracy). If Tristan has Zoom cloud recordings enabled, pull Zoom's own transcript via the Zoom API instead, it's already done and it's free.
Stage 06 (AI)

Deal Router

Input
Deals where amount_discussed < 15000 AND status = negotiating AND routed_to_boxer = false
Process
(1) Query active boxers from Boxer_Network where category overlaps with the deal's brand category.
(2) If multiple boxers match: rank by Claude scoring ("Rank these boxers for this deal. Consider audience fit for [brand category] and follower count.").
(3) Calculate split: split_amount = deal_amount × boxer.split_pct, boxer_earnings = deal_amount - split_amount.
(4) DeepSeek generates offer email to top-ranked boxer: deal summary, brand name, amount, deliverables expected, split breakdown, 48-hour response window.
(5) Send via Gmail API from Tristan's account (or a separate Verity sending address).
(6) Create Boxer_Deals record: deal_id, boxer_id, split_amount, boxer_earnings, status = "offered", offer_expires_at = now + 48h.
(7) Set Deals.routed_to_boxer = true.
(8) APScheduler checks every 4h for expired offers (offer_expires_at < now AND status = "offered"). If expired: mark "rejected", move to next ranked boxer.
NEEDConfirm with Tristan: auto-route or require approval before offer goes out?
Output
Boxer_Deals record created, offer email sent, split amounts calculated
Libraries
pyairtable, openrouter (for ranking + email gen), googleapiclient (Gmail)
Gotcha: Get Tristan's split percentage at discovery call. It may vary by deal type or boxer. If it varies, add a split_pct field per boxer OR a deal_type_overrides config dict. Don't assume flat rate.
Stage 08 (AI)

Fulfill

Input
Deals where status = won and no Fulfillment records yet created
Process
(1) On deal Won: create Fulfillment records from deal terms. Use pre-built templates by deliverable type:
  • Reel: due = signed_date + 14 days, requirements = brand guidelines from deal
  • Story set (3x): due = signed_date + 7 days
  • Post: due = signed_date + 10 days
  • Event appearance: due = event date from deal terms
(2) APScheduler Monday 8am job: query Fulfillment where due_date <= today + 7 AND status NOT IN (delivered, approved). Compile weekly summary. Send via Gmail API to Tristan (and his manager if applicable).
Output
Fulfillment records per deal, weekly deadline reminder email to Tristan
Libraries
apscheduler, pyairtable, googleapiclient (Gmail)
Gotcha: Fulfillment templates assume deal terms are captured somewhere structured. At discovery call: confirm whether Tristan tracks deliverable types in his current Sheet. If not, build a simple "deal type" field in the Deals table (single select: post_package, story_package, event, custom) that triggers the right template.

Instagram Safety Protocol

The line: Messenger API (official) for outreach, not browser automation. Account bans are existential risk for a 2M-follower creator.

Warm-Up Schedule Week 1: max 20 DMs/day. Week 2: max 40/day. Week 3+: up to 100/day. Never 1,000/day out of the gate regardless of what the API technically allows.
Personalization Floor Every message must include the brand name AND one specific brand detail (product line, recent campaign, something from their bio). No template blasts where only the name swaps.
30-Day Lock Never DM the same brand twice within 30 days. Hard check in code before every send: query Outreach_Log for brand_id + channel = instagram_dm + sent_at > 30 days ago.
Monitoring Check Meta Developer Console for API warnings weekly. Log all API response codes. Any 400-level errors on DMs: pause sends, investigate before resuming.
Opt-Out Handling Any reply containing "unsubscribe", "stop", "not interested", or "remove me": immediately set response_status = opted_out and do_not_contact = true in Brands table. Never contact again.
Fallback Protocol If Messenger API approval is suspended or delayed: Phantombuster at max 10 DMs/day while appealing. Never exceed this during a suspension. Appeal via Meta Developer Support, not forums.

On the PSID problem

The Messenger API requires a recipient's Page-Scoped User ID (PSID), not their Instagram handle. Brands that haven't interacted with Tristan's page don't have a PSID yet. Short-term: Phantombuster handles cold outreach (browser session DMs). Long-term: as brands reply and establish a thread, they get a PSID and move to the official API. Build a field in Brands (ig_psid) that gets populated when a brand first replies. Once populated, all future messages go through the API.


Testing + Go-Live Checklist

Each item must be manually verified before go-live. "The script ran without errors" is not verification.

Per-Stage Verification

  • Stage 01: 90+ brands in Airtable across all 3 categories. At least 30 per category. All have instagram_handle or contact_email (not both required, but one is required).
  • Stage 02: All brands have qualifier_score populated. At least 30 brands at outreach_ready status. Spot-check 5 scores manually against what you'd expect, if they look off, revisit the system prompt.
  • Stage 03: Send 10 test DMs + 10 test emails to real brands. Confirm all 20 show up in Outreach_Log with correct channel, sent_at, and follow_up_date. Confirm brand status updated to "outreached". Confirm at least one email lands in a real inbox (test with a brand you have a contact at).
  • Stage 04: Set a test brand's follow_up_date to today, run the scheduler manually. Confirm a follow-up message generates and sends. Confirm a new Outreach_Log entry is created. Confirm follow_up_date advances to next scheduled touch.
  • Stage 05: Drop a test audio file (even a 2-minute call recording from your phone) into the watch folder. Confirm: transcription runs, Claude extracts fields, Deal record appears in Airtable with brand_id correctly matched, all extracted fields populated.
  • Stage 06: Create a test Deal with amount = $10,000, status = negotiating, routed_to_boxer = false. Run the router. Confirm: Boxer_Deals record created, split_amount calculated correctly, offer email sent to boxer's email, Deals.routed_to_boxer = true.
  • Stage 08: Set a test Deal to status = won. Confirm: Fulfillment records created for each deliverable type. Run the Monday summary job manually. Confirm summary email arrives in Tristan's inbox.
  • Google Sheets sync: Run the gspread export manually. Open Tristan's Sheet. Confirm all three tabs updated with current Airtable data. Confirm nightly scheduled run fires (check next morning).
  • End-to-end run: One brand through the entire pipeline: uncontacted → outreach_ready → outreached → (simulate reply) responded → deal_opened → (fake Zoom file) Deal record created → amount < $15k → routed to boxer → (manually) won → Fulfillment checklist created → weekly reminder fires. All Airtable records correct at each step.
  • Handoff doc: One-pager delivered to Tristan: daily system behavior, his responsibilities (mark Won, drop Zoom files, check Airtable morning view), emergency contact if system breaks.
  • Process restart: Windows Task Scheduler configured for all Python processes. Simulate a reboot. Confirm all processes restart automatically.

NEED Flags: Confirm at Discovery Call

These block implementation decisions

  • NEEDIs Tristan's Instagram account a Business account (not personal, not Creator)? If not, the Messenger API path doesn't exist. Migration to Business account may be needed.
  • NEEDIs his Instagram Business account linked to a Facebook Page where he's the admin? Required for Meta app approval.
  • NEEDWhat is the current boxer network split percentage? Does it vary by boxer or deal type?
  • NEEDDeal routing: does Tristan want to approve each boxer assignment before the offer goes out, or is fully automatic routing acceptable?
  • NEEDZoom setup: paid plan or free? Cloud recordings enabled? Decision determines whether we use Zoom API or watchdog folder.
  • NEEDTristan's Instagram audience demographics (age, gender split, location top-5). Required for Stage 02 qualifier system prompt. Pull from his Instagram Insights at or before the discovery call.
  • NEEDWhat Gmail address does outreach go from? His personal Gmail, a team inbox, or a new Verity-managed address?
  • NEEDDoes Tristan want email summaries of daily activity, or will he check Airtable directly? This determines whether the daily digest feature gets built.
  • NEEDThe $15,000 routing threshold: confirmed or still a rough number? This is a hard-coded logic gate in Stage 06.