Spiral: what this is, in plain English
A beginner-friendly tour. If you've never used Claude Code or Claude Skills, start here.
1. What's a Claude Skill?
A Claude Skill is a folder with a SKILL.md instruction file plus optional Python scripts and reference docs. You invoke it inside Claude Code by typing /skill-name (e.g. /spiral-ingest-leads). When invoked, Claude reads the SKILL.md, follows the steps, runs the scripts, and reports back.
Think of a skill as a runbook Claude can execute — but the runbook can also call APIs, parse files, and decide things on its own. Claude is the orchestrator; the Python scripts are the deterministic muscle.
Skills live in ~/.claude/skills/<skill-name>/. The folder must contain SKILL.md with a YAML frontmatter block at the top defining the name, description, and what tools Claude is allowed to use.
2. What is Spiral?
Spiral is a cold-email pipeline for Spiral Studios. It automates the parts that are repetitive and error-prone, while keeping you (the human) in control of the moments that matter (which leads to target, which drafts to send, which replies to approve).
The system orchestrates four external services:
| Service | Role |
|---|---|
| Apollo | Lead database — gives you contact info, company facts, intent signals |
| Google Sheets | The master DB. Every lead, reply, and decision lives here |
| Instantly | Sends the cold emails and tracks replies |
| HubSpot | Where your client reviews and approves draft replies |
And Claude itself does the creative work: writing personalized email copy and replying to leads in your voice.
3. The pipeline at a glance
Four skills, each invoked when needed. The yellow nodes are the human-judgment moments (sending cold emails, approving reply drafts). The system stops there. No surprises, no auto-blast.
4. The four skills, one line each
/spiral-ingest-leads
Reads apollo_to_spreadsheet.csv from spiral-shared/inbox/, parses it, dedupes against existing rows, and appends new leads to the Google Sheet with processing_status=pending.
/spiral-weekly-enrichment
Picks the next batch of pending leads. For each one:
- Calls Apollo to refresh company facts (industry, funding, hiring, intent)
- Scrapes the company's homepage for positioning + tone
- Does a quick web search for activity in the last ~7 days
- Writes the full set of per-touch Instantly variables (
opener,bridge,proof,asset_line,fu1_line–fu3_line,breakup_line,cta,cta_soft,subject_t1–subject_t5) tuned to the lead's specific context, following the voice guide and email templates - Pushes the lead as a draft in the matching Instantly campaign
You then review and send the drafts manually from the Instantly UI.
/spiral-daily-replies
Pulls new replies from Instantly. For each:
- Classifies via Instantly's label (Interested / Meeting Booked / Neutral / OOO / Not Interested / Wrong Person)
- For Interested + Meeting Booked + Neutral, drafts a personalized reply
- Posts every draft to HubSpot for your client to approve
Replies labeled OOO, Not Interested, or Wrong Person are just logged — no draft.
/spiral-sync-approvals
Fetches all HubSpot records where the client has set approval_status=approved. For each, sends the approved reply through Instantly (so it threads natively in the inbox), logs it, and deletes the HubSpot record so the client's view only ever shows pending items.
5. How you actually use it (the rhythm)
Setup (one time)
- Create
.envfrom.env.example, fill in API keys - Drop
google-service-account.jsoninspiral-shared/ pip install -r spiral-shared/scripts/requirements.txt- Run
python spiral-shared/scripts/healthcheck.py— all green - Run
python spiral-shared/scripts/bootstrap_sheet.py— creates Sheet tabs - Run
python spiral-shared/scripts/bootstrap_hubspot.py— creates the HubSpot object - Fill in the real Instantly campaign UUIDs in
references/campaigns.json
For step-by-step setup see runbook.md.
Monday morning (lead acquisition)
- In Apollo, tag your search contacts with two lists:
icp_type:dream(or normal/lookalike) andtier:T1(or T2/T3 — funding-based, seesheet_schema.md) - Export CSV → save as
apollo_to_spreadsheet.csvinspiral-shared/inbox/ - In Claude Code:
/spiral-ingest-leads - Then:
/spiral-weekly-enrichment - Open Instantly → review drafts → send
Tue–Fri morning
/spiral-daily-replies— drafts get pushed to HubSpot- Your client reviews + approves drafts in HubSpot whenever
- You run
/spiral-sync-approvals(or it can run on a schedule)
6. Why this design
Sheets is canonical. Every lead, every state, every timestamp lives in Google Sheets. HubSpot is temporary (records appear only when client approval is needed). Instantly is the sender. If two systems disagree, Sheets wins (except when it's about what was actually mailed — there Instantly wins).
Idempotent everywhere. Every skill checks state before acting. If a skill crashes mid-run, just run it again — it picks up where it left off. Safe to over-run, never under-run.
Human gates on the dangerous things. Spiral never auto-sends a cold email and never auto-sends a reply without client approval. Both moments require explicit human action through a UI.
Claude inside Claude Code. All creative writing (email variables, reply drafts) happens through the Claude that's running the skill — no separate Anthropic API key, no background workers, no daemons. When your Claude Code session is idle, the system is paused.
Sheet columns match Instantly column names. The Leads tab uses Instantly's exact field names for everything that gets pushed to Instantly (firstName, companyName, icp_type, tier, activity, cadence, signal_fact, vertical, matched_case, case_link, plus the email vars opener, bridge, proof, asset_line, fu1_line–fu3_line, breakup_line, cta, cta_soft, subject_t1–subject_t5). Apollo enrichment fields and Spiral pipeline state stay in snake_case. Routing goes by {icp_type_lowercased}_{tier} → Instantly campaign UUID. 5 valid combinations (C1–C5): Dream/T1, Normal/T2, Normal/T3, Lookalike/T2, Lookalike/T3. Dream is T1-only; Normal and Lookalike skip T1. enrich_one.py clamps invalid combos automatically.
6.5. The voice guide is non-negotiable
Cold email is where AI tells become obvious. The voice guide (voice_guide.md) is loaded into Claude on every drafting run with hard rules:
- No em-dashes (—). Use periods or commas instead. They're the #1 AI tell.
- No startup-speak. Buzzword blocklist includes "inflection", "GTM motion", "playbook", "design-partner mapping", "unlock", "compound", "ecosystem", "leverage", "synergy", and more.
- No price quotes, no date commitments, no "guarantee", no negative competitor mentions.
The email templates have been written to match. If you notice Claude reaching for a phrase you don't like, add it to the avoid list — the next drafting run will respect it.
6.75. Case study setup
The proof line in every email references a Spiral Studios case study. Today that's a constant:
matched_case=Aalo Atomicscase_link=https://www.spiralstudios.io/work/aalo
When you add more case studies, set up a vertical → matched_case mapping table in email_templates.md and the enrichment flow will pick the right one per lead. case_link is its own Instantly column — don't paste the URL inside the proof text.
7. Where to go next
| If you want to… | Read… |
|---|---|
| Run the system day-to-day | runbook.md (the operator playbook) |
| Understand the data model + state machine | architecture.md |
| Debug a specific error | error_handling.md |
| Know what's in each Sheet column | sheet_schema.md |
| Tune Claude's writing tone | voice_guide.md and spiral-weekly-enrichment/references/email_templates.md |
| Add a new campaign | campaigns.json |