We are going to build a sales outreach agent that reads customer context from an existing CRM and drafts personalized follow-up emails. This helps sales teams automate pipeline touchpoints without replacing their current tools. I will use Oxlo.ai as the inference backend because its request-based pricing keeps costs flat even when I stuff long CRM histories into the prompt.
What you'll need
- Python 3.10 or newer
- The OpenAI SDK:
pip install openai - An Oxlo.ai API key from https://portal.oxlo.ai
Step 1: Init the Oxlo.ai client
I start by importing the SDK and pointing the client at Oxlo.ai. This is a drop-in replacement for the standard OpenAI client.
from openai import OpenAI
client = OpenAI(base_url="https://api.oxlo.ai/v1", api_key="YOUR_OXLO_API_KEY")
Step 2: Create a mock CRM
Most teams already have a CRM, so I will simulate one with a local SQLite database containing leads and interaction history.
import sqlite3
def init_crm(db_path="crm.db"):
conn = sqlite3.connect(db_path)
cur = conn.cursor()
cur.execute("""
CREATE TABLE IF NOT EXISTS leads (
id INTEGER PRIMARY KEY,
name TEXT,
company TEXT,
status TEXT,
last_contact TEXT
)
""")
cur.execute("""
CREATE TABLE IF NOT EXISTS interactions (
id INTEGER PRIMARY KEY,
lead_id INTEGER,
note TEXT,
created_at TEXT
)
""")
cur.execute(
"INSERT OR IGNORE INTO leads VALUES "
"(1, 'Alice Smith', 'Acme Corp', 'Qualified', '2025-05-01')"
)
cur.execute(
"INSERT OR IGNORE INTO interactions VALUES "
"(1, 1, 'Alice asked for pricing on 2025-05-01. She is comparing vendors.', '2025-05-01')"
)
conn.commit()
return conn
Step 3: Fetch lead context
I need a helper that pulls the lead record and all related notes so the LLM has full context to work with.
def get_lead_context(conn, lead_id):
cur = conn.cursor()
cur.execute(
"SELECT name, company, status, last_contact FROM leads WHERE id = ?",
(lead_id,),
)
lead = cur.fetchone()
cur.execute(
"SELECT note, created_at FROM interactions WHERE lead_id = ? ORDER BY created_at",
(lead_id,),
)
notes = cur.fetchall()
return {"lead": lead, "notes": notes}
Step 4: Define the system prompt
The system prompt grounds the model in the CRM data and constrains the output format.
SYSTEM_PROMPT = """You are a sales assistant that drafts personalized outreach emails based on CRM context.
You will receive a JSON blob containing a lead's profile and interaction history.
Draft a concise, professional follow-up email.
Rules:
- Reference specific details from the interaction history.
- Do not invent facts not present in the context.
- Output only the email body, no subject line or salutation prefix like "Email:".
- Keep the tone consultative, not pushy.
"""
Step 5: Build the outreach generator
This function assembles the CRM context into a user message and calls Oxlo.ai. I am using Llama 3.3 70B because it handles structured instructions and long context reliably.
import json
def draft_outreach(lead_context):
user_message = json.dumps(lead_context, indent=2)
response = client.chat.completions.create(
model="llama-3.3-70b",
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": user_message},
],
)
return response.choices[0].message.content
Run it
Now I wire the pieces together, execute the pipeline, and print the result.
if __name__ == "__main__":
conn = init_crm()
context = get_lead_context(conn, 1)
email_body = draft_outreach(context)
print("--- Generated Outreach ---")
print(email_body)
Example output:
--- Generated Outreach ---
Hi Alice,
I hope you have had a chance to review the pricing we shared on May 1st. I know Acme Corp is evaluating vendors right now, so I wanted to check in and see if any questions have come up on your end.
Let me know if you need a deeper walkthrough of the specific features we discussed.
Best regards,
Sales Team
Wrap-up
To productionize this, connect the script to your live CRM via its REST API instead of SQLite, and schedule it with a cron job or a lightweight task queue like Celery. If you are processing large batches of leads with lengthy interaction histories, Oxlo.ai's request-based pricing means your bill stays flat regardless of how much context you include in each prompt. You can explore the details at https://oxlo.ai/pricing.
Top comments (0)