How to Build an AI Sales Development Representative (SDR) in Slack - Part 4: Campaign Creation

Introduction #
In this fourth part of our AI SDR series, we'll enhance our AI sales assistant with email campaign creation capabilities. With this template and guide, your AI SDR will not only find, enrich leads with emails, and generate personalized email sequences, but also create campaigns in Smartlead, significantly boosting your sales team's efficiency and outreach effectiveness.
This guide is Part 4 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-3 and the subsequent guides are linked at the bottom of this post.
Getting Started #
To begin enhancing your AI sales assistant with campaign creation and execution capabilities, fork this template by clicking "Use Template" below:
Smartlead API
For this enhanced version of our AI for lead generation and outreach, you'll need to get a Smartlead API key in the following way:
- Head to your Smartlead Profile Settings
- On the Your Profile page, you'll see a section titled Smartlead API Key
- Click the Copy button next to the key.
- Open your Replit Project to the Secrets tool. To find it, type ⌘ + K (or Ctrl + K) and search for "secrets"
- Paste in your key into the value for SMARTLEAD_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 campaign execution:
Smartlead Campaign Creation and Lead Upload
The create_smartlead_campaign and upload_leads_to_smartlead functions in utils.py handle the creation of campaigns and uploading of leads to Smartlead:
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
def create_smartlead_campaign(campaign_name, max_retries=5, retry_delay=1):
print(f"Creating Smartlead campaign: {campaign_name}")
url = f"https://server.smartlead.ai/api/v1/campaigns/create?api_key={os.environ['SMARTLEAD_API_KEY']}"
payload = {
"name": campaign_name
}
for attempt in range(max_retries):
try:
response = requests.post(url, json=payload)
print(f"Smartlead campaign creation response status: {response.status_code}")
if response.status_code == 429:
print(f"Rate limit hit, retrying in {retry_delay} seconds (attempt {attempt + 1}/{max_retries})")
time.sleep(retry_delay)
continue
response.raise_for_status()
result = response.json()
print(f"Smartlead campaign creation result: {result}")
return result['id']
except requests.exceptions.RequestException as e:
print(f"Error in create_smartlead_campaign (attempt {attempt + 1}/{max_retries}): {str(e)}")
if attempt < max_retries - 1:
time.sleep(retry_delay)
else:
raise Exception(f"Failed to create Smartlead campaign after {max_retries} attempts: {str(e)}")
raise Exception(f"Failed to create Smartlead campaign after {max_retries} attempts")
def upload_leads_to_smartlead(campaign_id, leads, max_retries=5, retry_delay=1):
print(f"Uploading {len(leads)} leads to Smartlead campaign {campaign_id}")
url = f"https://server.smartlead.ai/api/v1/campaigns/{campaign_id}/leads?api_key={os.environ['SMARTLEAD_API_KEY']}"
payload = {
"lead_list": leads,
"settings": {
"ignore_global_block_list": False,
"ignore_unsubscribe_list": False,
"ignore_community_bounce_list": False,
"ignore_duplicate_leads_in_other_campaign": False
}
}
for attempt in range(max_retries):
try:
response = requests.post(url, json=payload)
print(f"Smartlead lead upload response status: {response.status_code}")
if response.status_code == 429:
print(f"Rate limit hit, retrying in {retry_delay} seconds (attempt {attempt + 1}/{max_retries})")
time.sleep(retry_delay)
continue
response.raise_for_status()
result = response.json()
print(f"Smartlead lead upload result: {result}")
return result
except requests.exceptions.RequestException as e:
print(f"Error in upload_leads_to_smartlead (attempt {attempt + 1}/{max_retries}): {str(e)}")
if attempt < max_retries - 1:
time.sleep(retry_delay)
else:
raise Exception(f"Failed to upload leads to Smartlead after {max_retries} attempts: {str(e)}")
raise Exception(f"Failed to upload leads to Smartlead after {max_retries} attempts")
These functions create a new campaign in SmartLead and upload the enriched leads to that campaign.
Saving Email Sequences to Smartlead
The save_campaign_sequence function in utils.py saves the generated email sequence to the Smartlead campaign:
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
def save_campaign_sequence(campaign_id, emails, max_retries=5, retry_delay=1):
print(f"Saving sequence for Smartlead campaign {campaign_id}")
url = f"https://server.smartlead.ai/api/v1/campaigns/{campaign_id}/sequences?api_key={os.environ['SMARTLEAD_API_KEY']}"
payload = {
"sequences": [
{
"seq_number": i,
"seq_delay_details": {
"delay_in_days": 1 if i > 1 else 0
},
"variant_distribution_type": "MANUAL_EQUAL",
"seq_variants": [
{
"subject": email.split('\n', 1)[0].replace("Subject: ", ""),
"email_body": convert_to_html(email.split('\n', 1)[1]),
"variant_label": "A",
"variant_distribution_percentage": 100
}
]
} for i, email in enumerate(emails.values(), 1)
]
}
print(f"Sequence payload: {json.dumps(payload, indent=2)}")
for attempt in range(max_retries):
try:
response = requests.post(url, json=payload)
print(f"Smartlead sequence save response status: {response.status_code}")
if response.status_code == 429:
print(f"Rate limit hit, retrying in {retry_delay} seconds (attempt {attempt + 1}/{max_retries})")
time.sleep(retry_delay)
continue
response.raise_for_status()
result = response.json()
print(f"Smartlead sequence save result: {result}")
return result
except requests.exceptions.RequestException as e:
print(f"Error in save_campaign_sequence (attempt {attempt + 1}/{max_retries}): {str(e)}")
if attempt < max_retries - 1:
time.sleep(retry_delay)
else:
raise Exception(f"Failed to save campaign sequence after {max_retries} attempts: {str(e)}")
raise Exception(f"Failed to save campaign sequence after {max_retries} attempts")
This function takes the generated email sequence and saves it to the Smartlead campaign, setting up the entire outreach sequence.
Executing the Campaign Creation Process
In main.py, we've updated the post_final_email_sequence function to handle the entire campaign creation and execution process:
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
def post_final_email_sequence(client, csv_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"Upload result: {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. 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)}"
channel = app.state[csv_ts]["channel"]
thread_ts = app.state[csv_ts]["thread_ts"]
client.chat_postMessage(channel=channel, thread_ts=thread_ts, text=message)
This function orchestrates the entire process of creating a SmartLead campaign, uploading leads, saving the email sequence, and providing a summary of the created campaign to the user in Slack.
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 end-to-end tool for lead generation, email address enrichment, email sequence generation, and campaign creation.
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 (this guide) - Create Smartlead campaign
- AI SDR - Part 5 - Add Leads to HubSpot
- AI SDR - Part 6 (Final) - Agent Mode
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!