Skip to content

Lead Generation Pipeline

Turn X/Twitter into a lead generation machine.

The Lead Generation Framework

flowchart LR
    A[🔍 Discovery] --> B[📊 Qualification]
    B --> C[💬 Engagement]
    C --> D[🎯 Conversion]
    D --> E[📈 Nurture]

    style A fill:#4ECDC4
    style B fill:#45B7D1
    style C fill:#96CEB4
    style D fill:#FFEAA7
    style E fill:#DDA0DD

Intent-Based Lead Discovery

Find people actively looking for solutions:

import asyncio
from xeepy import Xeepy
from dataclasses import dataclass
from datetime import datetime, timedelta
from enum import Enum

class LeadIntent(Enum):
    HIGH = "high"      # Actively seeking solution
    MEDIUM = "medium"  # Has problem, exploring
    LOW = "low"        # General interest

@dataclass
class Lead:
    username: str
    name: str
    bio: str
    followers: int
    intent: LeadIntent
    intent_signals: list
    trigger_tweet: str
    score: float
    discovered_at: datetime

async def discover_high_intent_leads(
    problem_keywords: list,
    solution_keywords: list,
    competitor_mentions: list = None,
):
    """
    Find leads showing buying intent signals:
    - Asking "how to" questions
    - Expressing frustration with current solutions
    - Asking for recommendations
    - Mentioning competitors negatively
    """

    async with Xeepy() as x:
        leads = []

        # Intent signal patterns
        high_intent_patterns = [
            "looking for",
            "need help with",
            "anyone recommend",
            "what do you use for",
            "best tool for",
            "alternative to",
            "switching from",
            "frustrated with",
            "hate using",
        ]

        medium_intent_patterns = [
            "how do you",
            "how to",
            "tips for",
            "advice on",
            "struggling with",
        ]

        # Search for problem keywords
        for keyword in problem_keywords:
            tweets = await x.scrape.search(
                keyword,
                search_type="latest",
                limit=100,
                max_age_hours=48
            )

            for tweet in tweets:
                text_lower = tweet.text.lower()
                signals = []
                intent = LeadIntent.LOW

                # Check for high intent signals
                for pattern in high_intent_patterns:
                    if pattern in text_lower:
                        signals.append(f"high:{pattern}")
                        intent = LeadIntent.HIGH

                # Check for medium intent signals
                if intent != LeadIntent.HIGH:
                    for pattern in medium_intent_patterns:
                        if pattern in text_lower:
                            signals.append(f"medium:{pattern}")
                            intent = LeadIntent.MEDIUM

                # Check for competitor mentions
                if competitor_mentions:
                    for comp in competitor_mentions:
                        if comp.lower() in text_lower:
                            signals.append(f"competitor:{comp}")
                            if "problem" in text_lower or "issue" in text_lower:
                                intent = LeadIntent.HIGH

                if not signals:
                    continue

                # Score the lead
                score = calculate_lead_score(tweet.author, intent, signals)

                lead = Lead(
                    username=tweet.author.username,
                    name=tweet.author.name,
                    bio=tweet.author.bio or "",
                    followers=tweet.author.followers_count,
                    intent=intent,
                    intent_signals=signals,
                    trigger_tweet=tweet.text,
                    score=score,
                    discovered_at=datetime.now()
                )

                leads.append(lead)

        # Deduplicate and sort by score
        seen = set()
        unique_leads = []
        for lead in leads:
            if lead.username not in seen:
                seen.add(lead.username)
                unique_leads.append(lead)

        unique_leads.sort(key=lambda x: x.score, reverse=True)

        return unique_leads

def calculate_lead_score(author, intent: LeadIntent, signals: list) -> float:
    """Score leads 0-100 based on quality indicators"""
    score = 0

    # Intent score (40 points max)
    if intent == LeadIntent.HIGH:
        score += 40
    elif intent == LeadIntent.MEDIUM:
        score += 20
    else:
        score += 5

    # Signal count (20 points max)
    score += min(len(signals) * 5, 20)

    # Follower score (20 points max) - sweet spot is 500-10000
    followers = author.followers_count
    if 500 <= followers <= 10000:
        score += 20
    elif 100 <= followers <= 50000:
        score += 10
    elif followers > 50000:
        score += 5  # Too big, probably won't respond

    # Profile quality (20 points max)
    if author.bio:
        score += 10
    if author.has_profile_pic:
        score += 5
    if not author.is_default_profile:
        score += 5

    return score

# Usage
async def main():
    leads = await discover_high_intent_leads(
        problem_keywords=[
            "twitter automation",
            "social media scheduling",
            "grow twitter following",
        ],
        solution_keywords=[
            "automation tool",
            "scheduling app",
            "growth tool",
        ],
        competitor_mentions=[
            "tweepy",
            "buffer",
            "hootsuite",
        ]
    )

    print("🎯 HIGH-INTENT LEADS DISCOVERED")
    print("="*70)

    for lead in leads[:20]:
        print(f"\n@{lead.username} | Score: {lead.score:.0f} | Intent: {lead.intent.value}")
        print(f"   {lead.bio[:60]}..." if lead.bio else "   (no bio)")
        print(f"   Signals: {', '.join(lead.intent_signals)}")
        print(f"   Trigger: \"{lead.trigger_tweet[:100]}...\"")

asyncio.run(main())

Lead Qualification Pipeline

import asyncio
from xeepy import Xeepy

@dataclass
class QualifiedLead:
    lead: Lead
    qualification_score: float
    company_size: str  # startup, smb, enterprise
    decision_maker: bool
    budget_signals: list
    urgency: str  # low, medium, high
    fit_score: float

async def qualify_leads(leads: list, ideal_customer_profile: dict):
    """
    Qualify leads based on ICP matching:
    - Company size (from bio/job title)
    - Role/decision making authority
    - Budget indicators
    - Urgency signals
    """

    async with Xeepy() as x:
        qualified = []

        # Decision maker keywords
        dm_keywords = [
            "founder", "ceo", "cto", "vp", "head of",
            "director", "manager", "owner", "co-founder"
        ]

        # Budget indicators
        budget_signals_keywords = [
            "hiring", "growing", "funded", "series",
            "enterprise", "team of", "employees"
        ]

        for lead in leads:
            # Get full profile
            try:
                profile = await x.scrape.profile(lead.username)
                recent_tweets = await x.scrape.tweets(lead.username, limit=20)
            except:
                continue

            bio_lower = (profile.bio or "").lower()

            # Check decision maker status
            is_dm = any(kw in bio_lower for kw in dm_keywords)

            # Detect company size
            if any(kw in bio_lower for kw in ["enterprise", "fortune"]):
                company_size = "enterprise"
            elif any(kw in bio_lower for kw in ["startup", "founder", "indie"]):
                company_size = "startup"
            else:
                company_size = "smb"

            # Find budget signals in tweets
            budget_signals = []
            for tweet in recent_tweets:
                for kw in budget_signals_keywords:
                    if kw in tweet.text.lower():
                        budget_signals.append(kw)

            # Calculate urgency
            urgency_keywords = ["asap", "urgent", "need now", "immediately"]
            urgency = "high" if any(kw in lead.trigger_tweet.lower() for kw in urgency_keywords) else "medium"

            # Calculate fit score against ICP
            fit_score = calculate_icp_fit(
                lead, profile, company_size, is_dm, 
                ideal_customer_profile
            )

            # Overall qualification score
            qual_score = (
                lead.score * 0.4 +  # Intent
                fit_score * 0.4 +   # ICP fit
                (30 if is_dm else 0) +  # Decision maker bonus
                (10 if urgency == "high" else 0)  # Urgency bonus
            )

            qualified.append(QualifiedLead(
                lead=lead,
                qualification_score=qual_score,
                company_size=company_size,
                decision_maker=is_dm,
                budget_signals=list(set(budget_signals)),
                urgency=urgency,
                fit_score=fit_score
            ))

        # Sort by qualification score
        qualified.sort(key=lambda x: x.qualification_score, reverse=True)

        return qualified

def calculate_icp_fit(lead, profile, company_size, is_dm, icp: dict) -> float:
    """Score 0-100 based on ICP match"""
    score = 0

    # Company size match
    if company_size == icp.get("company_size"):
        score += 30
    elif company_size in icp.get("acceptable_sizes", []):
        score += 15

    # Industry match (from bio keywords)
    bio = (profile.bio or "").lower()
    for industry in icp.get("industries", []):
        if industry.lower() in bio:
            score += 20
            break

    # Decision maker preference
    if icp.get("decision_maker_required") and is_dm:
        score += 25
    elif is_dm:
        score += 15

    # Follower range
    min_f, max_f = icp.get("follower_range", (0, float("inf")))
    if min_f <= profile.followers_count <= max_f:
        score += 15

    # Negative signals
    for neg in icp.get("exclude_keywords", []):
        if neg.lower() in bio:
            score -= 30

    return max(0, min(100, score))

# Usage
async def main():
    leads = await discover_high_intent_leads(...)

    icp = {
        "company_size": "startup",
        "acceptable_sizes": ["smb"],
        "industries": ["saas", "tech", "software", "marketing"],
        "decision_maker_required": True,
        "follower_range": (500, 50000),
        "exclude_keywords": ["agency", "consultant", "freelance"]
    }

    qualified = await qualify_leads(leads, icp)

    print("🎯 QUALIFIED LEADS")
    print("="*70)

    for ql in qualified[:10]:
        print(f"\n@{ql.lead.username} | Qual Score: {ql.qualification_score:.0f}")
        print(f"   Size: {ql.company_size} | DM: {'✓' if ql.decision_maker else '✗'} | Urgency: {ql.urgency}")
        print(f"   Fit: {ql.fit_score:.0f} | Budget signals: {', '.join(ql.budget_signals) or 'none'}")

asyncio.run(main())

Automated Outreach Sequences

import asyncio
from xeepy import Xeepy
from xeepy.ai import ContentGenerator
from datetime import datetime, timedelta

class OutreachCampaign:
    """Automated multi-touch outreach campaign"""

    def __init__(self, x: Xeepy, ai: ContentGenerator):
        self.x = x
        self.ai = ai
        self.sequences = {}

    async def start_sequence(self, lead: QualifiedLead, campaign_id: str):
        """
        Start a multi-step outreach sequence:
        1. Day 0: Helpful reply to their tweet
        2. Day 1: Like 3 of their tweets
        3. Day 3: Follow them
        4. Day 5: Reply to another tweet
        5. Day 7: DM if they followed back
        """

        sequence = {
            "lead": lead,
            "campaign_id": campaign_id,
            "started_at": datetime.now(),
            "step": 0,
            "completed": False,
            "engaged": False,
        }

        self.sequences[lead.lead.username] = sequence

        return await self.execute_step(lead, 0)

    async def execute_step(self, lead: QualifiedLead, step: int):
        """Execute a single step in the sequence"""

        username = lead.lead.username

        if step == 0:
            # Helpful reply to trigger tweet
            reply = await self.ai.generate_reply(
                tweet_text=lead.lead.trigger_tweet,
                style="helpful",
                context=f"This person is looking for {lead.lead.intent_signals}",
                include_value=True,
                subtle_pitch=False,  # Not yet
            )

            # Find the original tweet
            tweets = await self.x.scrape.tweets(username, limit=20)
            trigger_tweet = None
            for t in tweets:
                if lead.lead.trigger_tweet[:50] in t.text:
                    trigger_tweet = t
                    break

            if trigger_tweet:
                await self.x.engage.reply(trigger_tweet.url, reply)
                print(f"Step 0: Replied to @{username}")
                return True

        elif step == 1:
            # Like 3 of their recent tweets
            tweets = await self.x.scrape.tweets(username, limit=10)
            liked = 0
            for tweet in tweets[:5]:
                if liked >= 3:
                    break
                await self.x.engage.like(tweet.url)
                liked += 1
                await asyncio.sleep(2)
            print(f"Step 1: Liked {liked} tweets from @{username}")
            return True

        elif step == 2:
            # Follow them
            await self.x.follow.user(username, source="lead_gen")
            print(f"Step 2: Followed @{username}")
            return True

        elif step == 3:
            # Another helpful reply
            tweets = await self.x.scrape.tweets(username, limit=10)

            # Find a tweet we haven't replied to
            for tweet in tweets:
                reply = await self.ai.generate_reply(
                    tweet_text=tweet.text,
                    style="conversational",
                    build_relationship=True,
                )
                await self.x.engage.reply(tweet.url, reply)
                print(f"Step 3: Second reply to @{username}")
                return True

        elif step == 4:
            # DM if they followed back
            profile = await self.x.scrape.profile(username)

            if profile.following_you:
                dm = await self.ai.generate_dm(
                    context=f"This person showed interest in: {lead.lead.intent_signals}",
                    bio=lead.lead.bio,
                    style="friendly",
                    offer_value=True,
                )
                await self.x.dm.send(dm, username)
                print(f"Step 4: DM sent to @{username}")
                self.sequences[username]["completed"] = True
                return True
            else:
                print(f"Step 4: @{username} didn't follow back, sequence ended")
                self.sequences[username]["completed"] = True
                return False

        return False

# Usage
async def run_outreach_campaign():
    async with Xeepy() as x:
        ai = ContentGenerator(provider="openai")
        campaign = OutreachCampaign(x, ai)

        # Get qualified leads
        leads = await discover_high_intent_leads(...)
        qualified = await qualify_leads(leads, icp)

        # Start sequences for top leads
        for lead in qualified[:10]:
            await campaign.start_sequence(lead, "q1_campaign")
            await asyncio.sleep(60)  # Space out initial contacts

        # Run daily to progress sequences
        while True:
            for username, seq in campaign.sequences.items():
                if seq["completed"]:
                    continue

                days_since_start = (datetime.now() - seq["started_at"]).days

                # Progress to next step based on timing
                step_timing = [0, 1, 3, 5, 7]  # Days for each step

                for step, day in enumerate(step_timing):
                    if days_since_start >= day and seq["step"] == step:
                        await campaign.execute_step(seq["lead"], step)
                        seq["step"] = step + 1
                        break

            await asyncio.sleep(3600)  # Check hourly

asyncio.run(run_outreach_campaign())

Lead Scoring Dashboard

from xeepy.storage import Database
from datetime import datetime

class LeadDatabase:
    """Track and score leads over time"""

    def __init__(self, db_path: str = "leads.db"):
        self.db = Database(db_path)
        self._init_tables()

    def _init_tables(self):
        self.db.execute("""
            CREATE TABLE IF NOT EXISTS leads (
                username TEXT PRIMARY KEY,
                name TEXT,
                bio TEXT,
                followers INTEGER,
                intent TEXT,
                intent_signals TEXT,
                trigger_tweet TEXT,
                initial_score REAL,
                current_score REAL,
                status TEXT DEFAULT 'new',
                discovered_at TIMESTAMP,
                last_contact TIMESTAMP,
                notes TEXT
            )
        """)

        self.db.execute("""
            CREATE TABLE IF NOT EXISTS interactions (
                id INTEGER PRIMARY KEY,
                username TEXT,
                type TEXT,
                content TEXT,
                response TEXT,
                timestamp TIMESTAMP,
                FOREIGN KEY (username) REFERENCES leads(username)
            )
        """)

    def add_lead(self, lead: Lead):
        self.db.execute("""
            INSERT OR REPLACE INTO leads 
            (username, name, bio, followers, intent, intent_signals, 
             trigger_tweet, initial_score, current_score, discovered_at)
            VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
        """, (
            lead.username, lead.name, lead.bio, lead.followers,
            lead.intent.value, ",".join(lead.intent_signals),
            lead.trigger_tweet, lead.score, lead.score, lead.discovered_at
        ))

    def log_interaction(self, username: str, type: str, content: str, response: str = None):
        self.db.execute("""
            INSERT INTO interactions (username, type, content, response, timestamp)
            VALUES (?, ?, ?, ?, ?)
        """, (username, type, content, response, datetime.now()))

        # Update score based on interaction
        self._update_score(username, type, response)

    def _update_score(self, username: str, interaction_type: str, response: str):
        """Adjust lead score based on interactions"""
        score_adjustments = {
            "reply_received": 20,
            "follow_back": 15,
            "like_received": 5,
            "dm_opened": 25,
            "dm_replied": 30,
            "meeting_scheduled": 50,
            "no_response": -5,
        }

        adjustment = score_adjustments.get(interaction_type, 0)

        self.db.execute("""
            UPDATE leads 
            SET current_score = current_score + ?,
                last_contact = ?
            WHERE username = ?
        """, (adjustment, datetime.now(), username))

    def get_hot_leads(self, limit: int = 20):
        """Get highest scoring leads"""
        return self.db.query("""
            SELECT * FROM leads 
            WHERE status != 'closed'
            ORDER BY current_score DESC
            LIMIT ?
        """, (limit,))

    def get_stale_leads(self, days: int = 7):
        """Get leads needing attention"""
        cutoff = datetime.now() - timedelta(days=days)
        return self.db.query("""
            SELECT * FROM leads
            WHERE status = 'active'
            AND last_contact < ?
            ORDER BY current_score DESC
        """, (cutoff,))

# Usage
db = LeadDatabase()

# Add leads
for lead in discovered_leads:
    db.add_lead(lead)

# Log interactions
db.log_interaction("username", "reply_sent", "Great question about...")
db.log_interaction("username", "reply_received", "Thanks for the help!")

# Get hot leads
hot = db.get_hot_leads(10)
print("🔥 Hot Leads:")
for lead in hot:
    print(f"  @{lead['username']}: Score {lead['current_score']}")

Export to CRM

import csv
import json
from datetime import datetime

async def export_leads_to_crm(leads: list, format: str = "csv"):
    """Export qualified leads to CRM-compatible format"""

    if format == "csv":
        with open("leads_export.csv", "w", newline="") as f:
            writer = csv.DictWriter(f, fieldnames=[
                "Username", "Name", "Bio", "Followers", "Intent",
                "Signals", "Score", "Company Size", "Decision Maker",
                "Trigger Tweet", "Twitter URL", "Discovered"
            ])
            writer.writeheader()

            for lead in leads:
                writer.writerow({
                    "Username": lead.lead.username,
                    "Name": lead.lead.name,
                    "Bio": lead.lead.bio,
                    "Followers": lead.lead.followers,
                    "Intent": lead.lead.intent.value,
                    "Signals": ", ".join(lead.lead.intent_signals),
                    "Score": lead.qualification_score,
                    "Company Size": lead.company_size,
                    "Decision Maker": "Yes" if lead.decision_maker else "No",
                    "Trigger Tweet": lead.lead.trigger_tweet[:200],
                    "Twitter URL": f"https://x.com/{lead.lead.username}",
                    "Discovered": lead.lead.discovered_at.isoformat()
                })

    elif format == "hubspot":
        # HubSpot-compatible JSON
        contacts = []
        for lead in leads:
            contacts.append({
                "email": f"{lead.lead.username}@twitter.placeholder",  # Placeholder
                "properties": {
                    "twitter_handle": lead.lead.username,
                    "firstname": lead.lead.name.split()[0] if lead.lead.name else "",
                    "lastname": " ".join(lead.lead.name.split()[1:]) if lead.lead.name else "",
                    "company": "",  # Would need enrichment
                    "lead_score": lead.qualification_score,
                    "lead_source": "Twitter Intent Discovery",
                    "notes": f"Intent: {lead.lead.intent.value}\nSignals: {', '.join(lead.lead.intent_signals)}"
                }
            })

        with open("leads_hubspot.json", "w") as f:
            json.dump(contacts, f, indent=2)

    print(f"✅ Exported {len(leads)} leads to {format} format")

Best Practices

Lead Gen Do's

  • ✅ Focus on intent signals, not just keywords
  • ✅ Provide value before pitching
  • ✅ Personalize every interaction
  • ✅ Track and score all interactions
  • ✅ Use multi-touch sequences

Lead Gen Don'ts

  • ❌ Spam DMs to cold leads
  • ❌ Pitch in your first interaction
  • ❌ Use generic templates
  • ❌ Ignore low-score leads entirely
  • ❌ Over-automate personalization

Next Steps

Competitor Intel - Find leads from competitor audiences

Brand Monitoring - Catch leads mentioning you

Crisis Detection - Turn complaints into opportunities