How to Build an AI Sales Development Representative (SDR) in Slack - Part 3: Email Sequence Generation
Introduction #
In this third part of our AI SDR series, we'll enhance our AI sales assistant with email sequence generation capabilities. This powerful feature transforms our AI-powered lead generation and qualification tool into a comprehensive outreach system. By the end of this guide, your AI SDR will not only find and qualify leads but also generate email sequences, significantly boosting your sales team's efficiency and outreach effectiveness.
This guide is Part 3 of a six-part series for how to build your own AI SDR (Sales Development Representative) in Slack. This template includes the code from both Parts 1 and 2 and the subsequent guides are linked at the bottom of this post.
Getting Started #
To begin enhancing your AI sales assistant with email sequence generation, fork this template by clicking "Use Template" below:
Additional API Setup for Your AI SDR
For this enhanced version of our AI for lead generation and outreach, you'll need to ensure you have the OpenAI API key set up:
OpenAI API Key
- Log in to the OpenAI developer platform.
- Navigate to API keys and create a new secret key.
- Copy the secret key and add it to Replit's Secrets tab as OPENAI_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, and email sequence generation:
Email Sequence Generation Process
The generate_email function in utils.py is the core of our email sequence generation 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
def generate_email(company_details, current_email, total_emails, lead_info=None):
api_key = os.environ["OPENAI_API_KEY"]
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {api_key}'
}
url = 'https://api.openai.com/v1/chat/completions'
system_message = """
You are an expert email marketer on a sales team.
Your job is to generate a single email template for a cold outreach campaign.
You will be provided with company details about the company that you are working for.
Based on the company details, your email should highlight the key benefits of the
product/service that you are working for.
"""
user_message = f"""
Company details: {company_details}
Email number: {current_email} of {total_emails}
Generate a single email for a cold outreach campaign.
Use the details given to tailor the content of the email.
"""
if lead_info:
user_message += "\nSample lead information:"
for lead in lead_info:
user_message += f"\n- Job title: {lead['title']}, Company: {lead['company_name']}"
if current_email == total_emails:
user_message += """
This should be a "break up" email. It should:
1. Acknowledge that the recipient might be busy
2. Provide a final piece of value (e.g., a case study or relevant link)
3. Leave the door open for future communication
4. Be brief and respectful
"""
user_message += """
Your output should be in the following format:
Subject: [Subject line]
[Email body]
Do not output anything else other than the email.
No conversation, no intro, no outro, no explanation, no comments.
"""
payload = {
'model': 'gpt-4o-mini',
'messages': [
{'role': 'system', 'content': system_message},
{'role': 'user', 'content': user_message}
],
'temperature': 0.7,
'max_tokens': 500
}
response = requests.post(url, headers=headers, json=payload)
result = response.json()['choices'][0]['message']['content']
return result.strip()
This function uses OpenAI's GPT model to generate personalized email content based on the company details, the current email in the sequence, and sample lead information.
Handling Email Sequence Generation in Slack
In main.py, we've added new functions to handle the email sequence generation 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
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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
@app.action("prepare_email_sequence")
def handle_prepare_email_sequence(ack, body, client):
ack()
csv_ts = body["actions"][0]["value"]
client.views_open(
trigger_id=body["trigger_id"],
view={
"type": "modal",
"callback_id": "email_count_modal",
"title": {"type": "plain_text", "text": "Email Sequence"},
"submit": {"type": "plain_text", "text": "Next"},
"blocks": [
{
"type": "section",
"text": {"type": "mrkdwn", "text": "How many emails would you like to include in your sequence?"}
},
{
"type": "actions",
"block_id": "email_count_selection",
"elements": [
{
"type": "button",
"text": {"type": "plain_text", "text": str(i)},
"value": f"{i}:{csv_ts}",
"action_id": f"select_email_count_{i}"
} for i in range(1, 7)
]
}
]
}
)
@app.action("select_email_count_1")
@app.action("select_email_count_2")
@app.action("select_email_count_3")
@app.action("select_email_count_4")
@app.action("select_email_count_5")
@app.action("select_email_count_6")
def handle_email_count_selection(ack, body, client):
ack()
selected_count = body["actions"][0]["value"].split(":")[0]
csv_ts = body["actions"][0]["value"].split(":")[1]
# Update the view to show the selected count
client.views_update(
view_id=body["view"]["id"],
view={
"type": "modal",
"callback_id": "email_count_modal",
"title": {"type": "plain_text", "text": "Email Sequence"},
"submit": {"type": "plain_text", "text": "Next"},
"blocks": [
{
"type": "section",
"text": {"type": "mrkdwn", "text": f"You've selected *{selected_count}* emails for your sequence."}
}
],
"private_metadata": json.dumps({"email_count": int(selected_count), "csv_ts": csv_ts})
}
)
@app.view("email_count_modal")
def handle_email_count_submission(ack, body, client, view):
ack()
metadata = json.loads(view["private_metadata"])
email_count = metadata["email_count"]
csv_ts = metadata["csv_ts"]
print(f"EMAIL COUNT SELECTED: {email_count}")
# Open company details modal
open_company_details_modal(client, body["trigger_id"], email_count, csv_ts)
def open_company_details_modal(client, trigger_id, email_count, csv_ts):
modal = {
"type": "modal",
"callback_id": "company_details_modal",
"title": {"type": "plain_text", "text": "Your Company Details"},
"submit": {"type": "plain_text", "text": "Submit"},
"blocks": [
{
"type": "input",
"block_id": "company_name",
"label": {"type": "plain_text", "text": "Company Name"},
"element": {"type": "plain_text_input", "action_id": "company_name_input"}
},
{
"type": "input",
"block_id": "company_description",
"label": {"type": "plain_text", "text": "Company Description - What do you sell?"},
"element": {"type": "plain_text_input", "action_id": "company_description_input", "multiline": True}
}
],
"private_metadata": json.dumps({"email_count": email_count, "csv_ts": csv_ts})
}
client.views_open(trigger_id=trigger_id, view=modal)
@app.view("company_details_modal")
def handle_company_details_submission(ack, body, client, view):
ack()
metadata = json.loads(view["private_metadata"])
company_details = {
"name": view["state"]["values"]["company_name"]["company_name_input"]["value"],
"description": view["state"]["values"]["company_description"]["company_description_input"]["value"]
}
email_count = metadata["email_count"]
csv_ts = metadata["csv_ts"]
# Generate first email and open email review modal
generate_and_open_email_modal(client, body["trigger_id"], company_details, email_count, 1, csv_ts)
def generate_and_open_email_modal(client, trigger_id, company_details, total_emails, current_email, csv_ts):
# Show loading indicator
loading_view = client.views_open(
trigger_id=trigger_id,
view={
"type": "modal",
"title": {"type": "plain_text", "text": "Generating Email"},
"blocks": [
{
"type": "section",
"text": {"type": "mrkdwn", "text": "Please wait while we generate the email..."}
}
]
}
)
# Get lead information from the enriched CSV
enriched_csv = app.state[csv_ts].get("enriched_csv", "")
lead_info = get_random_lead_info(enriched_csv, sample_size=3)
# Generate email using GPT
email_content = generate_email(company_details, current_email, total_emails, lead_info)
# Update the modal with the generated email content
modal = {
"type": "modal",
"callback_id": f"email_review_modal_{current_email}",
"title": {"type": "plain_text", "text": f"Review Email {current_email}"},
"submit": {"type": "plain_text", "text": "Confirm"},
"blocks": [
{
"type": "input",
"block_id": "email_content",
"label": {"type": "plain_text", "text": f"Email {current_email} Content"},
"element": {
"type": "plain_text_input",
"action_id": "email_content_input",
"multiline": True,
"initial_value": email_content
}
}
],
"private_metadata": json.dumps({
"company_details": company_details,
"total_emails": total_emails,
"current_email": current_email,
"csv_ts": csv_ts
})
}
client.views_update(
view_id=loading_view["view"]["id"],
view=modal
)
@app.view("email_review_modal_1")
@app.view("email_review_modal_2")
@app.view("email_review_modal_3")
@app.view("email_review_modal_4")
@app.view("email_review_modal_5")
@app.view("email_review_modal_6")
def handle_email_review_submission(ack, body, client, view):
ack()
metadata = json.loads(view["private_metadata"])
company_details = metadata["company_details"]
total_emails = metadata["total_emails"]
current_email = metadata["current_email"]
csv_ts = metadata["csv_ts"]
# Save the confirmed email content
email_content = view["state"]["values"]["email_content"]["email_content_input"]["value"]
app.state.setdefault(csv_ts, {}).setdefault("emails", {})[current_email] = email_content
if current_email < total_emails:
# Generate next email and open new review modal
generate_and_open_email_modal(client, body["trigger_id"], company_details, total_emails, current_email + 1, csv_ts)
else:
# All emails reviewed, post final sequence
post_final_email_sequence(client, csv_ts)
def post_final_email_sequence(client, csv_ts):
email_sequence = app.state[csv_ts]["emails"]
message = "Final Email Sequence:\n\n"
for i, email in email_sequence.items():
message += f"Email {i}:\n{email}\n\n"
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)
These functions handle the process of generating an email sequence, from selecting the number of emails to generating and reviewing each email in the sequence.
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 #
In the next part of this series, we'll be adding the ability for the assistant to add your leads and your drafted email sequence into a Smartlead outbound campaign. The other parts of the series include:
- AI SDR - Part 1 - Lead List Creation
- AI SDR - Part 2 - Lead Enrichment
- AI SDR - Part 3 (this guide) - Draft Email Copy
- AI SDR - Part 4 - 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!