How to Build an Autonomous AI Cold Email Optimizer Using Claude, GitHub Actions & Instantly
Category: AI Tools · Automation · Email Marketing · Python
Skill Level: Intermediate–Advanced
Target Audience: Founders, developers, growth engineers, B2B marketers
Table of Contents
- What Is an Autonomous Email Optimizer?
- How the System Works: The 3-Phase Loop
- Tech Stack & Architecture
- The Science of Cold Email: Built-In Best Practices
- Project File Structure Explained
- Step-by-Step Setup Guide
- How Claude AI Generates Challenger Emails
- Deploying to GitHub Actions for Fully Autonomous Operation
- Safety Rules & Data Integrity
- Key Takeaways & Practical Insights
1. What Is an Autonomous Email Optimizer? {#what-is-it}
Most cold email campaigns are written once, sent, and never systematically improved. This project changes that entirely.
The Email Optimizer is an open-source Python project that runs a continuous, headless A/B testing loop on cold email copy — with no human intervention after initial setup. It is inspired by Andrej Karpathy’s “autoresearch” pattern, applying the same self-improving agent concept to outbound sales email.
Every 4 hours, the system automatically:
- Harvests reply rate data from experiments that have run for 48 hours
- Uses Claude AI (Anthropic’s LLM) to generate a mutated “challenger” version of the current best email
- Deploys both the baseline and challenger as live campaigns via the Instantly.ai API
- Commits all results back to the GitHub repository
Over time, the baseline email ratchets upward — only proven winners survive and get promoted. The result is a system that gets smarter with every cycle, autonomously discovering what copy resonates with your specific target audience.
2. How the System Works: The 3-Phase Loop {#how-it-works}
Each run of orchestrator.py executes three sequential phases:
Phase 1: HARVEST
- Scans all currently active experiments
- Identifies any experiment that has been running for 48 hours or longer
- Pulls reply rate statistics from the Instantly.ai API for both baseline and challenger campaigns
- Compares winner vs. loser: if the challenger has a higher reply rate and meets the minimum reply threshold, it is promoted to baseline
- Saves results to an append-only JSONL log (
results/results.log) and per-experiment folders underresults/experiments/
Phase 2: GENERATE
- Reads the current
config/baseline.md(the best-performing email so far) - Reads
data/resource.md(what you sell, who you target, your value proposition) - Optionally reads
data/cold-email-course.md(custom strategy notes and frameworks) - Sends all of this context to Claude via the Anthropic API
- Claude returns a fully formatted challenger config — a mutated version of the baseline with a new subject line, body, opening line, or CTA
Phase 3: DEPLOY
- Draws 250 fresh leads per arm from the local SQLite lead pool (
data/lead_pool.db) - Creates two new Instantly.ai campaigns: one for the baseline, one for the challenger
- Marks all assigned leads as
status = 'assigned'in the pool DB - Writes dedup records to
data/contacted.dbso no one is ever emailed twice - Updates
data/active_experiments.jsonwith the new experiment metadata
HARVEST → GENERATE → DEPLOY → (wait 4 hours) → HARVEST → ...
3. Tech Stack & Architecture {#tech-stack}
| Component | Tool / Service | Role |
|---|---|---|
| LLM / AI | Anthropic Claude (claude-sonnet) | Challenger email generation |
| Email platform | Instantly.ai API v2 | Campaign creation & analytics |
| Lead sourcing | Apify + SQLite | Pre-scraped lead pool management |
| Orchestration | Python 3.12 | Core logic (orchestrator.py) |
| Automation | GitHub Actions (cron) | Runs every 4 hours, headless |
| Notifications | Slack Webhooks | Optional run summaries |
| Data persistence | SQLite (2 databases) | Lead pool + dedup tracking |
| Config format | Markdown (.md files) | Human-readable, Claude-parseable |
Python dependencies (from requirements.txt):
anthropic>=0.40.0— Anthropic Python SDKapify-client>=1.8.0— Lead scraping clientrequests>=2.32.0— HTTP calls to Instantly APIpython-dotenv>=1.0.0— Secrets management via.env
4. The Science of Cold Email: Built-In Best Practices {#cold-email-science}
One of the most valuable parts of this project is its embedded cold email knowledge base (data/resource.md). These are the optimization levers it uses, ranked by impact:
Lead Quality (Highest Leverage)
- Decision-maker titles only: CEO, Founder, Owner, Managing Director reply significantly more than VP or Director roles at large companies
- Company size sweet spot: 5–50 employees — large enough to need your service, small enough that the founder reads their own email
- Industry targeting: Service businesses, agencies, and consulting firms reply 2–3× more than SaaS or ecommerce companies
- Avoid gatekeeping layers: At 500+ person companies, emails rarely reach the decision-maker
Subject Lines
- Lowercase beats Title Case in cold email open rates
- Keep it under 40 characters
- Personal and direct outperforms clever:
"quick question">"Revolutionize Your Business" - Question format adds approximately 15% open rate lift
- Avoid
"re:"tricks — they work short-term but damage domain reputation
Body Copy Rules
- First line must not be about you — open with something about the recipient (their company, their work, a relevant observation)
- Under 100 words total — brevity is a feature, not a limitation
- One CTA only — “quick call” or “worth a chat” (avoid “book a demo” language)
- No em dashes, no corporate jargon, no exclamation marks
- Line breaks after every 1–2 sentences for mobile readability
- Use
{{firstName}}and{{companyName}}merge tags
Deliverability
- Text-only or minimal HTML (no images, no heavy formatting)
- Disable link tracking and open tracking — both hurt inbox placement
- Maximum 125 emails per day per campaign
- Minimum 10-minute gap between sends
Experiment Control Variables (Never Mutated)
These settings are held constant across all experiments to ensure clean A/B test results:
- Email gap: 10 minutes
- Daily limit: 125 sends/campaign
- Steps: 1 (single initial email only — no follow-up sequences)
- Send schedule: 9am–5pm local time
- Tracking: disabled
5. Project File Structure Explained {#file-structure}
email-optimizer-demo/
│
├── orchestrator.py # Main 3-phase loop: harvest → generate → deploy
├── instantly_client.py # Wrapper for Instantly.ai API v2
├── deploy_batch.py # Deploy multiple experiments in one run
├── export_campaigns.py # Archive all campaigns to JSON + CSV
├── purge_old_leads.py # Free Instantly contact slots (export then delete)
├── test_parsers.py # Validate config parser logic
│
├── config/
│ ├── baseline.md # The current best email (the one being mutated)
│ └── challenger_preview.md # Auto-generated preview from --dry-run mode
│
├── data/
│ ├── resource.md # Product description + cold email strategy (read-only)
│ ├── cold-email-course.md # Optional: paste course notes or playbooks here
│ ├── active_experiments.json # Currently running experiment metadata
│ ├── lead_pool.db # SQLite: pre-scraped leads (not in git — use LFS)
│ └── contacted.db # SQLite: global dedup — nobody gets emailed twice
│
├── results/
│ ├── results.log # Append-only JSONL: all experiment history
│ └── experiments/ # Per-experiment records: copy, configs, results
│
├── .github/workflows/
│ └── optimize.yml # GitHub Actions cron: runs every 4 hours
│
├── .env.example # API key template
└── requirements.txt # Python dependencies
Two critical SQLite databases:
lead_pool.db— the “inbox” of available leads, drawn down per experiment with fields:email, first_name, last_name, company_name, job_title, industry, city, state, status, experiment_idcontacted.db— the dedup safeguard that ensures no contact is ever emailed twice, even across hundreds of experiments
6. Step-by-Step Setup Guide {#setup-guide}
Step 1: Clone and Install
git clone <your-repo-url>
cd email-optimizer-demo
pip install -r requirements.txt
cp .env.example .env
Step 2: Add API Keys to .env
INSTANTLY_API_KEY=your_instantly_api_v2_bearer_token # Required
APIFY_API_TOKEN=your_apify_api_token # Required
ANTHROPIC_API_KEY=your_anthropic_api_key # Required
WEBHOOK_URL=your_slack_webhook_url # Optional
Step 3: Define Your Product in data/resource.md
Fill in:
- What you offer (2–3 sentences)
- Who your ideal customer is (industry, company size, job titles)
- Your core value proposition
- Any social proof (revenue figures, client count, case studies)
Step 4: Write Your Baseline Email in config/baseline.md
The baseline config uses a structured Markdown + YAML format:
---
version: 1
last_updated: 2026-01-01
experiment_id: exp-2026-01-01
---
## Lead Filter
contact_job_title:
- "CEO"
- "Founder"
company_industry:
- "marketing"
size:
- "1-10"
- "11-50"
fetch_count: 250
## Email Sequence
### Step 1 (Day 0)
subject: quick question
body: |
Hey {{firstName}},
[Opening line about THEM — not you.]
[Your offer in under 50 words. Social proof if you have it.]
[Specific, low-commitment CTA — e.g., "How's a 15-min call Thursday?"]
Thanks,
- {{sendingAccountFirstName}}
Step 5: Build the Lead Pool
Run deploy_batch.py to populate data/lead_pool.db using your Apify scraper, or create it manually using the schema:
CREATE TABLE leads (
email TEXT,
first_name TEXT,
last_name TEXT,
company_name TEXT,
job_title TEXT,
industry TEXT,
city TEXT,
state TEXT,
status TEXT DEFAULT 'available',
experiment_id TEXT
);
Step 6: Test with Dry Run
python orchestrator.py --dry-run
This generates a challenger preview saved to config/challenger_preview.md without deploying any campaigns. Review it to verify Claude’s output quality.
Step 7: Push to GitHub and Enable Automation
git push origin main
Add these secrets to your GitHub repository (Settings → Secrets → Actions):
INSTANTLY_API_KEYAPIFY_API_TOKENANTHROPIC_API_KEYWEBHOOK_URL
Enable GitHub Actions and the optimize.yml workflow runs automatically at 02:00, 06:00, 10:00, 14:00, 18:00, and 22:00 UTC.
7. How Claude AI Generates Challenger Emails {#claude-ai-role}
This is the intellectual core of the system. During Phase 2 (GENERATE), the orchestrator builds a rich prompt for Claude that includes:
- The current baseline email (
config/baseline.md) — what Claude is mutating from - Product and audience context (
data/resource.md) — what you’re selling and to whom - Cold email strategy notes (
data/cold-email-course.md) — optional expert knowledge base - Previous experiment history from
results/results.log— what has worked and what hasn’t - A structured mutation strategy defining which levers to pull and in what priority order
Mutation Levers (Ranked by Impact)
| Priority | Lever | What Changes |
|---|---|---|
| 1 | Lead filter | Different audience segment entirely |
| 2 | Subject line | Different hook / angle |
| 3 | Opening line | Different personalization approach |
| 4 | CTA | Call vs. audit vs. case study |
| 5 | Body length | Shorter vs. longer |
| 6 | Tone | Casual vs. professional |
Early experiments (no history): Claude is instructed to go bold — change multiple categories at once to explore the search space quickly.
Later experiments (with history): Claude makes targeted, surgical changes based on patterns from winning experiments.
Claude returns a complete, valid baseline config in the same Markdown format — ready to deploy without any human editing.
8. Deploying to GitHub Actions for Fully Autonomous Operation {#github-actions}
The .github/workflows/optimize.yml file handles the entire automation:
name: Email Optimization
on:
schedule:
- cron: "0 2,6,10,14,18,22 * * *" # Every 4 hours
workflow_dispatch: # Manual trigger available
jobs:
optimize:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
lfs: true
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- run: pip install -r requirements.txt
- run: python orchestrator.py
env:
INSTANTLY_API_KEY: ${{ secrets.INSTANTLY_API_KEY }}
APIFY_API_TOKEN: ${{ secrets.APIFY_API_TOKEN }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
WEBHOOK_URL: ${{ secrets.WEBHOOK_URL }}
- name: Commit results
run: |
git config user.name "email-optimizer[bot]"
git add config/baseline.md data/active_experiments.json \
results/results.log results/experiments/ \
data/contacted.db data/lead_pool.db
git diff --staged --quiet || git commit -m "exp $(date +%Y-%m-%d-%H): auto-optimize"
git push
Key design decisions:
- Results, the promoted baseline, and the dedup database are all committed back to the repo after every run — the git history is your full experiment audit trail
timeout-minutes: 30prevents runaway jobs from consuming GitHub Actions minutesworkflow_dispatchallows manual runs for testing without waiting for the cron schedule- Git LFS (
lfs: true) is required becauselead_pool.dbcan grow large
9. Safety Rules & Data Integrity {#safety-rules}
The project enforces several explicit safety rules that are worth understanding — especially before making any manual changes:
| Rule | Why It Matters |
|---|---|
| Never delete Instantly campaigns without explicit confirmation | The API can return stale analytics; that doesn’t mean a campaign is broken |
Never overwrite active_experiments.json, results.log, or contacted.db | These are irreplaceable experiment state — overwriting them corrupts your history and dedup protection |
| Never pause active campaigns without a validated reason | Pausing mid-experiment invalidates the 48-hour comparison window |
| Always use the dedup DB | contacted.db is the system-wide guarantee that no one receives duplicate emails across all experiments |
10. Key Takeaways & Practical Insights {#takeaways}
For Developers
- The project is a clean example of an agentic loop with a stateful feedback mechanism — each run reads prior results and uses them to inform the next generation step
- Using Markdown files as the “config format” is a deliberate choice: it keeps configs human-readable and trivially parseable by LLMs without any special schema
- SQLite is used for both the lead pool and dedup tracking — a pragmatic choice that requires zero infrastructure beyond the repo itself
- The system decouples lead scraping (offline, bulk) from campaign deployment (online, per-run) to eliminate a common point of failure in outbound automation
For Marketers & Founders
- The 250 leads per arm / 125 per day / 48-hour window is a deliberately calibrated setup that balances statistical signal with experiment velocity
- The single-step (no follow-up) constraint is intentional: follow-ups dilute measurement and make it harder to isolate which variable is driving results
- The system embeds a full cold email best-practice framework as operating knowledge for Claude — meaning the AI challenger generator is constrained by real-world email deliverability and conversion principles, not just arbitrary text mutations
- Slack notifications give you a passive feed of what’s winning without requiring you to log in anywhere
SEO & Content Takeaway for Technowild.com Readers
This project demonstrates a broader pattern that is increasingly relevant across digital marketing:
AI + APIs + CI/CD = self-optimizing systems that improve continuously without human bottlenecks
The same architecture — a feedback loop where an LLM reads historical performance data and generates an improved variant for autonomous deployment — can be applied to landing page copy, ad creative, SEO meta descriptions, push notification text, and more. The email optimizer is a working blueprint for that class of system.
Further Resources
- Instantly.ai API v2 Documentation
- Anthropic Claude API Reference
- Apify Platform for Web Scraping
- GitHub Actions Documentation
- SQLite Python Documentation
This article was produced for Technowild.com based on a technical analysis of the email-optimizer-demo open-source project. All code references reflect the project files as analyzed.




