AI Learning Hub Roadmap Resources Blog Projects Paths
Advanced 8–10 hours · Python, OpenAI API, SQLite, FastAPI, requests

Build an AI Sales Agent: Automated Lead Qualification and Outreach

· 5 min read · AI Learning Hub

Project Overview

An AI sales agent that pulls leads from a database, researches companies, scores lead quality, writes personalized outreach emails, tracks engagement, and generates pipeline reports — automating the research and writing portions of the sales workflow.


Learning Goals

  • Build an agentic loop with external tool calls
  • Design multi-step AI workflows with state management
  • Use function calling for structured data operations
  • Build a complete business workflow with AI

Architecture

Lead database (SQLite)
        ↓
Lead qualification agent
  → research company + ICP scoring
        ↓
Outreach agent
  → personalized email generation
        ↓
Follow-up agent
  → response classification + next action
        ↓
Pipeline report generator

Implementation

Step 1: Setup

pip install openai requests fastapi uvicorn sqlite-utils pydantic

Step 2: Lead Database

# database.py
import sqlite3
import json
from datetime import datetime
from contextlib import contextmanager

DB_PATH = "sales.db"


def init_db():
    with get_conn() as conn:
        conn.executescript("""
            CREATE TABLE IF NOT EXISTS leads (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                name TEXT NOT NULL,
                email TEXT,
                company TEXT NOT NULL,
                title TEXT,
                website TEXT,
                industry TEXT,
                company_size TEXT,
                score INTEGER DEFAULT 0,
                qualification_notes TEXT,
                status TEXT DEFAULT 'new',
                created_at TEXT DEFAULT CURRENT_TIMESTAMP
            );
            CREATE TABLE IF NOT EXISTS outreach (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                lead_id INTEGER,
                email_subject TEXT,
                email_body TEXT,
                sent_at TEXT,
                opened_at TEXT,
                replied_at TEXT,
                reply_text TEXT,
                reply_sentiment TEXT,
                next_action TEXT,
                FOREIGN KEY (lead_id) REFERENCES leads(id)
            );
        """)


@contextmanager
def get_conn():
    conn = sqlite3.connect(DB_PATH)
    conn.row_factory = sqlite3.Row
    try:
        yield conn
        conn.commit()
    finally:
        conn.close()


def add_lead(name: str, company: str, email: str = "", title: str = "",
             website: str = "", industry: str = "", company_size: str = "") -> int:
    with get_conn() as conn:
        cursor = conn.execute(
            "INSERT INTO leads (name, company, email, title, website, industry, company_size) VALUES (?,?,?,?,?,?,?)",
            (name, company, email, title, website, industry, company_size)
        )
        return cursor.lastrowid


def get_leads(status: str = "new", limit: int = 20) -> list[dict]:
    with get_conn() as conn:
        rows = conn.execute(
            "SELECT * FROM leads WHERE status=? ORDER BY score DESC LIMIT ?",
            (status, limit)
        ).fetchall()
        return [dict(r) for r in rows]


def update_lead(lead_id: int, **kwargs):
    if not kwargs:
        return
    sets = ", ".join(f"{k}=?" for k in kwargs)
    values = list(kwargs.values()) + [lead_id]
    with get_conn() as conn:
        conn.execute(f"UPDATE leads SET {sets} WHERE id=?", values)


def save_outreach(lead_id: int, subject: str, body: str) -> int:
    with get_conn() as conn:
        cursor = conn.execute(
            "INSERT INTO outreach (lead_id, email_subject, email_body, sent_at) VALUES (?,?,?,?)",
            (lead_id, subject, body, datetime.now().isoformat())
        )
        return cursor.lastrowid


init_db()

Step 3: Company Research Tool

# research.py
import requests
from openai import OpenAI

client = OpenAI()

RESEARCH_PROMPT = """Research this company and return a sales intelligence brief.

Company: {company}
Website: {website}
Industry: {industry}

Return JSON:
{{
  "company_summary": "2-3 sentences about what the company does",
  "key_pain_points": ["pain point 1", "pain point 2"],
  "relevant_use_cases": ["how our product could help them"],
  "talking_points": ["specific personalization hook 1", "hook 2"],
  "icp_score": <0-100>,
  "icp_rationale": "why this score"
}}

Assume we're selling an AI development platform for engineering teams."""


def research_company(company: str, website: str = "", industry: str = "") -> dict:
    import json
    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": RESEARCH_PROMPT.format(
            company=company, website=website or "unknown", industry=industry or "unknown"
        )}],
        response_format={"type": "json_object"},
        temperature=0.3,
    )
    return json.loads(response.choices[0].message.content)

Step 4: Qualification Agent

# qualification_agent.py
from research import research_company
from database import get_leads, update_lead

ICP_CRITERIA = """Ideal Customer Profile:
- Engineering teams of 10-500 people
- Building AI/ML features or products
- Tech companies or AI-forward enterprises
- Budget: $10k-$100k/year"""


def qualify_leads(batch_size: int = 10):
    """Qualify new leads and update their scores."""
    leads = get_leads("new", batch_size)
    print(f"Qualifying {len(leads)} leads...")

    for lead in leads:
        print(f"  Researching {lead['company']}...")
        research = research_company(
            lead["company"],
            lead.get("website", ""),
            lead.get("industry", ""),
        )

        score = research.get("icp_score", 0)
        notes = f"Score: {score}/100\n{research.get('icp_rationale', '')}"

        update_lead(
            lead["id"],
            score=score,
            qualification_notes=notes,
            status="qualified" if score >= 60 else "disqualified",
        )
        print(f"    → Score: {score} | Status: {'qualified' if score >= 60 else 'disqualified'}")

    return len(leads)

Step 5: Outreach Agent

# outreach_agent.py
from openai import OpenAI
from research import research_company
from database import get_leads, save_outreach, update_lead

client = OpenAI()

EMAIL_PROMPT = """Write a personalized cold outreach email for a sales representative.

Prospect: {name}, {title} at {company}
Company research: {research}

Rules:
- Subject line: specific, relevant, 8 words max
- Opening: reference something specific about their company
- Value prop: 2 sentences on how we help teams like theirs
- CTA: one specific ask (15-min call, demo, etc.)
- Total length: 5-7 sentences MAX
- Tone: conversational, not salesy

Return JSON:
{{
  "subject": "email subject line",
  "body": "full email body (plain text)"
}}"""


def generate_outreach(lead: dict, research: dict) -> dict:
    import json
    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": EMAIL_PROMPT.format(
            name=lead["name"],
            title=lead.get("title", ""),
            company=lead["company"],
            research=str(research),
        )}],
        response_format={"type": "json_object"},
        temperature=0.7,
    )
    return json.loads(response.choices[0].message.content)


def run_outreach_batch(batch_size: int = 5, dry_run: bool = True):
    """Generate outreach emails for qualified leads."""
    leads = get_leads("qualified", batch_size)
    print(f"Generating outreach for {len(leads)} leads (dry_run={dry_run})...")

    emails = []
    for lead in leads:
        research = research_company(lead["company"], lead.get("website", ""), lead.get("industry", ""))
        email = generate_outreach(lead, research)
        outreach_id = save_outreach(lead["id"], email["subject"], email["body"])

        if not dry_run:
            # In production: send via SendGrid/SES here
            update_lead(lead["id"], status="contacted")
            print(f"  ✓ Sent to {lead['email']}: {email['subject']}")
        else:
            print(f"\n--- Lead: {lead['name']} ({lead['company']}) ---")
            print(f"Subject: {email['subject']}")
            print(f"Body:\n{email['body']}")

        emails.append({"lead": lead, "email": email, "outreach_id": outreach_id})

    return emails

Step 6: Pipeline Report

# pipeline.py
from database import get_conn

def generate_pipeline_report() -> str:
    with get_conn() as conn:
        stats = conn.execute("""
            SELECT
                status,
                COUNT(*) as count,
                AVG(score) as avg_score
            FROM leads GROUP BY status
        """).fetchall()

        recent_outreach = conn.execute("""
            SELECT l.name, l.company, o.email_subject, o.sent_at
            FROM outreach o JOIN leads l ON o.lead_id = l.id
            ORDER BY o.sent_at DESC LIMIT 10
        """).fetchall()

    lines = ["# Sales Pipeline Report", ""]
    lines.append("## Lead Status Summary")
    for row in stats:
        lines.append(f"- **{row['status'].title()}**: {row['count']} leads (avg score: {row['avg_score'] or 0:.0f})")

    lines.append("\n## Recent Outreach")
    for row in recent_outreach:
        lines.append(f"- {row['name']} ({row['company']}): *{row['email_subject']}* — {row['sent_at'][:10]}")

    return "\n".join(lines)

Step 7: Run

# Add sample leads
python -c "
from database import add_lead
add_lead('Jane Smith', 'Acme AI', 'jane@acme.ai', 'VP Engineering', 'https://acme.ai', 'AI Software', '50-200')
add_lead('Bob Chen', 'DataFlow Inc', 'bob@dataflow.io', 'CTO', 'https://dataflow.io', 'Data Analytics', '10-50')
"

# Qualify leads
python -c "from qualification_agent import qualify_leads; qualify_leads()"

# Generate outreach (dry run)
python -c "from outreach_agent import run_outreach_batch; run_outreach_batch(dry_run=True)"

# Generate report
python -c "from pipeline import generate_pipeline_report; print(generate_pipeline_report())"

Extension Ideas

  1. CRM sync — integrate with HubSpot or Salesforce APIs
  2. Email sending — connect SendGrid for actual email delivery + tracking
  3. Reply handling — classify inbound replies and suggest next actions
  4. A/B testing — generate multiple subject lines and track open rates
  5. Meeting scheduler — auto-schedule demos using Calendly API for interested leads

What to Learn Next

← Back to all projects