How to Build an AI Sales Development Representative (SDR) in Slack - Part 5: HubSpot CRM Integration

Introduction #
In this fifth part of our AI SDR series, we'll enhance our AI sales assistant with HubSpot integration and multi-platform campaign execution capabilities. By the end of this guide, your AI SDR will be able to handle the entire process from lead generation to campaign creation across both Smartlead and log these leads in HubSpot.
This guide is Part 5 of a six-part series for how to build your own AI SDR (Sales Development Representative) in Slack. This template includes the code from Parts 1-4 and the subsequent guides are linked at the bottom of this post.
Getting Started #
To begin enhancing your AI sales assistant with HubSpot integration and multi-platform campaign execution capabilities, fork this template by clicking "Use Template" below:
HubSpot Private App Key
Log in to your HubSpot account and go to Settings > Integrations > Private Apps.

Click Create a private app and give it a name.

Set the required scopes (you'll need access to deals, contacts, companies, and owners). For this app you will need to give the following scopes:
crm.schemas.contacts.write
crm.schemas.contacts.read
crm.schemas.custom.read
crm.schemas.custom.write
crm.schemas.companies.read
crm.schemas.companies.write
crm.objects.goals.read
Click Create app and you'll receive a private app access token.
In Replit, add this token to the Secrets tab as HUBSPOT_API_KEY.

Breaking Down the Enhanced AI Sales Assistant Code #
Let's explore the key components that make our AI SDR a more powerful tool for lead generation, qualification, email sequence generation, and multi-platform campaign execution:
HubSpot Integration
We've added a new function in utils.py to handle the upload of leads to HubSpot:
python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
def upload_leads_to_hubspot(campaign_id, leads):
print(f"Uploading {len(leads)} contacts to HubSpot CRM for campaign {campaign_id}")
# HubSpot API settings
api_key = os.environ['HUBSPOT_API_KEY']
base_url = "https://api.hubapi.com"
endpoint = "/crm/v3/objects/contacts/batch/create"
# Headers
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json"
}
# Convert leads into HubSpot contact objects
contacts = []
for lead in leads:
contact = {
"properties": {
"email": lead.get("email", ""),
"firstname": lead.get("first_name", ""),
"lastname": lead.get("last_name", ""),
"company": lead.get("company_name", "")
}
}
contacts.append(contact)
# Split contacts into batches of 100 (HubSpot's recommended batch size)
max_retries = 5
retry_delay = 1
batch_size = 100
batches = [contacts[i:i + batch_size] for i in range(0, len(contacts), batch_size)]
total_uploaded = 0
for batch in batches:
retry_count = 0
while retry_count < max_retries:
try:
# Prepare the request body
request_body = {"inputs": batch}
# Make the API request
response = requests.post(
f"{base_url}{endpoint}",
headers=headers,
data=json.dumps(request_body)
)
# Check the response
if response.status_code == 201 or response.status_code == 200:
total_uploaded += len(batch)
print(f"Batch uploaded successfully. Total uploaded: {total_uploaded}")
break
else:
print(f"Error uploading batch. Status code: {response.status_code}")
print(response.text)
retry_count += 1
time.sleep(retry_delay)
except Exception as e:
print(f"An error occurred: {str(e)}")
retry_count += 1
time.sleep(retry_delay)
# Respect rate limits (10 requests per second for free plans)
time.sleep(0.1)
print(f"Upload complete. Total contacts uploaded: {total_uploaded}")
This function handles the process of uploading leads to HubSpot, ensuring that we respect API rate limits and handle potential errors.
Multi-Platform Campaign Execution
We've updated the post_final_email_sequence function in main.py to handle campaign execution across both Smartlead and HubSpot:
python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
def post_final_email_sequence(client, csv_ts):
channel = app.state[csv_ts]["channel"]
thread_ts = app.state[csv_ts]["thread_ts"]
client.chat_postMessage(text="Drafting campaign and adding leads to CRM...", channel=channel, thread_ts=thread_ts)
print(f"Starting post_final_email_sequence for csv_ts: {csv_ts}")
email_sequence = app.state[csv_ts]["emails"]
enriched_csv = app.state[csv_ts]["enriched_csv"]
print(f"Email sequence count: {len(email_sequence)}")
print(f"Enriched CSV length: {len(enriched_csv.splitlines())}")
# Create Smartlead campaign
campaign_name = f"Campaign-{csv_ts}"
try:
print(f"Creating Smartlead campaign: {campaign_name}")
campaign_id = create_smartlead_campaign(campaign_name)
print(f"Smartlead campaign created with ID: {campaign_id}")
# Prepare leads for upload
leads = []
for row in csv.DictReader(io.StringIO(enriched_csv)):
lead = {
"first_name": row.get("first_name", ""),
"last_name": row.get("last_name", ""),
"email": row.get("email", ""),
"company_name": row.get("company_name", ""),
"website": row.get("company_website", ""),
"location": f"{row.get('city', '')}, {row.get('state', '')}, {row.get('country', '')}".strip(", "),
"custom_fields": {
"Title": row.get("title", ""),
"Company Industry": row.get("company_industry", ""),
"Company Size": row.get("company_size", ""),
"Seniority": row.get("seniority", ""),
"Departments": row.get("departments", ""),
"Functions": row.get("functions", "")
},
"linkedin_profile": row.get("linkedin_url", "")
}
leads.append(lead)
print(f"Prepared {len(leads)} leads for upload")
# Upload leads to Smartlead
print(f"Uploading leads to Smartlead campaign {campaign_id}")
upload_result = upload_leads_to_smartlead(campaign_id, leads)
print(f"Smartlead Upload result: {upload_result}")
# Upload leads to HubSpot
print("Uploading leads to HubSpot")
hs_upload_result = upload_leads_to_hubspot(campaign_id, leads)
print(f"HubSpot Upload result: {hs_upload_result}")
# Save campaign sequence
print(f"Saving campaign sequence for campaign {campaign_id}")
sequence_result = save_campaign_sequence(campaign_id, email_sequence)
print(f"Sequence save result: {sequence_result}")
# Get campaign details
print(f"Fetching campaign details for campaign {campaign_id}")
campaign_details = get_campaign_by_id(campaign_id)
print(f"Campaign details: {campaign_details}")
message = f"I have now prepared this campaign in Smartlead and added these leads as Contacts in HubSpot. I look forward to seeing the returns this has for your business. Please feel free to come back anytime if you have any other sales or outreach needs:\n\n"
message += f":point_right: <https://app.smartlead.ai/app/email-campaign/{campaign_id}/analytics|Smartlead Campaign - {campaign_name}>\n\n"
message += f"Campaign Status: {campaign_details['status']}\n"
message += f"Uploaded Leads: {upload_result['upload_count']}\n"
message += f"Total Emails in Sequence: {len(email_sequence)}\n"
except Exception as e:
print(f"Error in post_final_email_sequence: {str(e)}")
message = f"An error occurred while creating the Smartlead campaign: {str(e)}"
client.chat_postMessage(channel=channel, thread_ts=thread_ts, text=message)
This function now handles the uploading leads to both Smartlead and HubSpot, and provides a summary of the actions taken across both platforms.
Deploying Your AI SDR #
In order to keep your AI SDR running 24/7 and receive requests whenever someone mentions it in Slack, you'll need to deploy it on a hosted server.
Open a new tab in the Workspace and search for “Deployments” or open the control console by typing ⌘ + K (or Ctrl + K) and type "deploy". You should find a screen like this.

For bots like this that need always need to be up listening to requests, we recommend using a Reserved VM. On the next screen, click Approve and configure build settings most internal bots work fine with the default machine settings but if you need more power later, you can always come back and change these settings later. You can monitor your usage and billing at any time at: replit.com/usage.
On the next screen, you’ll be able to set your primary domain and edit the Secrets that will be in your production deployment. Usually, we keep these settings as they are.
Finally, click Deploy and watch your bot go live!
What's Next for Your AI Sales Assistant #
With these enhancements, your AI SDR is now a comprehensive tool for lead generation, email address enrichment, email sequence generation, and CRM syncs. In the next part of this series, we'll be adding the ability for the assistant to add your leads and into your HubSpot CRM. The other parts of the series include:
- AI SDR - Part 1 - Lead List Creation
- AI SDR - Part 2 - Lead Enrichment
- AI SDR - Part 3 - Draft Email Copy
- AI SDR - Part 4 - Create Smartlead campaign
- AI SDR - Part 5 (this guide) - Add Leads to HubSpot
- AI SDR - Part 6 (Final) - Agent Mode
It is important to note that HubSpot's Acceptable Use Policy (Section 2c) prohibits the emailing of purchased emails like those pulled from Apollo in Part 1 of this six-part tutorial. Therefore, we recommend only emailing these leads from tools like Smartlead.
If you'd like to discuss how to enable your team to build and implement tools like these, feel free to schedule some time with the Replit team for a quick demo of our product.
Happy coding and selling!