Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 27 additions & 7 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,21 @@ anything-command-center/
callback/ # OAuth callback for TikTok
outreach/
launch/ # Template-based draft generation
send/ # Server-side email send via Gmail API
approve/ # Approve/skip drafts (uses lifecycle module)
check-rate/ # GET — rate limit status per platform
auth/
gmail/ # Gmail OAuth flow
callback/ # Gmail OAuth callback
cron/
follow-ups/ # Daily Vercel Cron (9am UTC)
lib/
outreach-lifecycle.ts # Centralized status state machine
instagram.ts # Meta Graph API client (stub)
tiktok.ts # TikTok API client (stub)
playwright-dm.ts # Browser DM automation
rate-limiter.ts # Token bucket + circuit breaker
gmail.ts # mailto: URL generator
gmail.ts # Gmail API: sendEmailViaAPI(), token refresh, reply detection
supabase-server.ts # Server Supabase client
types.ts # Creator/OutreachLog interfaces
components/
Expand All @@ -52,13 +61,15 @@ anything-command-center/
sidebar.tsx # Navigation
packages/
db/ # Database migrations, seed scripts
migrations/ # SQL migration files
migrations/ # SQL migration files (001_initial, 002_outreach_automation)
seed/ # CSV import scripts
campaigns/
templates/ # Outreach message templates (Mustache-style)
scout/ # Python creator discovery scripts
scout/ # Creator discovery + automation scripts
dm-automator.ts # Playwright DM automation (local, persistent browser session)
data/ # JSON batch files from scouting runs
docs/ # Architecture, schema, and API setup docs
vercel.json # Cron schedule config (follow-up automation)
```

## Key Patterns
Expand All @@ -74,16 +85,21 @@ anything-command-center/
- **Creators Table**: Uses @tanstack/react-table with `VisibilityState` for column toggle (persisted in localStorage). Inline stage editing uses optimistic updates with rollback on failure. Row numbers, profile links, and 9 optional columns (email, region, sub_niche, archetype, specific_hook, engagement_signal, dm_status, source, notes) hidden by default.
- **Campaign Templates**: Branded for Anything Creator Program with archetype-aware variations (Mega/Macro vs Micro/Rising). Template variables include `{{name}}`, `{{handle}}`, `{{niche}}`, `{{specific_hook}}`, `{{content_idea}}`, `{{sender_name}}`, `{{product_name}}`, `{{product_url}}`, `{{program_name}}`, `{{value_prop}}`.
- **Rate Limiter**: Token bucket algorithm with per-platform daily caps, warm-up schedule (20/day → 100/day over 4 weeks), random delays (8–15s), and circuit breaker (>10% failure rate pauses sending).
- **Outreach Lifecycle**: Centralized state machine in `src/lib/outreach-lifecycle.ts`. Events (`draft_created`, `initial_sent`, `follow_up_1_sent`, `follow_up_2_sent`, `reply_detected`, `sequence_complete`) drive automatic `stage` and `dm_status` transitions on the creator record. The `/api/outreach/approve` route uses this module instead of hardcoded status logic.
- **Gmail API**: `src/lib/gmail.ts` provides `sendEmailViaAPI()` with RFC 2822 MIME formatting, automatic token refresh, and reply detection via Gmail readonly scope. Settings page shows Connect/Disconnect state. DraftCard calls the API when connected, falls back to `mailto:` when not.
- **Follow-Up Cron**: `/api/cron/follow-ups` runs daily at 9am UTC via Vercel Cron (configured in `vercel.json`). Checks for replies before generating follow-ups, auto-sends email follow-ups if Gmail API is connected, queues DM follow-ups as drafts for human review, and auto-completes sequences 7 days after FU2 with no reply.
- **DM Focus Mode**: The OutreachQueue "DM Focus" tab presents one DM at a time with an editable textarea, progress bar, rate limit badges (X/Y platform today, warm-up week N), and a confirm-before-sent dialog.
- **Playwright Automation**: Opens creator DM page in persistent browser session, pre-fills personalized message, human reviews and clicks Send. Never fully automated for cold outreach.
- **DM Automator** (`scout/dm-automator.ts`): Local Playwright script for Instagram/TikTok DMs. Fetches queue from API, respects rate limits, uses persistent browser session (login once, reuse cookies). Requires local execution — not deployed to Vercel.

## Database

- **Supabase project**: Configure via environment variables
- **Migration**: Run `packages/db/migrations/001_initial.sql` in the Supabase SQL editor
- **Migration**: Run `packages/db/migrations/001_initial.sql` and `002_outreach_automation.sql` in the Supabase SQL editor
- **Seed**: `cd packages/db && pnpm seed` (requires `.env` with `SUPABASE_URL` and `SUPABASE_SERVICE_ROLE_KEY`)
- **Tables**:
- `creators` — main CRM table (columns: `stage`, `score`, `followers` as INTEGER)
- `outreach_log` — message history
- `outreach_log` — message history (includes `scheduled_for`, `parent_outreach_id`, `gmail_message_id` columns from migration 002)
- `rate_limit_state` — per-platform rate limit tracking (token bucket state, circuit breaker)
- `oauth_tokens` — stored OAuth access/refresh tokens for connected platforms

Expand All @@ -94,7 +110,7 @@ anything-command-center/
| `/` | Main dashboard — creator table with filters, search, pipeline view |
| `/import` | Bulk import creators from CSV or JSON files |
| `/guide` | Setup guide and onboarding documentation |
| `/settings` | Platform connections (OAuth), rate limit config |
| `/settings` | Platform connections (OAuth for Instagram, TikTok, Gmail), rate limit config |
| `/analytics` | Pipeline funnel visualization, outreach stats |

## Testing
Expand All @@ -106,7 +122,9 @@ anything-command-center/

- Vercel auto-deploys from `main` branch
- Preview deploys on pull requests
- Environment variables must be set in Vercel project settings
- Environment variables must be set in Vercel project settings (including `CRON_SECRET` and Gmail OAuth vars)
- Vercel Cron configured in `vercel.json` for daily follow-up automation (9am UTC)
- Run `002_outreach_automation.sql` migration before deploying outreach features
- Live at: https://anything-command-center.vercel.app

## Campaign Templates
Expand All @@ -133,3 +151,5 @@ Located in `campaigns/templates/`:
| `META_PAGE_ID` | Facebook Page ID connected to Instagram Business account |
| `TIKTOK_CLIENT_KEY` | TikTok app Client Key (from developers.tiktok.com) |
| `TIKTOK_CLIENT_SECRET` | TikTok app Client Secret |
| `CRON_SECRET` | Secret token to authenticate Vercel Cron job requests |
| `GMAIL_REDIRECT_URI` | Gmail OAuth callback URL (`https://your-app.vercel.app/api/auth/gmail/callback`) |
72 changes: 67 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,15 +62,19 @@ cd ../.. && pnpm dev
| Column Visibility | Live | Show/hide optional columns (email, region, hook, DM status, etc.) with localStorage persistence |
| Profile Links | Live | Dedicated column with direct links to creator's social profile |
| Outreach Queue | Live | Draft, review, and send personalized messages |
| DM Focus Mode | Live | One-DM-at-a-time queue with editable textarea, progress bar, rate limit badges |
| Send DM Flow | Live | Copy message to clipboard + open creator's profile in new tab in one click |
| Campaign Templates | Live | Anything-branded cold DM, cold email, follow-up sequences with archetype-aware variations |
| CSV Import | Live | Upload CSV, map columns, preview, batch insert |
| Analytics | Live | Pipeline funnel, platform distribution, conversion metrics |
| Rate Limiter | Live | Per-platform caps, warm-up schedule, circuit breaker |
| OAuth Connections | Live | Connect Instagram (Meta) and TikTok accounts via Settings page |
| Outreach Lifecycle | Live | Centralized state machine for outreach status transitions (draft → sent → follow-ups → complete) |
| Gmail API | Live | OAuth connect/disconnect, RFC 2822 send, token refresh, reply detection |
| Follow-Up Automation | Live | Daily Vercel Cron job: reply detection, auto-send email follow-ups, queue DM follow-ups as drafts |
| OAuth Connections | Live | Connect Instagram (Meta), TikTok, and Gmail accounts via Settings page |
| Instagram DMs | Planned | Meta Graph API (warm) + browser automation (cold) |
| TikTok DMs | Planned | TikTok API (warm) + browser automation (cold) |
| Gmail | MVP | mailto: links with pre-filled subject/body |
| DM Automator | Planned | Local Playwright script for Instagram/TikTok DMs with persistent browser session |

## Pipeline Stages

Expand All @@ -93,6 +97,8 @@ Prospect → Contacted → Replied → Negotiating → Onboarded → Active
| `META_APP_SECRET` | Meta/Facebook app secret |
| `TIKTOK_CLIENT_KEY` | TikTok developer client key |
| `TIKTOK_CLIENT_SECRET` | TikTok developer client secret |
| `CRON_SECRET` | Secret token to authenticate Vercel Cron jobs |
| `GMAIL_REDIRECT_URI` | Gmail OAuth callback URL (e.g., `https://your-app.vercel.app/api/auth/gmail/callback`) |

## Project Structure

Expand All @@ -102,18 +108,32 @@ anything-command-center/
│ └── command-center/ # Next.js 16 dashboard
│ └── src/
│ ├── app/ # App Router pages + API routes
│ │ └── api/
│ │ ├── outreach/
│ │ │ ├── send/ # Server-side email send with rate limiter
│ │ │ └── check-rate/ # Rate limit status per platform
│ │ ├── auth/
│ │ │ └── gmail/ # Gmail OAuth flow + callback
│ │ └── cron/
│ │ └── follow-ups/ # Daily Vercel Cron (9am UTC)
│ ├── components/ # React components (shadcn/ui)
│ └── lib/ # Supabase, rate limiter, social APIs
│ └── lib/
│ ├── outreach-lifecycle.ts # Status state machine
│ ├── gmail.ts # Gmail API: send, token refresh, reply detection
│ ├── rate-limiter.ts # Token bucket + circuit breaker
│ └── supabase-server.ts # Server Supabase client
├── packages/
│ └── db/ # Database layer
│ ├── migrations/ # SQL schema (4 tables)
│ ├── migrations/ # SQL schema (001_initial + 002_outreach_automation)
│ └── seed/ # CSV + JSON import scripts
├── campaigns/
│ └── templates/ # Outreach message templates
├── scout/ # Python creator discovery pipeline
├── scout/ # Creator discovery + automation
│ ├── *.py # Discovery + verification scripts
│ ├── dm-automator.ts # Playwright DM automation (local, human-in-the-loop)
│ └── data/ # JSON batch outputs (800+ creators)
├── docs/ # Architecture docs
├── vercel.json # Cron schedule config
├── CLAUDE.md # AI assistant context
└── pnpm-workspace.yaml # Monorepo config
```
Expand All @@ -129,6 +149,11 @@ anything-command-center/
| GET | `/api/outreach` | List outreach queue by status |
| POST | `/api/outreach/launch` | Generate draft messages for selected creators |
| POST | `/api/outreach/approve` | Approve/skip outreach drafts |
| POST | `/api/outreach/send` | Send email via Gmail API (falls back to mailto:) |
| GET | `/api/outreach/check-rate` | Rate limit status per platform (X/Y today, warm-up week) |
| GET | `/api/auth/gmail` | Initiate Gmail OAuth flow |
| GET | `/api/auth/gmail/callback` | Gmail OAuth callback — stores tokens |
| POST | `/api/cron/follow-ups` | Daily follow-up cron (reply detection, auto-send, sequence completion) |
| POST | `/api/import` | Bulk import creators from CSV |
| GET | `/api/analytics` | Pipeline stage counts + conversion metrics |

Expand All @@ -143,6 +168,43 @@ anything-command-center/

Safety: Circuit breaker auto-pauses if >10% failure rate in last 25 sends (30-min pause).

## Outreach Automation

The outreach system is split between automated and human-in-the-loop actions:

**Automated (server-side):**
- Email sending via Gmail API (OAuth connected)
- Follow-up scheduling: daily Vercel Cron at 9am UTC (`/api/cron/follow-ups`)
- Reply detection via Gmail readonly scope before generating follow-ups
- Auto-send email follow-ups when Gmail API is connected
- Auto-complete sequences 7 days after FU2 with no reply

**Human-in-the-loop (manual review required):**
- DM follow-ups are queued as drafts for human review in the DM Focus tab
- Cold DMs require manual send (copy + open profile, or local DM automator)
- Confirm-before-sent dialog prevents accidental DM status marks

**Lifecycle State Machine** (`src/lib/outreach-lifecycle.ts`):
- Events: `draft_created` → `initial_sent` → `follow_up_1_sent` → `follow_up_2_sent` → `reply_detected` → `sequence_complete`
- Automatically advances both `stage` and `dm_status` on the creator record

## DM Automation Setup

The DM automator (`scout/dm-automator.ts`) runs locally via Playwright for Instagram/TikTok DMs:

1. Install dependencies: `cd scout && pnpm install`
2. Log into Instagram/TikTok in the Playwright browser session (one-time)
3. The script fetches the DM queue from `/api/outreach/check-rate`, respects rate limits
4. Uses persistent browser session (cookies reused across runs)
5. Human reviews each DM before confirming send

## Deployment Checklist

- Set all environment variables in Vercel (including `CRON_SECRET` and Gmail OAuth vars)
- Run `packages/db/migrations/002_outreach_automation.sql` in Supabase SQL editor
- Configure Gmail OAuth redirect URI to `https://your-app.vercel.app/api/auth/gmail/callback`
- Verify Vercel Cron is active (`vercel.json` includes the follow-up schedule)

## Deployment

- **Production**: Auto-deploys from `main` via Vercel
Expand Down
1 change: 1 addition & 0 deletions apps/command-center/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
},
"dependencies": {
"@radix-ui/react-accordion": "^1.2.12",
"@radix-ui/react-alert-dialog": "^1.1.15",
"@radix-ui/react-checkbox": "^1.3.3",
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-dropdown-menu": "^2.1.16",
Expand Down
41 changes: 30 additions & 11 deletions apps/command-center/src/app/(dashboard)/creators/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
import { PlatformBadge } from "@/components/platform-icon"
import { PipelineBadge } from "@/components/pipeline-badge"
import type { Creator, PipelineStage, OutreachLog, OutreachChannel } from "@/lib/types"
import { sendViaGmail } from "@/lib/gmail"
import { generateMailtoUrl } from "@/lib/gmail"
import {
ArrowLeft,
Rocket,
Expand Down Expand Up @@ -155,19 +155,38 @@ export default function CreatorDetailPage({

const handleApproveAndSend = async (log: OutreachLog) => {
if (!creator) return
if (log.channel === "email" && creator.email) {
const result = await sendViaGmail({
outreachId: log.id,
to: creator.email,
subject: log.subject || "",
body: log.content,
if (log.channel === "email") {
// Try server-side Gmail API send first
const sendRes = await fetch("/api/outreach/send", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ outreach_id: log.id }),
})
if (result.success) {
toast.success("Email opened in mail client and marked as sent!")

if (sendRes.ok) {
toast.success(`Email sent to ${creator.email} via Gmail API!`)
fetchCreator()
} else {
toast.error(result.error || "Failed to send")
return
}

// Fallback to mailto: if Gmail API not connected
if (creator.email) {
const url = generateMailtoUrl({
to: creator.email,
subject: log.subject || "",
body: log.content,
})
window.open(url, "_blank")
}

// Mark as sent via approve endpoint
await fetch("/api/outreach/approve", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ outreach_id: log.id, action: "send" }),
})
toast.success("Email opened in mail client and marked as sent!")
fetchCreator()
} else {
// For DMs, just mark as sent
try {
Expand Down
31 changes: 8 additions & 23 deletions apps/command-center/src/app/(dashboard)/settings/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -133,9 +133,13 @@ function SettingsPageInner() {
toast.success("Instagram connected successfully!")
} else if (connected === "tiktok") {
toast.success("TikTok connected successfully!")
} else if (connected === "gmail") {
toast.success("Gmail connected! Emails will now send via Gmail API.")
}

if (error === "instagram_not_configured") {
if (error === "gmail_not_configured") {
toast.error("Set up GOOGLE_CLIENT_ID in Vercel environment variables first")
} else if (error === "instagram_not_configured") {
toast.error("Set up META_APP_ID in Vercel environment variables first")
} else if (error === "tiktok_not_configured") {
toast.error("Set up TIKTOK_CLIENT_KEY in Vercel environment variables first")
Expand Down Expand Up @@ -267,27 +271,8 @@ function SettingsPageInner() {
)
}

// Gmail is always connected
if (platform === "gmail") {
return (
<div className="flex items-center justify-between rounded-lg border p-4">
<div className="flex items-center gap-3">
{icon}
<div>
<p className="text-sm font-medium">{label}</p>
<p className="text-xs text-muted-foreground">{description}</p>
</div>
</div>
<Badge
variant="outline"
className="border-emerald-500/50 text-emerald-500"
>
<CheckCircle2 className="mr-1 h-3 w-3" />
Connected
</Badge>
</div>
)
}
// Gmail: show OAuth connect/disconnect (same as other platforms)
// Falls through to the standard connected/not-connected logic below

const status = authStatus?.[platform]

Expand Down Expand Up @@ -420,7 +405,7 @@ function SettingsPageInner() {
"gmail",
<Mail className="h-5 w-5 text-red-400" />,
"Gmail",
"Send emails via mailto: protocol"
"Send emails via Gmail API (connect to automate sending)"
)}
{renderPlatformRow(
"instagram",
Expand Down
Loading
Loading