How to Build an AI Sales Development Representative (SDR) in Slack

Introduction #
With this guide and template, we will create an AI SDR (Sales Development Representative) that can be used from your Slack workspace. The AI SDR can create a leads list using Apollo.io, enrich the lead list with Clay, draft an email sequence in Smartlead, and log the leads as Contacts in HubSpot. This guide covers how to allow the assistant to complete all steps autonomously while providing real-time updates in Slack.
Getting Started #
This guide is sixth and final part, of a six-part series for how to build your own AI SDR in Slack. This template includes the final code of the project:
If you'd like to dive deeper and remix specific components of this final project, check out the previous guides here:
- 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 - Add Leads to HubSpot
Breaking Down the AI Sales Assistant Code #
Let's explore the key components of our AI SDR project:
Asynchronous Operations
We've updated our main application to use asynchronous operations, which allows for better handling of concurrent tasks and improved responsiveness. Here's the updated main function:
python
1
2
3
4
5
6
async def main():
handler = AsyncSocketModeHandler(app, os.environ["SLACK_APP_TOKEN"])
await handler.start_async()
if __name__ == "__main__":
asyncio.run(main())
This change allows our application to handle multiple requests concurrently, improving overall performance and user experience.
Cross-platform Integrations
We've improved our integration with both Smartlead and HubSpot, allowing for a more seamless workflow. Here's an example of how we handle lead uploads to both platforms:
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
async def post_final_email_sequence(client, csv_ts):
channel = app.state[csv_ts]["channel"]
thread_ts = app.state[csv_ts]["thread_ts"]
await 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:
# Format leads for HubSpot and Smartlead uploads
leads = prepare_leads_for_upload(enriched_csv)
print(f"Prepared {len(leads)} leads for upload")
# Upload leads to HubSpot
print("Uploading leads to HubSpot")
await client.chat_postMessage(channel=channel, thread_ts=thread_ts, text="Uploading leads as Contacts to HubSpot...")
hs_upload_result = await upload_leads_to_hubspot(leads)
print(f"HubSpot Upload result: {hs_upload_result}")
await client.chat_postMessage(channel=channel, thread_ts=thread_ts, text="Leads successfully added to HubSpot Contacts :hubspot:")
# Create Smartlead campaign
print(f"Creating SmartLead campaign: {campaign_name}")
campaign_id = await create_smartlead_campaign(campaign_name)
print(f"SmartLead campaign created with ID: {campaign_id}")
# Upload leads to SmartLead
print(f"Uploading leads to Smartlead campaign {campaign_id}")
upload_result = await upload_leads_to_smartlead(campaign_id, leads)
print(f"Smartlead Upload result: {upload_result}")
# Save campaign sequence
print(f"Saving campaign sequence for campaign {campaign_id}")
sequence_result = await 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 = await 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 to Smartlead: {upload_result['upload_count']}\n"
message += f"Uploaded Leads to HubSpot: {hs_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)}"
channel = app.state[csv_ts]["channel"]
thread_ts = app.state[csv_ts]["thread_ts"]
await client.chat_postMessage(channel=channel, thread_ts=thread_ts, text=message)
This function demonstrates how we handle the process of creating a campaign in SmartLead, uploading leads to both SmartLead and HubSpot, and providing real-time updates to the user throughout the process.
Error Handling and User Feedback
This guide includes enhanced error handling and user feedback mechanisms to provide more informative messages throughout the process. Here's an example from our autonomous process function:
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
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
async def autonomous_process(csv_ts, channel, thread_ts, company_details, client, email_count=3):
try:
print(f"Starting autonomous process for csv_ts: {csv_ts}")
print(f"Your company details: {company_details}")
print(f"Email count: {email_count}")
# Enrich leads
await client.chat_postMessage(channel=channel, thread_ts=thread_ts, text="Enriching leads with email addresses, this may take a while...")
csv_content = app.state[csv_ts]["csv_content"]
print(f"CSV content type: {type(csv_content)}")
print(f"CSV content length: {len(csv_content)}")
print("Calling enrich_leads function...")
enriched_csv = await enrich_leads(csv_content)
print(f"Enriched CSV type: {type(enriched_csv)}")
if not enriched_csv:
print("No leads could be enriched")
await client.chat_postMessage(channel=channel, thread_ts=thread_ts, text="No leads could be enriched with valid email addresses. Process terminated.")
return
app.state[csv_ts]["enriched_csv"] = enriched_csv
# Upload enriched CSV
print("Uploading enriched CSV...")
result = await client.files_upload_v2(
channel=channel,
content=enriched_csv,
filename=f"Enriched-Leads-{csv_ts}.csv",
title=f"Enriched-Leads-{csv_ts}.csv",
initial_comment="Here are your enriched leads:",
thread_ts=thread_ts
)
if not result["ok"]:
raise Exception(f"Error uploading enriched file: {result['error']}")
print("Enriched CSV uploaded successfully")
# Pause for 5 seconds
await asyncio.sleep(3)
# Generate email sequence
await client.chat_postMessage(channel=channel, thread_ts=thread_ts, text=f"Generating email sequence ({email_count} emails)...")
email_sequence = {}
for i in range(1, email_count + 1):
print(f"Generating email {i}...")
email_content = await generate_email(company_details, i, email_count)
email_sequence[i] = email_content
await client.chat_postMessage(channel=channel, thread_ts=thread_ts, text=f"> Email {i} generated:\n\n{email_content}")
# Add a short pause between each email generation
await asyncio.sleep(0.5)
# Format leads for HubSpot and Smartlead uploads
leads = prepare_leads_for_upload(enriched_csv)
# Add leads to HubSpot
print("Uploading leads to HubSpot...")
await client.chat_postMessage(channel=channel, thread_ts=thread_ts, text="> Uploading leads to HubSpot Contacts...")
hs_upload_result = await upload_leads_to_hubspot(leads)
print(f"HubSpot Upload result: {hs_upload_result}")
await client.chat_postMessage(channel=channel, thread_ts=thread_ts, text="Leads successfully added to HubSpot Contacts :hubspot:")
# Create Smartlead campaign and upload data
print("Creating Smartlead campaign...")
await client.chat_postMessage(channel=channel, thread_ts=thread_ts, text="> Email sequence drafted, creating Smartlead campaign...")
campaign_name = f"Campaign-{csv_ts}"
campaign_id = await create_smartlead_campaign(campaign_name)
print(f"Smartlead campaign created with ID: {campaign_id}")
print("Uploading leads to Smartlead...")
await client.chat_postMessage(channel=channel, thread_ts=thread_ts, text="Uploading leads to Smartlead...")
smartlead_upload_result = await upload_leads_to_smartlead(campaign_id, leads)
print(f"Smartlead upload result: {smartlead_upload_result}")
print("Saving campaign sequence...")
await client.chat_postMessage(channel=channel, thread_ts=thread_ts, text="Saving campaign sequence...")
sequence_result = await save_campaign_sequence(campaign_id, email_sequence)
print(f"Sequence save result: {sequence_result}")
# Get campaign details
print("Fetching campaign details...")
campaign_details = await get_campaign_by_id(campaign_id)
print(f"Campaign details: {campaign_details}")
# Post final message
final_message = f"Campaign created successfully in Smartlead:\n\n"
final_message += f":point_right: <https://app.smartlead.ai/app/email-campaign/{campaign_id}/analytics|Smartlead Campaign - {campaign_name}>\n\n"
final_message += f"Campaign Status: {campaign_details['status']}\n"
final_message += f"Uploaded Leads: {smartlead_upload_result['upload_count']}\n"
final_message += f"Total Emails in Sequence: {len(email_sequence)}\n"
await client.chat_postMessage(channel=channel, thread_ts=thread_ts, text=final_message)
except Exception as e:
print(f"Error in autonomous_process: {str(e)}")
error_message = f"An error occurred during the autonomous process: {str(e)}"
await client.chat_postMessage(channel=channel, thread_ts=thread_ts, text=error_message)
finally:
# Clear the cache for this conversation
if csv_ts in app.state:
del app.state[csv_ts]
await client.chat_postMessage(channel=channel, thread_ts=thread_ts, text="Autonomous process completed. All temporary data has been cleared.")
This function provides detailed feedback to the user at each step of the process, making it easier to track progress and identify any issues that may arise.
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 #
Congratulations! After deploying your Slack bot, you now have a live AI SDR in your workspace for you and your team to create and draft email campaigns.
If you want to make edits so the tool integrates into your specific needs, use the built-in Replit AI chat to ask questions and feature requests. The AI will guide you and recommend the necessary changes to the project.
Some ideas for features and improvements you could add include:
- Add the ability for the assistant to search Google for accounts that fit desired criteria instead of just using Apollo.ioImplement real-time campaign performance tracking and reporting from a Smartlead webhook.
- Add functionality for your AI SDR to automatically adjust outreach strategies based on performance data from both Smartlead and HubSpot.
- Improve the prompt to OpenAI to improve the copy of your email sequences.
- Explore integration with additional sales and marketing tools to create an even more comprehensive, AI-driven sales ecosystem.
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 implement these advanced features or bring this enhanced AI for lead generation and multi-platform outreach project to your team, feel free to schedule some time with the Replit team for a quick demo of our free AI tools for comprehensive and cross-platform sales development.
Happy coding and selling!