How I Automated LinkedIn Prospecting with AI Agents
The Problem
I was spending 2-3 hours every day on LinkedIn prospecting. Manual tasks included:
- Searching for prospects using filters
- Checking each profile for fit
- Researching company backgrounds
- Writing personalized connection requests
- Managing follow-ups
At 3 PM, my messages started sounding generic. My 9 AM messages were great. By late afternoon, I was just copying templates and changing names.
I calculated: 2.5 hours daily x 22 workdays = 55 hours monthly. That’s more than a full work week lost to repetitive tasks.
First Attempt: Simple Automation
I tried using PhantomBuster to send automated connection requests. The result?
Day 1: Sent 50 connection requestsDay 2: 3 accepted, 47 ignoredDay 3: LinkedIn restricted my account for "suspicious activity"My messages were generic. People could tell they were automated. The response rate was terrible.
Second Attempt: AI-Powered Personalization
I decided to build an AI agent that would:
- Research each prospect thoroughly
- Write genuinely personalized messages
- Queue everything for my morning review
- Run overnight while I slept
Here’s the architecture I designed:
Prospect Discovery -> Profile Analysis -> Context Gathering -> Message Drafting -> Human Review QueueBuilding the Agent with LangGraph
I chose LangGraph for its graph-based workflow. Here’s my initial implementation:
from langgraph.graph import StateGraph, ENDfrom typing import TypedDict, Listimport anthropic
class ProspectState(TypedDict): profile_url: str name: str company: str title: str recent_posts: List[str] company_news: List[str] draft_message: str approved: bool
def analyze_profile(state: ProspectState) -> ProspectState: """Extract key information from LinkedIn profile""" profile_data = scrape_linkedin_profile(state["profile_url"]) return {**state, **profile_data}
def gather_context(state: ProspectState) -> ProspectState: """Enrich with company and industry context""" company_news = search_recent_news(state["company"]) recent_posts = get_linkedin_posts(state["profile_url"]) return {**state, "company_news": company_news, "recent_posts": recent_posts}
def draft_message(state: ProspectState) -> ProspectState: """Generate personalized outreach message""" client = anthropic.Anthropic()
prompt = f"""Write a LinkedIn connection request message for:
Name: {state["name"]}Title: {state["title"]}Company: {state["company"]}
Recent posts: {state["recent_posts"][:2]}Company news: {state["company_news"][:1]}
Guidelines:- Keep under 300 characters- Reference specific context (post or news)- Be conversational, not salesy- Ask a question to encourage response- No generic "I'd love to connect" openings
Write only the message text."""
response = client.messages.create( model="claude-3-5-sonnet-20241022", max_tokens=200, messages=[{"role": "user", "content": prompt}] )
return {**state, "draft_message": response.content[0].text}
# Build the graphworkflow = StateGraph(ProspectState)workflow.add_node("analyze_profile", analyze_profile)workflow.add_node("gather_context", gather_context)workflow.add_node("draft_message", draft_message)
workflow.set_entry_point("analyze_profile")workflow.add_edge("analyze_profile", "gather_context")workflow.add_edge("gather_context", "draft_message")workflow.add_edge("draft_message", END)
agent = workflow.compile()The Rate Limiting Problem
When I ran this overnight, I hit LinkedIn’s rate limits:
ERROR: HTTP 429 - Too Many RequestsFailed after processing 15 prospectsAccount flagged for reviewI learned that LinkedIn limits daily actions. I added rate limiting:
import timeimport randomfrom rate_limiter import RateLimiter
def run_prospecting_agent(): prospects = load_prospect_list("target_accounts.csv") limiter = RateLimiter(max_per_hour=20)
results = [] for prospect in prospects: # Wait if we've hit rate limit limiter.wait_if_needed()
try: result = agent.invoke(prospect) results.append(result)
# Random delay between 30-60 seconds time.sleep(random.uniform(30, 60))
except RateLimitError: logger.warning("Rate limit hit, pausing for 1 hour") time.sleep(3600)
save_to_review_queue(results)Improving Message Quality
My first AI-generated messages were better than templates, but still felt robotic:
Hi John, I noticed your recent post about AI adoption. As VP of Engineeringat TechCorp, you might be interested in our platform. Would you like toconnect and discuss?Too generic. I improved the personalization logic:
def create_personalization_prompt(prospect: dict) -> str: """Generate context-rich prompt for message drafting"""
hooks = []
# Recent activity hook if prospect.get("recent_posts"): post = prospect["recent_posts"][0] hooks.append(f"Their recent post: '{post[:100]}...'")
# Company news hook if prospect.get("company_news"): news = prospect["company_news"][0] hooks.append(f"Company update: {news[:80]}")
# Role-based hook if "VP" in prospect["title"] or "Head of" in prospect["title"]: hooks.append("Senior leader likely focused on team efficiency") elif "Founder" in prospect["title"]: hooks.append("Founder focused on growth and resource optimization")
return f"""Target: {prospect['name']} | {prospect['title']} at {prospect['company']}
Context:{chr(10).join(hooks)}
Write a connection request that:1. Opens with specific context (not "I noticed your profile")2. Connects their situation to a relevant insight3. Asks one specific question4. Stays under 300 characters"""Now the messages sounded more genuine:
Your post about migrating to microservices at 50 engineers resonated - thatexact inflection point where architectural decisions make or break scale.What's been the biggest surprise in your migration journey so far?Setting Up Overnight Processing
I configured the agent to run while I slept:
import scheduleimport timeimport randomfrom datetime import datetime
def run_prospecting_agent(): print(f"Starting prospecting agent at {datetime.now()}")
prospects = load_daily_prospects() print(f"Processing {len(prospects)} prospects")
for i, prospect in enumerate(prospects): try: result = agent.invoke(prospect) queue_for_review(result)
# Rate limiting: 30-60 seconds between prospects if i < len(prospects) - 1: time.sleep(random.uniform(30, 60))
except Exception as e: log_error(prospect, e) continue
print(f"Completed at {datetime.now()}") send_summary_email()
# Schedule for overnight executionschedule.every().day.at("23:00").do(run_prospecting_agent)
while True: schedule.run_pending() time.sleep(60)Building the Morning Review Dashboard
I never auto-send messages. Every morning, I review what the agent drafted:
from flask import Flask, jsonify, request
app = Flask(__name__)
@app.route('/api/messages/queue', methods=['GET'])def get_review_queue(): """Fetch messages awaiting review""" messages = db.query(""" SELECT id, prospect_name, prospect_company, draft_message, created_at, context_sources FROM message_queue WHERE reviewed = FALSE ORDER BY created_at DESC """) return jsonify(messages)
@app.route('/api/messages/<id>/approve', methods=['POST'])def approve_message(id): """Approve message for sending""" db.execute(""" UPDATE message_queue SET approved = TRUE, reviewed = TRUE WHERE id = %s """, id) return jsonify({"status": "approved"})
@app.route('/api/messages/<id>/edit', methods=['POST'])def edit_message(id): """Edit and approve message""" new_message = request.json.get('message') db.execute(""" UPDATE message_queue SET draft_message = %s, approved = TRUE, reviewed = TRUE WHERE id = %s """, new_message, id) return jsonify({"status": "edited_and_approved"})The Results
After two weeks:
Time saved: 2-3 hours daily recoveredResponse rate: 3x higher than manual outreachMeeting bookings: 8 from 50 connection requestsQuality: Messages at 9 AM and 3 PM are equally goodThe unexpected benefit: my AI-generated messages were actually better than my manual ones at 9 AM. The agent had “infinite patience” for context gathering. I would get lazy and skip reading recent posts. The agent never did.
Common Mistakes I Made
Mistake 1: Over-automation without personalization
My first attempt used templates. The AI fixed this by actually reading each prospect’s context.
Mistake 2: Ignoring LinkedIn’s limits
Sending 100+ daily requests got my account restricted. I now stay under 30 per day.
Mistake 3: Auto-sending without review
I never let the agent send directly. I review every message each morning. This caught several awkward phrasings before they went out.
Mistake 4: Using only basic profile data
Name and company are not enough. The agent now pulls recent posts, company news, and role-specific context.
Best Practices
- Start small - Begin with 20-30 messages daily, then scale
- Always review - Never auto-send without human approval
- Respect limits - Stay under LinkedIn’s daily thresholds
- Test and iterate - A/B test different message approaches
- Track everything - Log what works for future refinement
Summary
I built an AI agent that automates LinkedIn prospecting overnight. It saves 2-3 hours daily and produces better messages than I wrote manually. The key components are: LangGraph for workflow orchestration, Claude for message generation, rate limiting to avoid account restrictions, and a human review queue before anything goes out. The agent runs while I sleep, and I wake up to a queue of personalized messages ready for my approval.
Final Words + More Resources
My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me
Here are also the most important links from this article along with some further resources that will help you in this scope:
Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!
Comments