From Idea to Automation: Building a Serverless AI Lead Generation Agent (And the Bugs I Fixed Along the Way)

We’ve all been there. You have a great service, but finding the right people to tell about it is a constant, time-consuming grind. Lead generation is the lifeblood of any business, but it’s often a manual process of searching, filtering, and writing endless emails. I recently asked myself a question: “What if I could build an AI agent to do it for me?”

I set out with a few simple but firm rules: it had to be completely autonomous, run on a schedule, and use only free, browser-based tools. I’m not a DevOps expert, so I wanted to avoid complex local setups, command lines, or anything that couldn’t be managed from a web browser. I want to share my journey—the wins, the (many) bugs, and the final result. I hope my experience can help you build your own amazing automation tools.

The Grand Idea: What We Built

The goal was to create a “Set it and Forget It” AI agent that acts as a junior business development representative. Every day, it would:

The Blueprint: Our “Serverless” and Free-Tier Architecture

The magic of this project is its architecture. It’s completely serverless and costs nothing to run, relying on the generous free tiers of some amazing platforms.

The entire automation is controlled by a single workflow file in the repository. Here’s the snippet that tells the agent to run at 9 AM Dhaka time every day:

# .github/workflows/run_agent.yml
on:
  schedule:
    - cron: '0 3 * * *' # Runs at 3:00 AM UTC (9:00 AM GMT+6)

The Real Story: Challenges and Breakthroughs

Building something like this is never a straight line. The real learning happens when things break. Here are the biggest roadblocks I hit and how we solved them.

Challenge 1: The “404 Not Found” Mystery

My first attempt failed immediately. The agent couldn’t read the `company_profile.docx` file from the private GitHub repo. The log showed a `404 Not Found` error.

The Lesson: I was overthinking it! I was trying to download the file from the web, but the GitHub Actions workflow had already cloned the entire repository onto its virtual machine. The solution was much simpler: just read the file directly from the local disk. It was a great reminder that sometimes the easiest solution is the right one.

Challenge 2: The API Identity Crisis

Once the agent could read the profile, it failed at the search step with a `403 Forbidden` error. I was sure my API key was correct, but the server kept rejecting it. This was a frustrating one! After a lot of debugging, the breakthrough came from a simple code snippet. It turned out I was using a key from SerpApi (`serpapi.com`) but my code was written for a different service called Serper (`serper.dev`). They sound similar, but they’re completely different!

The Lesson: Always, always, always double-check that your code library matches the API documentation you’re reading. It’s a simple mistake that can cost hours of debugging.

Challenge 3: The Over-Eager AI Filter

My first successful run found leads, but some were low-quality (blogs, news articles, etc.). So, I tried to get clever. I built a “Qualifier” AI that would read the title of each search result and decide if it was a good lead. The result? It filtered out everything.

The Lesson: It’s easy to over-engineer an AI solution. My “smart” filter was too aggressive and had no nuance. I replaced it with a much simpler (and free) keyword filter that just skipped URLs with words like “blog,” “news,” or “jobs.” It was less “intelligent,” but far more effective.

The Final Engine: How It All Comes Together

After all the refinements, the core logic of the agent became a clean, efficient loop. Here’s a simplified look at the main function in `main.py` that orchestrates the whole process:

# A simplified view of the main loop

def main():
    # ... initialization ...
    
    # Generate a smart search query
    search_query = generate_search_query(company_profile, skill_of_the_day)
    
    # Get leads from SerpApi
    leads = search_leads_serpapi(search_query)
    
    leads_found_count = 0
    for lead in leads:
        # 1. Simple, reliable keyword filter
        if any(keyword in lead.get('url', '') for keyword in config.BAD_LEAD_KEYWORDS):
            continue

        # 2. Scrape the website for an email
        email = scrape_website(lead.get('url'))
        if not email:
            continue
            
        # 3. Check for duplicates in Supabase
        if is_duplicate(email):
            continue
            
        # 4. Clean the company name
        lead['company_name'] = clean_company_name(lead.get('company_name', ''), email)

        # 5. Draft the email with Gemini
        drafted_email_body = draft_email(company_profile, skill_of_the_day, lead)
        
        # 6. Save everything to the database!
        if drafted_email_body:
            save_lead(lead, email, drafted_email_body, lead.get('url'))
            leads_found_count += 1

Conclusion

This project was an incredible learning experience. It proved that you don’t need massive budgets or complex infrastructure to build powerful AI-powered automations. The entire agent runs reliably, finds quality leads, and hasn’t cost me a single penny.

The journey from a simple idea to a fully functional AI agent was all about iterative improvement: building, testing, seeing it fail, understanding why, and making it a little bit smarter each time. I hope sharing my process and my mistakes helps you on your own development journey.

What would you automate with an agent like this? Let me know in the comments!