Introducing API Playground (and YouTube Monitoring!) Learn more

How to Programmatically Generate Leads & Write Cold Emails
proxycurl

How to Programmatically Generate Leads & Write Cold Emails

How many cold emails do you receive every day? A dozen? More than that?

Now, how many do you respond to?

Further, how many cold emails have resulted in you exchanging money with a person or business?

I’m willing to bet you receive quite a few cold emails every day, and you’ve ignored nearly all of them.

Yet, nonetheless, you hear all the time cold email can be one of the best channels for sales across several different industries. Practically every B2B business for that matter.

It’s true, too, amidst all of the noise, cold emailing remains a valuable tool for businesses aiming to make genuine connections, foster partnerships, and drive sales.

The key to success at cold emailing, though?

It’s strategic prospecting and effective outreach, which is easier said than done, of course.

However, with tools like Proxycurl, accessing detailed insights on potential leads became much simpler, allowing for more personalized and impactful cold emails.

That being said, in this guide, we'll be walking you through a step-by-step process to not only automate your prospecting efforts, but also create compelling cold emails that actually convert.

Yes, contrary to what you may believe, cold emailing can even work for you too...

We’ll be showing you the entire process of crafting a successful cold email outreach campaign from start to finish.

And it doesn’t matter if you’re:

If you’re looking to contact a specific target audience to complete a desired action, booking a call, selling a product, etc, cold emailing will work for you.

This guide will equip you with all of the tools and knowledge to make cold emailing a powerful asset in your marketing arsenal.

The importance of targeted prospecting

Before diving into the mechanics of cold emailing, it's crucial to understand the significance of targeted prospecting.

Sending emails en masse without proper targeting is a recipe for low open rates and even lower conversion rates.

While the temptation might exist to cast a wide net with generic emails, this approach often leads to wasted efforts and missed opportunities.

Templated emails, devoid of any personal touch, often come across as spam and are easily dismissed by prospects, in other words, they don’t work.

Moreover, broad targeting, where emails are sent to a vast audience without consideration of their specific needs or interests, further dilutes the impact of the cold email.

When prospects feel that an email isn't tailored to them or doesn't address their unique challenges and aspirations, they’re more likely to ignore it.

In essence, the success of cold emailing hinges on the balance of precise targeting and personalization.

By ensuring that each email speaks directly to the recipient's needs, you can elevate your cold emailing efforts from mere shots in the dark to strategic, impactful communications.

Automating prospecting with Proxycurl

Update: Proxycurl has been sunset. I’m the founder behind Proxycurl, and the work has since moved to NinjaPear. I’m leaving the original Proxycurl examples here because the prospecting logic is still useful, but where it makes sense I’ve added NinjaPear alternatives you can use today.

Searching based on qualifying characteristics

Proxycurl's API was a game-changer for businesses looking to automate their prospecting efforts.

Using the Person Search Endpoint and Person Profile Endpoint, we could automatically search for prospects that meet certain criteria, falling within your ICP, and then enrich the results, and finally export the results into a .csv.

For example, let’s say we run a real estate showing/booking SaaS, and we want to start conducting a cold email campaign to win over more clients.

The first step is building a list of relevant, and accurate leads. Clearly, we don’t want to do this by hand. That takes forever, and there are more profitable activities you and your sales team can spend your time on.

That said, here’s how you could automate your entire lead generation process with a bit of Python and Proxycurl:

import requests
import csv

# Define the API key and headers
api_key = 'Your_API_Key_Here'
headers = {'Authorization': 'Bearer ' + api_key}

# Define the search endpoint and parameters
search_endpoint = 'https://nubela.co/proxycurl/api/search/person/'
search_params = {
    'country': 'US',
    'current_company_industry': '(?i)real estate',
    'current_role_title': '(?i)real estate agent',
    'page_size': '100'  # To get 100 profiles
}

# Define the profile endpoint
profile_endpoint = 'https://nubela.co/proxycurl/api/v2/linkedin'

# Fetch the LinkedIn profiles using the search endpoint
response = requests.get(search_endpoint, params=search_params, headers=headers)
profiles = response.json().get('results', [])

# Enrich each profile using the profile endpoint
enriched_profiles = []

for profile in profiles:
    linkedin_url = profile.get('linkedin_profile_url')

    if linkedin_url:
        response = requests.get(
            profile_endpoint,
            params={
                'linkedin_profile_url': linkedin_url,
                'personal_email': 'include'
            },
            headers=headers
        )

        if response.status_code == 200:
            enriched_data = response.json()
            enriched_profiles.append(enriched_data)
        else:
            print(f"Error enriching profile {linkedin_url}: {response.text}")

# Export the enriched profiles to a CSV
with open('enriched_real_estate_agents.csv', 'w', newline='', encoding='utf-8') as csvfile:
    writer = csv.writer(csvfile)

    headers = [
        'Full Name', 'LinkedIn URL', 'Occupation', 'Headline', 'Summary',
        'Country', 'City', 'State', 'Experiences', 'Education', 'Languages',
        'Inferred Salary', 'Gender', 'Industry', 'Interests', 'Personal Emails'
    ]
    writer.writerow(headers)

    for profile in enriched_profiles:
        writer.writerow([
            profile.get('full_name'),
            profile.get('linkedin_profile_url'),
            profile.get('occupation'),
            profile.get('headline'),
            profile.get('summary'),
            profile.get('country_full_name'),
            profile.get('city'),
            profile.get('state'),
            ", ".join([
                (exp.get('title') or '') + " at " + (exp.get('company') or '')
                for exp in profile.get('experiences', [])
            ]),
            ", ".join([
                (edu.get('school') or '') + " - " + (edu.get('degree') or '')
                for edu in profile.get('education', [])
            ]),
            ", ".join(profile.get('languages', [])),
            (profile.get('inferred_salary') or {}).get('range'),
            profile.get('gender'),
            profile.get('industry'),
            ", ".join(profile.get('interests', [])),
            ", ".join(profile.get('personal_emails', []))
        ])

print("Data exported to enriched_real_estate_agents.csv")

The above searches for a list of 100 real estate agents, then automatically enriches those results, the list of 100 real estate agents, and exports it into a file named enriched_real_estate_agents.csv.

You can get more specific with it, too, if you’d like. For example, we could also search by education, skills, current company, and beyond.

This will allow you to build different prospecting lists based on different target audiences, way beyond just real estate agents. All you need to do is change current_role_title.

Ultimately if there is a job title out there, you can target it, and you can further refine and filter your prospecting results with the various parameters available.

NinjaPear alternative

If you’re doing this today, the cleanest replacement path is NinjaPear.

NinjaPear does not mirror the old Proxycurl person search flow 1:1, so I wouldn’t pretend otherwise. Instead, you piece together the workflow using public-web company and person enrichment primitives:

  • Use the Company API to resolve a company name to a website and pull company details.
  • Use NinjaPear’s GET /api/v1/employee/profile to enrich a person from a work email, name + company, or role + company.
  • Use NinjaPear’s GET /api/v1/employee/work-email when you know the person and the company and need a verified work email.
  • If you need account-level targeting instead of person-first targeting, use NinjaPear’s competitive intelligence products like Customer API, Competitor API, and Company Updates to build tighter lead lists before you ever write the first email.

That last point matters more than people think. In 2026, the real edge is usually not “more leads.” It’s better timing and better account selection.

Now, let’s change the scenario and say you already have a list of social media profiles, and you’d like to get a list of email addresses from them.

Extracting email addresses from any given social media profile URL

Using Proxycurl’s Personal Email Lookup Endpoint, we could take a given social media profile URL and extract the email address from it.

It would work with any Twitter, Facebook, or LinkedIn profile URL you gave it, returning you the email address.

This endpoint also had an email_validation parameter that came in handy here so that we could ensure we were only sending to verified emails and weren’t endangering our emailing account.

Again using a bit of Python, here’s an example of extracting the email address from a social media profile:

import requests
import csv
import json

api_key = 'Your_API_Key_Here'  # Replace with your actual API key
headers = {'Authorization': 'Bearer ' + api_key}

api_endpoint = 'https://nubela.co/proxycurl/api/contact-api/personal-email'

params = {
    'email_validation': 'include',
    'page_size': '0',
}

# Create or overwrite the output CSV file
with open('output_responses.csv', 'w', newline='') as output_file:
    fieldnames = ['linkedin_url', 'valid_emails', 'invalid_emails']
    writer = csv.DictWriter(output_file, fieldnames=fieldnames)
    writer.writeheader()

    # Open the input CSV file and read the LinkedIn URLs
    with open('linkedin_urls.csv', 'r') as input_file:
        reader = csv.DictReader(input_file)

        for row in reader:
            linkedin_url = row['linkedin_profile_url']
            params['linkedin_profile_url'] = linkedin_url
            response = requests.get(api_endpoint, params=params, headers=headers)

            data = json.loads(response.text)
            valid_emails = ', '.join(data.get('emails', []))
            invalid_emails = ', '.join(data.get('invalid_emails', []))

            writer.writerow({
                'linkedin_url': linkedin_url,
                'valid_emails': valid_emails,
                'invalid_emails': invalid_emails
            })

print("Export completed!")

What that does is take a file named linkedin_urls.csv in the same folder as your Python script, with only one column, linkedin_profile_url, at the top, and then a list of LinkedIn profiles below it.

Then, it goes through the list extracting all of the valid emails into a file named output_responses.csv, filtering out all invalid email addresses.

Not bad, huh?

That means if you can find their Twitter, Facebook, or LinkedIn, you can likely send them cold emails.

I should also mention if you’d like to look up a work email address, you could do that with Proxycurl’s Work Email Lookup Endpoint, but it worked slightly differently and only worked with LinkedIn profile URLs.

NinjaPear alternative

If you’re replacing this flow with NinjaPear, the nearest live option is the Work Email Lookup endpoint.

NinjaPear’s pricing page lists GET /api/v1/employee/work-email at 2 credits when email is found, 0.5 credit on miss. That’s a cleaner operating model than the old “let’s scrape every social URL we can find and hope for the best” approach.

The tradeoff is obvious: NinjaPear is more explicit about identity resolution. You generally want a name + company domain, not just a random profile URL.

That sounds more limiting, but in practice it forces better hygiene in your workflow:

  • identify the person
  • confirm the company
  • resolve the company website
  • fetch the work email
  • send a tighter email to a better target

That is usually a win.

Now, let me throw in a freebie…

How to validate any email address for free

Using Proxycurl’s Disposable Email Address Check Endpoint, you could check if any email address belonged to a free or disposable email service.

In other words, you could validate if there were any worthless email addresses on any of your prospecting lists before you send, and this endpoint was entirely free.

Here’s an example of how you could use some Python with it to automatically validate a list of email addresses:

import requests
import csv

api_key = 'YOUR_API_KEY'  # Replace with your actual API key
headers = {'Authorization': 'Bearer ' + api_key}
api_endpoint = 'https://nubela.co/proxycurl/api/disposable-email'

valid_emails = []
free_emails = []
disposable_emails = []

# Open the input CSV file and read the emails
with open('emails_list.csv', 'r') as input_file:
    reader = csv.DictReader(input_file)

    for row in reader:
        email = row['email']
        params = {'email': email}
        response = requests.get(api_endpoint, params=params, headers=headers)
        data = response.json()

        if 'error' in data:
            print(f"Error for {email}: {data['error']}")
            continue

        if data.get('is_disposable_email') == True:
            disposable_emails.append(email)
        elif data.get('is_free_email') == True:
            free_emails.append(email)
        else:
            valid_emails.append(email)

# Write the emails to a new CSV file in separate columns
with open('categorized_emails.csv', 'w', newline='') as output_file:
    writer = csv.writer(output_file)
    writer.writerow(['Valid Emails', 'Free Emails', 'Disposable Emails'])

    max_rows = max(len(valid_emails), len(free_emails), len(disposable_emails))

    for i in range(max_rows):
        valid_email = valid_emails[i] if i < len(valid_emails) else ''
        free_email = free_emails[i] if i < len(free_emails) else ''
        disposable_email = disposable_emails[i] if i < len(disposable_emails) else ''
        writer.writerow([valid_email, free_email, disposable_email])

print("Export of categorized emails completed!")

That’ll take a list of emails within emails_list.csv and automatically validate them for you, placing them in one of three columns:

  • Valid emails
  • Free emails
  • Disposable emails

Again, entirely for free. There’s no reason to not be doing this before you ever send out any cold emails.

NinjaPear alternative

This one is easy. NinjaPear has a free Disposable Email Checker too.

Per NinjaPear’s pricing page, free endpoints include:

  • Company Logo
  • Disposable Email Check
  • Credit Balance
  • Feed list/get/update/delete
  • RSS feed

So if this is the only part of the old Proxycurl workflow you cared about, you still have a free path.

Okay, now that we’ve covered:

  1. Searching and building prospecting lists based on specific parameters
  2. Building prospecting lists with social media profiles
  3. Validating all of your email addresses

It’s time to move on to the fun part.

Crafting the perfect cold email

With a list of prospects in hand, it's time to write your cold email.

First things first: your email should be concise, relevant, and provide clear value to the recipient.

Furthermore, since we’ve enriched our contacts, we can use the data from Proxycurl, or now NinjaPear, to address the recipient by name and reference specific details about their company or role. That’s personalization.

Let’s start with an example of a bad cold email:

Subject: Hey! Check This Out!!!

Hey there *name*,

I'm just reaching out because I wanted to tell you about this thing called Proxycurl. It’s some kind of API or something? Anyway, I think it's supposed to help with data insights or whatever that means. I'm not really sure how it works, but I think you should totally check it out. Everyone's using it, I guess.

So, yeah, if you're into that kind of thing, maybe give it a look? Or don't. Whatever.

Catch you later,

Colton

P.S. I forgot to mention, but it's also good for sales and marketing teams, I think? Not sure. Anyway, bye!

I’ve made it comically bad intentionally, but you get the point.

This is literally how most cold emails sound:

  • Un-personal, bulk, and literally emotionally cold
  • Vague
  • Lack of value proposition
  • No clear problem/solution fit
  • No reason for them to take you seriously or respond to your email

Which is exactly why they don’t work.

Now, take a look at this cold email:

Hey *name*,

I took a look over your SaaS, and according to your blog post, it seems you’ve attempted cold emailing in the past and failed.

I work for Proxycurl, and we’re an API that provides data on people and companies, many people use us for cold email prospecting.

Do you have 15 minutes of your time to spare for me to show you how you could turn cold emailing from a flop into one of your highest-performing growth channels?

Look forward to hearing back from you,

Colton

P.S. I’m actually an alumnus from *college name* too, small world haha.

See the difference?

The clarity and value proposition is entirely different, and it presents a clear solution to an obvious problem.

It also doesn’t try to sell Proxycurl on the email. It only sells a call, and focuses first on conveying that we can provide value to them, and as concisely as possible.

Of course, the name of the game is all about automation, so instead of doing things the boring, time-consuming, and energy-draining way, aka, by hand, we can automate this process nearly entirely by using LLMs like ChatGPT.

Automate writing your cold emails with ChatGPT

In order to automate this with ChatGPT, we need to craft a prompt that works for your individual business.

Rather than using Proxycurl, I’ll use our real estate showing/booking SaaS example from earlier.

We could use something like this:

My name is Colton from RealEstateBooker. Our software as a service tool makes it much easier for real estate agents to book showings automatically. I want you to write a cold email that's a paragraph or less to the following prospect:

Name: Cassidy Jones

Location: Houston, Texas

Company: Berkshire Hathaway

Education: The University of Texas

Industry: Real estate

Job role: Real estate agent

The goal is to get a call booked. Be as concise as possible and present a clear problem/solution fit. The wording should be friendly and conversational. Don't use buzzwords, and use all of the data points provided to personalize the email and help it stand out.

Nice.

Plus, if you wanted to take this a step further, you could automate more of this using ChatGPT’s API.

This is all possible from our enrichment step earlier, because now you’ll have data points like:

  • Full name
  • Education
  • Summary of the individual
  • Current job role
  • Current location
  • And beyond

So you can automatically take these enriched variables and feed them to ChatGPT to improve the level of personalization in your email and, thus, the response.

You could personalize thousands of emails at once that way.

However, we just published an in-depth article about that here, so I won’t go into that any further in this article.

The main point here, though, is that automation doesn’t have to mean impersonal.

You can use Proxycurl to gather detailed insights about your prospects and use that data to craft personalized outreach emails, or do the same with NinjaPear’s person and company enrichment endpoints. But make sure to keep them concise and relevant.

Your cold emails should either directly provide value, or present an obvious solution to a major problem that the prospect is experiencing.

Now for the next step in the process...

What’s the best cold emailing tool?

Over here, we used Sendy.

We also already have an entire article about our cold emailing setup here that’s worth a read.

But, other than that, I wouldn’t put too much focus on the cold emailing tool. They all more or less do the same thing.

In our case, Sendy allowed us to self-host our emailing tool and use an SMTP relay to send out our emails, AWS SES.

That was the best of both worlds because it enabled us to keep our costs down and our deliverability high, which is the most important part of the equation.

Speaking of deliverability, here are a couple of best practices to get and keep you in the inbox folder instead of the spam folder:

  • Make sure you’re using a different domain to email from. You don’t want your main domain being flagged by the likes of Gmail, etc.
  • Warm up your cold email domain for a week or two prior. There are plenty of solutions out there to do that for you. You shouldn’t immediately go straight to blasting out thousands of emails on a new domain.
  • Use a relay with well-guarded IPs like AWS SES. Don’t try to send out your own cold emails with your own IPs. It’s not worth the hassle.
  • Keep an eye out for complaint rates. If they’re excessively high you can be kicked off any SMTP relay, AWS SES included. If you have an excessively high complaint rate it’s because your cold emails look spammy and aren’t personalized enough.

Cold emailing only works if your emails are getting read, and if they’re ending up in the spam folder it’s very unlikely they’re getting read.

The world’s best copy is useless if the email isn’t opened in the first place, so make sure you’re keeping an eye on deliverability, but the tool alone doesn’t inherently matter.

Practically all of them will allow you to build automated sequences nowadays, which is all we need here.

Things aren’t over once you start sending cold emails. In fact, things are really just starting.

Once you've started cold emailing, keep the following three things in mind:

1) Keep following up

You’ll find out many people who do convert don’t respond immediately.

Your prospects are busy people, and even though they might act right away, there’s a very big chance they simply won’t get around to fully digesting your message and value proposition the first time around.

That’s where following up comes in.

Don’t be afraid to throw a couple of emails in a sequence. In fact, if you aren’t, you’re doing it wrong.

Just make sure you provide the ability to unsubscribe from your sequence.

2) Usually, you aren’t right the first time

Be comfortable with testing new things, and be prepared to fail.

As I mentioned above, cold emailing is all about reiteration. You should expect to be wrong, but it takes testing, learning, and reiterating to perfect any skill, cold emailing included.

3) You might make a couple of people angry

Without a doubt, you will receive a couple of negative responses to your emails.

That’s understandable and is to be expected.

Of course, the more value-driven and personalized the cold email is, the less likely you are to receive a negative response.

But, nonetheless, it’ll happen, and you’ll need to expect it.

The criticism is usually fair too, not always, and if you actually read and understand the criticism, you can usually take it and use it to make your cold email copy better.

The next steps

Whew, that was a lot.

First, to put all of this into practice, you need a data source that can actually support the workflow.

If you’re reading this because you used Proxycurl before, the important update is simple: Proxycurl has been sunset.

The practical replacement now is NinjaPear, which is where the work moved. It gives you the pieces you’d actually need to run modern outbound today:

  • Company API for company details, headcount, funding, updates, and logo data
  • GET /api/v1/employee/profile for person enrichment
  • GET /api/v1/employee/work-email for verified work email lookup
  • Free disposable email checking
  • Competitive intelligence data if you want better account selection instead of just bigger lists

A couple of pricing details matter here:

  • NinjaPear starts with a 3-day free trial and 10 credits included
  • GET /api/v1/employee/profile is 3 credits per call
  • GET /api/v1/employee/work-email is 2 credits when found, 0.5 credit on miss
  • GET /api/v1/company/updates is 2 credits per call
  • Free endpoints include the Disposable Email Check and Company Logo

From that point forward, you have a couple of different options.

Technically, you could use the scripts provided here, adapted to your current stack, along with your preferred LLM API, to build a custom in-house solution and automate nearly everything involved in the cold emailing process including:

  • Searching for leads within your ICP
  • Building cold emailing lists
  • Creating cold emails at scale with enriched data and ChatGPT
  • Sending cold emails

Or, if you're a small business, you could keep things simple by using the prospecting logic above, enriching the right data points, pasting them into ChatGPT using the example prompt provided above, and finally loading them into a simple solution like Sendy.

Either way, the principle hasn’t changed.

Good cold email starts with accurate targeting, gets stronger with better data, and wins or loses on whether the message feels like it was written for one person instead of sprayed at 5,000.

If you want to put that into practice, start a free NinjaPear trial, pull a small list first, maybe 25 to 50 accounts, write the emails carefully, and learn from the replies before you scale anything up. That’s still the move.

Colton Randolph | Technical Writer
Colton is a technical writer skilled in Python, SEO, and content strategy. He combines rich data with his 8-year expertise in writing to illustrate how Sapiengraph can move the needle for you.

Featured Articles

Here's what we've been up to recently.

I dismissed someone, and it was not because of COVID19

The cadence of delivery. Last month, I dismissed the employment of a software developer who oversold himself during the interview phase. He turned out to be on the lowest rung of the software engineers in my company. Not being good enough is not a reason to be dismissed. But not

sharedhere

I got blocked from posting on Facebook

I tried sharing some news on Facebook today, and I got blocked from posting in other groups. I had figured that I needed a better growth engine instead of over-sharing on Facebook, so I spent the morning planning the new growth engine. Growth Hacking I term what I do in