GuidesHow to Build an AI Sales Development Representative (SDR) in Slack - Part 3: Email Sequence Generation

How to Build an AI Sales Development Representative (SDR) in Slack - Part 3: Email Sequence Generation

A guide by

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

OpenAI "Create new secret key" modal UI
  • 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.

Replit Deployments selection UI

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:

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!