LinkedIn Messaging API
The definitive guide to automating LinkedIn messaging and outreach.

/ proxycurl

How to Automate LinkedIn Messaging (without LinkedIn’s Messaging API)

Colton Randolph
Share:

Subscribe to our newsletter

Get the latest news from Proxycurl

The idea of being able to programmatically message anyone on LinkedIn, the world's largest professional networking site, is quite appealing.

Unfortunately, while the LinkedIn Messages API does exist, it falls under the Compliance API, and to gain access to the Compliance API, you need to become a member of the Compliance Partner Program, which is quite heavily regulated.

Here's how LinkedIn describes the requirements themselves:

The Compliance Partner Program is a private and paid program where LinkedIn provides access only to companies with use cases that meet our requirements. The requirement being that the organization or its customers should be FINRA / SEC registered. The primary use case should be archiving and monitoring regulated member's (from financial institutions) posts and public correspondence as required for the organization to be compliant with FINRA & SEC regulations.

So if you're not FINRA / SEC registered, don't bother. The main use case should also be for archiving and monitoring as required by FINRA and SEC regulations, not for anything like sales or marketing.

Furthermore, the LinkedIn Messages API has these additional ground rules:

  • Your messages must be tied to specific user actions.
  • Your users must opt-in for messaging.
  • You can't give incentives for opting in to receive messages from you.
  • Your messages must be pure text. No styling of any kind.

The above limitations would rule out about 99% of people, but, if you do happen to be one of 3 people in the world eligible for the LinkedIn Messages API (I'm kind of joking), you can apply for approval to the Compliance Partner Program here.

On the other hand, if you're in the majority that don't qualify for the Messages API, keep reading:

Alternative solutions to the LinkedIn Messages API

While there are quite a few LinkedIn automation tools designed to unofficially automate various tasks, there are three main LinkedIn automation tools designed entirely for messaging:

1) Dripify

Dripify CRM's homepage
Dripify CRM's homepage

As the name would suggest, Dripify focuses on "drip campaigns" which is just another way of saying complex outreach or if/then-based automation campaigns. Sometimes days, or weeks out, with varying paths possible depending on the prospects' actions (or lack of actions).

And it all functions via a simple web application:

What a Dripify campaign looks like (Source: Dripify)
What a Dripify campaign looks like (Source: Dripify)

It works quite well overall for LinkedIn messaging automation and integrates with many different applications.

On LinkedIn, you can only message your 1st-degree connections, so you'll need to be connected first to send a message.

Luckily Dripify comes with a way to send connection requests via email to help circumvent what is normally approximately a 100 connection-per-week limit, which definitely comes in handy. Outside of that, it has a limit of 100 messages per day for a free LinkedIn account or 150 messages per day for a paid LinkedIn account.

Dripify's plans range from $59 per user per month to $99 per user per month.

2) Octopus CRM

Octopus CRM's homepage
Octopus CRM's homepage

You could consider Octopus CRM Dripify's little brother in a sense. In fact, I'm honestly not sure if they're not owned by the same company.

But anyway, Octopus CRM does everything Dripify does in a simpler fashion, for less. Granted, it doesn't have as much automation complexity, so you are indeed getting less functionality.

Also, instead of operating via a web application, it operates via a Google Chrome extension:

Octopus CRM's LinkedIn messaging automation dashboard
Octopus CRM's LinkedIn messaging automation dashboard

It supports sending connection requests via email, as well as supporting 100 messages per day on a free LinkedIn account, or 150 per day on a paid LinkedIn account.

Pricing for Octopus CRM ranges from $9.99 per month to $39.99 per month, so quite a bit more affordable than Dripify.

Overall, I think it's a great balance of simplicity and functionality for automating LinkedIn messaging.

3) Expandi

Expandi's homepage
Expandi's homepage

Everything with Expandi just works. And you don't have to click around much to find what you need, but you can rest assured that it has all of the complexity you need.

It functions pretty similarly to Dripify and also operates via a web app (no browser extension required):

Expandi's messaging automation dashboard
Expandi's messaging automation dashboard (Source: Expandi)

They advertise 300 connection requests per week and place a limit on the number of messages you can send per day at 100.

Expandi clocks in at the most expensive on this list, starting at a relatively steep $99 per month.

Now that you know the top 3 alternative tools for automating LinkedIn messaging, let's talk about the bad part about doing it this way:

The con about doing LinkedIn messaging automation this way

LinkedIn isn't a fan of automation at all. Here's their official stance on it:

LinkedIn is committed to keeping our members' data safe and our platform free from fraud and abuse. In order to protect our members' privacy and keep LinkedIn a trusted platform for authentic interactions, we don't allow the use of third-party software or browser extensions that scrape, modify the appearance of, or automate activity on LinkedIn's website.

And they're pretty successful at preventing automation on LinkedIn, too.

These LinkedIn automation tools can only circumvent LinkedIn's built-in limitations so well, but at the end of the day, LinkedIn's limitations are difficult to bypass. That's why all of these tools more or less share the same limitations.

It's always going to be a game of cat and mouse automating LinkedIn messaging this way, and any account you automate LinkedIn messaging on will always be at risk. At scale, you'll constantly be rotating LinkedIn accounts.

This is why many opt to add LinkedIn messaging as a touchpoint in their outreach but don't rely on it entirely. There's a reason cold emailing and cold calling are still around, and that's because they still work.

That said, there are different ways to tap into the power of LinkedIn for outreach without having to deal with its messaging limitations...

Let me explain more:

Enriching LinkedIn contacts and taking your outreach off the platform

LinkedIn is the world's largest B2B database, and very useful because of this (particularly for B2B businesses), but there are some inherent flaws in doing outreach on the platform. Particularly at scale.

So, instead of just staying on LinkedIn, confined to the limitations that fundamentally exist on LinkedIn's messaging platform, you can tap into the data contained on LinkedIn, and automate your outreach off-platform. It's kind of the best of both worlds and works quite well.

We can get all of the things we need for outreach from LinkedIn like:

  • Name
  • Job role
  • Work experience
  • A general prospect description
  • Education history
  • Phone/email
  • Beyond

But it requires quite a bit of patience and knowledge to be able to successfully scrape this data at scale from LinkedIn, and a rotating rolodex of things like LinkedIn accounts and proxies.

(Note: Curious about the legality of scraping LinkedIn data? To keep things short: it's legal if it's public data. You can learn more about that here.)

Many software developers simply don't want to deal with scraping platforms like LinkedIn, even if they have the technical know-how, because they know it's going to be a big headache to keep operational.

LinkedIn definitely goes the extra mile to make a scraper's life painful and intentionally does things like slightly changing web structure all the time to break scrapers. Even if it's obviously not going to do anything other than temporarily break things.

Luckily for you, you don't have to worry about scraping LinkedIn yourself to source the data you need for outreach. We did it for you.

Meet Proxycurl: a B2B data provider and API

Over here at Proxycurl, we've scraped hundreds of millions of LinkedIn profiles (companies, personal profiles, jobs) with all of the same data points you could expect from LinkedIn. The dataset we've built with it is called LinkDB.

Then we took that dataset and built a B2B data enrichment API named Proxycurl with it as well as added additional data sources such as Crunchbase.

Using our different endpoints, you can programmatically pull all of the B2B data you could ever possibly need and use that to build an automated outreach system.

Our different API endpoints

Proxycurl has many different API endpoints, such as the Person Profile Endpoint, which allows you to enrich any given social media URL, specifically a LinkedIn profile URL. Or the Company Profile Endpoint, which allows you to get structured data of any given LinkedIn company URL.

For pulling contact information, we have our Personal Contact Number Lookup Endpoint, Personal Email Lookup Endpoint, and our Work Email Lookup Endpoint.

These endpoints, however, only allow you to input a given URL and receive a result back.

So, they work well if you're interested in enriching specified contacts/profiles, but, our Search API (which contains both a Company Search Endpoint, and a Person Search Endpoint), on the other hand, will allow you to search through our entire dataset for a given result.

For the sake of this article, we'll primarily be focusing on the Person Search Endpoint.

Let's put this into practice:

Programmatically searching and extracting data from LinkedIn

There are a ton of different ways you can use an API to build systems and pull data.

But to keep things simple and pretty universal, in this article, I'll only be using:

  1. Python, my programming language of choice
  2. A .CSV file
  3. An outreach tool

Python is quite easy to use and frequently used for data manipulation so it'll work perfectly here. I chose .CSV as our plain text format for storing data because it's easy and all of these outreach tools support using it as an export/import format.

So, for example, let's say we run a marketing agency that focuses on computer cleaner software companies, so we want to target decision-makers (not gatekeepers), but specifically the founders/co-founders of these companies. That way, our chance of actually closing a deal goes way up.

The following Python script would use our Person Search Endpoint as well as our Personal Contact Number Lookup Endpoint and our Personal Email Lookup Endpoint to search our API for the founders of computer software companies and export their contact information for us:

import requests
import csv

# API credentials and endpoint configuration
api_key = 'Your_API_Key_Here'  # Replace with your actual API key
headers = {'Authorization': 'Bearer ' + api_key}
search_api_endpoint = 'https://nubela.co/proxycurl/api/v2/search/person/'
contact_phone_endpoint = 'https://nubela.co/proxycurl/api/contact-api/personal-contact'
contact_email_endpoint = 'https://nubela.co/proxycurl/api/contact-api/personal-email'

# Parameters for the initial search request
search_params = {
    'country': 'US',
    'current_role_title': 'founder',
    'current_company_industry': 'computer software',
    'page_size': '1',
    'enrich_profiles': 'enrich',
}

# Output CSV file and headers configuration
output_file = 'enriched_profiles_with_contacts.csv'
fieldnames = [
    'linkedin_profile_url', 'first_name', 'last_name', 'occupation', 'headline',
    'company', 'company_linkedin_profile_url', 'city', 'state', 'personal_phone_number', 'personal_email'
]


def fetch_contact_info(api_endpoint, linkedin_profile_url):
    """Fetch personal contact info (phone number or email)."""
    params = {'linkedin_profile_url': linkedin_profile_url}
    response = requests.get(api_endpoint, headers=headers, params=params)
    if response.status_code == 200:
        data = response.json()
        if 'personal-contact' in api_endpoint:
            return ', '.join(data.get('numbers', []))
        elif 'personal-email' in api_endpoint:
            return ', '.join(data.get('emails', []))
    else:
        print(f"Error fetching contact info from {api_endpoint}: {response.status_code}, {response.text}")
    return ''


def extract_company_info(profile):
    """Extract the most relevant company name and LinkedIn URL from the profile's experiences."""
    company_name = ''
    company_linkedin_url = ''
    experiences = profile.get('experiences', [])
    if experiences:
        most_recent_experience = experiences[0]
        company_name = most_recent_experience.get('company', '')
        company_linkedin_url = most_recent_experience.get('company_linkedin_profile_url', '')
    return company_name, company_linkedin_url


def process_and_write_profiles(writer, result_limit):
    """Fetch profiles and write their details, including contact info, to the CSV file."""
    url = search_api_endpoint
    params = search_params.copy()
    result_count = 0

    while url and result_count < result_limit:
        response = requests.get(url, headers=headers, params=params)
        if response.status_code != 200:
            print(f"Error fetching profiles: {response.status_code}, {response.text}")
            break

        data = response.json()
        for profile in data.get('results', []):
            if result_count >= result_limit:
                break

            email_info = fetch_contact_info(contact_email_endpoint, profile['linkedin_profile_url'])
            phone_info = fetch_contact_info(contact_phone_endpoint, profile['linkedin_profile_url'])

            profile_details = profile.get('profile', {})
            first_name = profile_details.get('first_name', '')
            last_name = profile_details.get('last_name', '')
            occupation = profile_details.get('occupation', '')
            headline = profile_details.get('headline', '')

            company, company_linkedin_url = extract_company_info(profile_details)

            city = profile_details.get('city', '')
            state = profile_details.get('state', '')

            writer.writerow({
                'linkedin_profile_url': profile['linkedin_profile_url'],
                'first_name': first_name,
                'last_name': last_name,
                'occupation': occupation,
                'headline': headline,
                'company': company,
                'company_linkedin_profile_url': company_linkedin_url,
                'city': city,
                'state': state,
                'personal_phone_number': phone_info,
                'personal_email': email_info,
            })
            result_count += 1

        next_page_url = data.get('next_page')
        if next_page_url and result_count < result_limit:
            url = next_page_url
            params = {}
        else:
            break


with open(output_file, mode='w', newline='') as file:
    writer = csv.DictWriter(file, fieldnames=fieldnames)
    writer.writeheader()
    process_and_write_profiles(writer, 10)

    # Add desired result limit above

print("Completed fetching LinkedIn profile data with contact information.")
Result returned in Pycharm, my Python IDE of choice, when the script hits the specified lead quantity
Results returned in Pycharm, my Python IDE of choice, when the script hits the specified lead quantity

It's set to generate 10 leads, grabbing the following information from the prospects found in your given search query:

  • LinkedIn profile URL
  • Full name
  • Occupation
  • Headline of the given profile
  • Company they work for
  • Their companies LinkedIn URL
  • City
  • State
  • Personal phone number
  • Personal email number (to pull work emails on top of personal emails, you'd just need to integrate our Work Email Lookup Endpoint with the script as well)

And then writes it into a .CSV named enriched_profiles_with_contacts.csv in the same folder as script is ran in:

Exported results from our Python script
Exported results from our Python script (contact information being redacted)

(Note: To receive an API key for testing, you can click here to create your Proxycurl account.)

It's important to note that our Search API supports Boolean operators as well, so you can use AND,ORandNOT to refine your search results.

So, if for some reason you wanted exclusively founders instead of founders and co-founders (which the above query would search for), you could change it to 'current_role_title': 'founder NOT co-founder' accordingly, instead of 'current_role_title': 'founder'.

Additionally, to change the quantity of the amount of leads generated, just change the quantity at the end of the script where it says process_and_write_profiles(writer, 10).

We now have a way to programmatically search for and extract contact information

Nice! We have a .CSV full of our desired prospects' contact information ready for outreach. We just have to actually reach out.

(Note: To enrich a given profile, without searching, you'd use our Person Profile Endpoint. You can export the results in a very similar fashion. You can check out our docs here.)

To do so, we'll use an outreach tool:

Turning B2B data into automated outreach

We'll use Reply.io in this example as they work quite well (and they're a customer of ours), but there are several other automated outreach tools available, too.

Reply.io's homepage
Reply.io's homepage

Anyway, using that same .CSV from earlier, we can import every single contact and data point we gathered earlier into Reply.io and create an outreach sequence.

First, click "People" and then "New import" and then finally "Import from CSV" on Reply.io's dashboard:

Reply.io's import process
Reply.io's import process

Then map the according data in our .CSV to variables within Reply.io -- you can also create any custom variables you'd like for importing variables that don't already exist within the platform:

Mapping our variables within Reply.io
Mapping our variables within Reply.io

Now all you have to do is click "Sequences" and "New sequence" and build out your desired outreach automation campaign:

Reply.io's sequences
Reply.io's sequences

You can build it out until your hearts content, combining email, text, etc (Reply.io supports LinkedIn messaging as well):

Reply.io's sequence builder
Reply.io's sequence builder

Using any of the variables that we imported that you'd like:

Repy.io's message editor
Repy.io's message editor

Not bad! Automating your LinkedIn outreach really is that simple.

But let me show you another old-school option as well:

The barebones alternative option for cold emailing

If you want to focus solely on cold email outreach using LinkedIn data, you could use a tool like Sendy, which is a simple self-hosted email automation tool. I've used it for years, and as a company, Proxycurl has also used it.

Sendy's homepage
Sendy's homepage

It costs $69 one time and integrates with AWS SES, which costs $0.10 per 1,000 emails. You can also use it with any other email relay out there.

We published an A-Z guide on using Sendy for cold emailing here, so I won't get too in-depth on this article, but it's quite easy to use.

The only con is the importation system, but you could use the following script to import your .CSV using the Sendy API after configuring your custom fields in Sendy (which we explain how to do in the guide).

//-------------------- EDIT HERE -----------------------//
$sendy_url = 'https://SendyInstallation.com';
$api_key = 'xxxxxxxxxxxxxx';
$list_id = 'xxxxxxxxxxxxxx'; 
$csv_file = 'enriched_profiles_with_contacts.csv';   
//------------- STOP EDITING HERE --------------------//
ini_set('auto_detect_line_endings',TRUE);
$file_handle = fopen($csv_file, "r");
$ch = curl_init();

while (!feof($file_handle) ) {
    set_time_limit(0);

    $line_of_text = fgetcsv($file_handle, 1024);
    // Extract the first email if there are multiple emails
    $emails = explode(',', $line_of_text[10]); // Emails are in the 11th column
    $email = isset($emails[0]) ? trim($emails[0]) : '';

    // Check if a phone number is present
    $phone_number = isset($line_of_text[9]) && !empty($line_of_text[9]) ? $line_of_text[9] : ''; // Phone numbers are in the 10th column

    $fields = array(
        'name' => urlencode($line_of_text[1] . ' ' . $line_of_text[2]), // Combining first name and last name
        'email' => urlencode($email), // Using the first email if multiple
        // Add your custom fields here
        'Occupation' => urlencode($line_of_text[3]), // Occupation is in the 4th column
        'Company' => urlencode($line_of_text[5]), // Company is in the 6th column
        'City' => urlencode($line_of_text[7]), // City is in the 8th column
        'State' => urlencode($line_of_text[8]), // State is in the 9th column
        'PhoneNumber' => urlencode($phone_number), // Phone number is in the 10th column
        'api_key' => urlencode($api_key),
        'list' => urlencode($list_id)
    );

    $fields_string = http_build_query($fields);
    
    curl_setopt($ch, CURLOPT_URL, $sendy_url.'/subscribe');
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $fields_string);
    
    // Making sure there's a first name and a valid email before sending the request
    if (!empty($line_of_text[1]) && filter_var($email, FILTER_VALIDATE_EMAIL)) {
        $response = curl_exec($ch);
        // It is recommended to check the response from curl_exec() for errors
        if(curl_errno($ch)) {
            echo 'Curl error: ' . curl_error($ch);
        }
        sleep(5); // Throttling requests to avoid overloading Sendy or your server
    }
    
    echo htmlspecialchars($line_of_text[1], ENT_QUOTES, 'UTF-8');
    echo '<br>';
}

fclose($file_handle);
curl_close($ch);
//------------------------------------------------------//

Just create an import folder in your Sendy installation, create index.php within that, and copy and paste the above script, adding your Sendy API information at the top (it's available in your Sendy settings).

Then upload your .CSV in that same folder and go to YourSendyInstall.com/import/index.php and all of your list will be imported with the according data points (it may take a while depending on your quantity of leads, let it run).

Then from there on, you can automatically cold email your new leads!

See? You didn't need a LinkedIn Messages API anyway

You can do all of your outreach on your own.

From LinkedIn messaging to cold emailing, to texting and beyond. You can do it all, programmatically, with Proxycurl in combination with your cold emailing or outreach tool of choice.

Unfortunately, it may not be as you imagined, officially through the LinkedIn API, but it's certainly still possible to tap into the vast B2B database that LinkedIn offers and automate outreach with it.

You could even further improve on the ideas displayed here in this article, automating this process even more, such as integrating our API directly with other outreach solutions APIs, requiring no need to upload a .CSV or anything similar.

You'll just need to weigh out the pros and cons of LinkedIn automation and decide what outreach setup is right for you.

Create your Proxycurl account

If you're ready to level up your outreach and tap into an excessive amount of rich B2B data, you can click here to create your Proxycurl account today.

It's free and you'll start with 10 credits to test out a few basic actions. Then you can opt for a pay-as-you-go plan and top up as little as $10, or select a subscription. For more information on our pricing, you can view this page here.

Thanks for reading, and here's to better outreach!

P.S. Have any questions about our API or about how we can help your LinkedIn outreach process? Send us an email over to "[email protected]" and we'll be glad to help.

Subscribe to our newsletter

Get the latest news from Proxycurl

Colton Randolph
Share:

Featured Articles

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