Send LinkedIn invitations from database

Tags: #notion #invitation #automation #content #linkedin
Author: Florent Ravenel
Description: This notebook allows users to quickly and easily send LinkedIn invitations to contacts stored in a database.


Import libraries

import naas
from naas_drivers import notion, linkedin
import pandas as pd
import os
from datetime import datetime
import requests

Setup Notion

# Enter Token API
NOTION_TOKEN = "*****"
# Enter Database URL
DATABASE_URL = "********"
# Column with Linkedin URL
col_lk_notion = "Name"

Setup LinkedIn

# LinkedIn cookies
# LinkedIn limit invitations to 100 per week, this notebook only send 10 invites at once.
LIMIT = 10

Setup variables

# CSV to manage and remove profile already in your contact
# CSV to manage URL not valid
csv_not_valid = "LINKEDIN_NOT_VALID.csv"
# CSV to store invitations sent
csv_invitation = "LINKEDIN_INVITATIONS_SENT.csv"

Schedule your notebook

# Scheduler your invitation everyday at 8:00 AM
naas.scheduler.add(cron="0 8 * * *")
# Uncomment the line below to delete your scheduler
# naas.scheduler.delete()


Get Notion database

db_notion = notion.connect(NOTION_TOKEN).database.get(DATABASE_URL)
df_notion = db_notion.df()

Get LinkedIn invitations sent

df_lk_invitations = linkedin.connect(LI_AT, JSESSIONID).invitation.get_sent()

Get profile checked and already in your network

def get_csv(output_path):
df = pd.DataFrame()
if os.path.exists(output_path):
df = pd.read_csv(output_path).drop_duplicates()
return df
df_contacts = get_csv(csv_contact)

Get URL not valid

df_not_valid = get_csv(csv_not_valid)

Get invitations sent (CSV)

Public ID can be different between what we get from LinkedIn and from your source URL. So we need to double check invitations sent with a CSV stored on your local
df_csv_invitations = get_csv(csv_invitation)

Get new invitation

  • Clean Notion database to get valid URL
  • Remove profile when already invited
def get_new_invitations(
df_notion, df_lk_invitations, df_csv_invitations, df_contacts, df_not_valid
# Cleaning
df = df_notion.copy()
df = df[df[col_lk_notion].str.match("")].reset_index(drop=True)
df["PROFILE_ID"] = df.apply(
lambda row: row[col_lk_notion].split("com/in/")[-1].split("/")[0], axis=1
print("✔️ LinkedIn valid URL :", len(df))
# Get list of pending LinkedIn invitations
pending_lk_invitations = []
if len(df_lk_invitations) > 0:
pending_lk_invitations = df_lk_invitations["PUBLIC_ID"].unique().tolist()
print("❌ Pending LinkedIn invitations :", len(pending_lk_invitations))
# Get list of CSV invitations
pending_csv_invitations = []
if len(df_csv_invitations) > 0:
pending_csv_invitations = df_csv_invitations["PUBLIC_ID"].unique().tolist()
print("❌ Pending CSV invitations :", len(pending_csv_invitations))
# Get profile already in network
contacts = []
if len(df_contacts) > 0:
contacts = df_contacts["PUBLIC_ID"].unique().tolist()
print("❌ Already in network :", len(contacts))
# Get profile not valid
not_valids = []
if len(df_not_valid) > 0:
not_valids = df_not_valid["PROFILE_ID"].unique().tolist()
print("❌ Profile not valid:", len(not_valids))
# Remove pending invitations / already in network / not valid profile from dataframe
exclude = pending_lk_invitations + pending_csv_invitations + contacts + not_valids
df = df[~df["PROFILE_ID"].isin(exclude)].reset_index(drop=True)
print("➡️ New invitation:", len(df))
return df
df_new_invitations = get_new_invitations(
df_notion, df_lk_invitations, df_csv_invitations, df_contacts, df_not_valid

Send invitation

def send_invitation(df, df_not_valid=None, df_contacts=None, df_csv_invitations=None):
# Check if new invitations to perform
if len(df) == 0:
print("🤙 No new invitations to send")
return df
# Setup variables
if df_not_valid is None:
df_not_valid = pd.DataFrame()
if df_contacts is None:
df_contacts = pd.DataFrame()
if df_csv_invitations is None:
df_csv_invitations = pd.DataFrame()
# Loop
count = 1
for index, row in df.iterrows():
df_network = pd.DataFrame()
profile = row[col_lk_notion]
print(f"➡️ Checking :", profile)
# Get distance with profile
df_network = linkedin.connect(LI_AT, JSESSIONID).profile.get_network(
except Exception as e:
# If error, profile URL is not valid => append Notion page to CSV not valid to not check it again
df_not_valid = pd.concat([df_not_valid, df[index : index + 1]])
df_not_valid.to_csv(csv_not_valid, index=False)
print("❌ URL not valid", e)
# Check if profile is already in your network
if len(df_network) > 0:
distance = df_network.loc[0, "DISTANCE"]
# If not profile in network...
if distance not in ["SELF", "DISTANCE_1"]:
# => send invitation
linkedin.connect(LI_AT, JSESSIONID).invitation.send(
print(count, "- 🙌 Invitation successfully sent")
df_csv_invitations = pd.concat([df_csv_invitations, df_network])
df_csv_invitations.to_csv(csv_invitation, index=False)
except Exception as e:
print(count, "- ❌ Invitation not sent", e)
count += 1
# If profile already in your network => append network result to CSV existing contact to not check it again
df_contacts = pd.concat([df_contacts, df_network])
df_contacts.to_csv(csv_contact, index=False)
print(f"👍 Already in my network, 💾 saved in CSV")
# Manage LinkedIn limit
if count > LIMIT:
print("⚠️ LinkedIn invitation limit reached", LIMIT)
return df_csv_invitations
return df_csv_invitations
df_csv_invitations = send_invitation(
df_new_invitations, df_not_valid, df_contacts, df_csv_invitations


Display invitations sent