Outbound Sales Agent
A proactive voice agent that makes outbound calls for sales campaigns, appointment setting, and follow-ups.
Configuration
{
"agent": {
"name": "Outbound Sales",
"language": "en-US",
"llmProvider": "gemini-2.5",
"llmModel": "gemini-2.5-flash-lite",
"llmTemperature": 0.7,
"sttProvider": "deepgram",
"sttModel": "nova-3",
"ttsProvider": "cartesia",
"ttsVoice": "95856005-0332-41b0-935f-352e296aa0df",
"greetingMessage": "Hi {{contactName}}! This is Alex from {{companyName}}. I'm calling because {{reason}}. Do you have a quick minute?",
"prompt": "...",
"allowInterruptions": true
}
}
System Prompt
You are an outbound sales representative for {{companyName}}. Your goal is to engage prospects, share value, and schedule meetings with the sales team.
## Call Objective
{{callObjective}}
## About {{companyName}}
{{companyDescription}}
## Prospect Information
- Name: {{contactName}}
- Company: {{prospectCompany}}
- Role: {{contactRole}}
- Previous Interaction: {{previousInteraction}}
## Call Script Framework
### Opening (10 seconds)
Get permission to continue the conversation.
"Hi {{contactName}}! This is Alex from {{companyName}}.
I'm reaching out because [reason].
Do you have a quick minute?"
### Value Hook (20 seconds)
Share one compelling insight or benefit.
"The reason I'm calling is that we've been helping companies like [similar company]
[achieve specific result]. I thought it might be relevant for {{prospectCompany}}."
### Engagement Question
Ask an open-ended question to start dialogue.
"I'm curious - how is your team currently handling [problem area]?"
### Value Proposition
Based on their response, share relevant benefits.
### Close
Ask for a specific next step.
"Would you be open to a 15-minute call next week to show you how this works?"
## Objection Handling
### "I'm busy right now"
"Totally understand! What would be a better time for a 2-minute call?
I can share something that saved [customer] 10 hours/week."
### "Not interested"
"I hear you. Before I go - just curious, is it the timing or
the solution that doesn't fit right now?"
### "Send me an email"
"Happy to! What specifically would be most useful to include?
That way I can tailor it to what matters to you."
### "We already have a solution"
"Great that you're already addressing this! What made you go with [competitor]?
We often work alongside existing tools."
### "How did you get my number?"
"Fair question! [Explain source - trade show, referral, public directory].
If this isn't a good fit, I completely understand. Would you prefer I remove you?"
## Tone Guidelines
- Confident but not pushy
- Respectful of their time
- Focused on value, not features
- Curious and listening
- Professional but personable
## What NOT to Do
- Don't read a script robotically
- Don't push after clear rejection
- Don't make promises you can't keep
- Don't talk more than 50% of the time
- Don't ignore their responses
Campaign Setup
Initiating Outbound Calls
func runOutboundCampaign(campaignID string) {
campaign, _ := db.GetCampaign(campaignID)
contacts := db.GetCampaignContacts(campaignID)
for _, contact := range contacts {
// Check calling hours (9 AM - 6 PM local time)
if !isWithinCallingHours(contact.Timezone) {
scheduleForLater(contact)
continue
}
// Prepare variables
variables := map[string]string{
"contactName": contact.Name,
"prospectCompany": contact.Company,
"contactRole": contact.Role,
"companyName": campaign.CompanyName,
"companyDescription": campaign.CompanyDescription,
"callObjective": campaign.Objective,
"reason": campaign.CallReason,
"previousInteraction": contact.LastInteraction,
}
// Make call
initiateCall(campaign.AgentID, contact.Phone, variables)
// Respect rate limits and calling regulations
time.Sleep(campaign.DelayBetweenCalls)
}
}
API Call
curl -X POST https://api.edesy.in/v1/calls \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{
"agent_id": "outbound_sales",
"to": "+1234567890",
"variables": {
"contactName": "Sarah",
"prospectCompany": "Acme Corp",
"contactRole": "VP of Operations",
"companyName": "TechCo",
"callObjective": "schedule demo",
"reason": "I saw your recent post about scaling challenges",
"previousInteraction": "Downloaded case study last week"
},
"webhook_url": "https://your-server.com/webhooks/call-complete"
}'
Tools
Schedule Callback
{
"type": "function",
"function": {
"name": "schedule_callback",
"description": "Schedule a callback at a better time",
"parameters": {
"type": "object",
"properties": {
"contact_id": { "type": "string" },
"callback_time": {
"type": "string",
"description": "Preferred callback time (ISO 8601)"
},
"reason": {
"type": "string",
"description": "Why they want a callback"
}
},
"required": ["contact_id", "callback_time"]
}
}
}
Book Meeting
{
"type": "function",
"function": {
"name": "book_meeting",
"description": "Book a meeting with the sales team",
"parameters": {
"type": "object",
"properties": {
"contact_id": { "type": "string" },
"meeting_type": {
"type": "string",
"enum": ["discovery", "demo", "consultation"]
},
"preferred_times": {
"type": "array",
"items": { "type": "string" }
},
"attendees": {
"type": "array",
"items": { "type": "string" },
"description": "Additional attendees from prospect's side"
},
"notes": { "type": "string" }
},
"required": ["contact_id", "meeting_type"]
}
}
}
Update Contact Status
{
"type": "function",
"function": {
"name": "update_contact",
"description": "Update contact status and notes",
"parameters": {
"type": "object",
"properties": {
"contact_id": { "type": "string" },
"status": {
"type": "string",
"enum": ["interested", "callback", "not_interested", "no_answer", "wrong_number", "do_not_call"]
},
"notes": { "type": "string" },
"next_action": { "type": "string" }
},
"required": ["contact_id", "status"]
}
}
}
Send Follow-up Email
{
"type": "function",
"function": {
"name": "send_email",
"description": "Send follow-up email after call",
"parameters": {
"type": "object",
"properties": {
"contact_id": { "type": "string" },
"template": {
"type": "string",
"enum": ["intro", "meeting_invite", "resources", "case_study"]
},
"personalization": { "type": "string" }
},
"required": ["contact_id", "template"]
}
}
}
Tool Handlers
func handleBookMeeting(params map[string]any) (string, error) {
contactID := params["contact_id"].(string)
meetingType := params["meeting_type"].(string)
// Get available slots from sales team calendar
slots, _ := calendar.GetTeamAvailability(3) // 3 options
if len(slots) == 0 {
return "Let me have a colleague reach out with some times. What's the best email to send that to?", nil
}
// Format for speech
response := "Perfect! I have a few times available: "
for i, slot := range slots {
response += formatSlotForSpeech(slot)
if i < len(slots)-1 {
response += ", or "
}
}
response += ". Which works best?"
return response, nil
}
func handleSendEmail(params map[string]any) (string, error) {
contactID := params["contact_id"].(string)
template := params["template"].(string)
contact, _ := db.GetContact(contactID)
email := buildEmail(template, contact, params["personalization"])
err := emailService.Send(email)
if err != nil {
return "I'll make sure you get that email.", err
}
return fmt.Sprintf("Done! I just sent that to %s. You should see it shortly.", contact.Email), nil
}
Compliance & Regulations
Do Not Call (DNC) Handling
func preCallCheck(contact *Contact) error {
// Check internal DNC list
if db.IsOnDNCList(contact.Phone) {
return errors.New("contact is on DNC list")
}
// Check calling hours
if !isWithinCallingHours(contact.Timezone) {
return errors.New("outside calling hours")
}
// Check frequency cap
callsToday := db.GetCallCountToday(contact.Phone)
if callsToday >= 3 {
return errors.New("frequency cap reached")
}
return nil
}
Calling Hours
var callingHours = map[string]struct{ start, end int }{
"US": {9, 21}, // 9 AM - 9 PM local
"EU": {9, 20}, // 9 AM - 8 PM local (GDPR consideration)
"IN": {9, 21}, // 9 AM - 9 PM local
}
func isWithinCallingHours(timezone string) bool {
loc, _ := time.LoadLocation(timezone)
localTime := time.Now().In(loc)
hour := localTime.Hour()
region := getRegion(timezone)
hours := callingHours[region]
return hour >= hours.start && hour < hours.end
}
DNC Request Handling
If the contact asks to be removed or not called again:
"Absolutely, I've made a note and you won't receive any more calls from us.
I apologize for any inconvenience. Have a great day!"
→ Immediately mark as do_not_call
→ Add to DNC list
→ End call gracefully
Call Outcomes
type CallOutcome string
const (
OutcomeConnected CallOutcome = "connected"
OutcomeVoicemail CallOutcome = "voicemail"
OutcomeNoAnswer CallOutcome = "no_answer"
OutcomeBusy CallOutcome = "busy"
OutcomeInvalidNumber CallOutcome = "invalid_number"
)
type CallResult struct {
ContactID string
Outcome CallOutcome
Duration time.Duration
Status string // interested, callback, not_interested, etc.
MeetingScheduled bool
CallbackTime *time.Time
Notes string
Transcript string
}
func handleCallComplete(result CallResult) {
// Update CRM
crm.UpdateContact(result.ContactID, result)
// Track metrics
metrics.Increment("outbound.calls.total")
metrics.Increment("outbound.outcome." + string(result.Outcome))
if result.MeetingScheduled {
metrics.Increment("outbound.meetings.booked")
notifySalesRep(result)
}
// Schedule follow-ups
if result.Status == "callback" && result.CallbackTime != nil {
scheduleCallback(result.ContactID, *result.CallbackTime)
}
}
Voicemail Handling
When call goes to voicemail:
{
"voicemail": {
"enabled": true,
"detection": "auto",
"message": "Hi {{contactName}}, this is Alex from {{companyName}}. I was calling about {{reason}}. I'd love to connect briefly - you can reach me at 555-123-4567 or I'll try you again {{callbackDay}}. Looking forward to speaking with you!"
}
}
func handleVoicemail(contact *Contact, campaign *Campaign) {
voicemailMsg := buildVoicemailMessage(contact, campaign)
// Leave voicemail via TTS
tts.Speak(voicemailMsg)
// Update contact
db.UpdateContact(contact.ID, ContactUpdate{
Status: "voicemail_left",
NextAction: "follow_up_call",
NextActionAt: time.Now().Add(48 * time.Hour),
})
// Optionally send follow-up email
sendVoicemailFollowupEmail(contact)
}
Campaign Analytics
type CampaignMetrics struct {
TotalCalls int
Connected int
ConnectionRate float64
MeetingsBooked int
BookingRate float64
AverageCallTime time.Duration
CallsPerHour float64
BestTimeToCall string
TopObjections []string
}
func getCampaignMetrics(campaignID string) *CampaignMetrics {
calls := db.GetCampaignCalls(campaignID)
metrics := &CampaignMetrics{
TotalCalls: len(calls),
}
for _, call := range calls {
if call.Outcome == "connected" {
metrics.Connected++
}
if call.MeetingScheduled {
metrics.MeetingsBooked++
}
metrics.AverageCallTime += call.Duration
}
metrics.ConnectionRate = float64(metrics.Connected) / float64(metrics.TotalCalls)
metrics.BookingRate = float64(metrics.MeetingsBooked) / float64(metrics.Connected)
metrics.AverageCallTime /= time.Duration(len(calls))
return metrics
}
A/B Testing
Test different approaches:
{
"campaign": {
"id": "summer_outreach",
"variants": [
{
"id": "direct",
"weight": 50,
"greetingMessage": "Hi {{contactName}}! I have a quick question about your team's approach to [topic]. Got 30 seconds?"
},
{
"id": "value_first",
"weight": 50,
"greetingMessage": "Hi {{contactName}}! We just helped a company like yours save $50K. Thought it might be relevant - do you have a minute?"
}
]
}
}
Best Practices
1. Respect Their Time
Opening should be < 15 seconds.
Ask permission: "Do you have a quick minute?"
If not: "When would be better? I'll call back then."
2. Lead with Value
❌ "I'm calling to tell you about our product..."
✅ "I'm calling because we helped [similar company] reduce [problem] by 40%..."
3. Handle Rejection Gracefully
"I appreciate you taking my call. If anything changes, feel free to reach out."
→ Update status, respect their decision, don't push
4. Follow Up Appropriately
followUpRules := map[string]FollowUp{
"interested": {
Delay: 24 * time.Hour,
Action: "send_calendar_link",
},
"callback": {
Delay: time.Duration(0), // Use scheduled time
Action: "call_again",
},
"voicemail": {
Delay: 48 * time.Hour,
Action: "try_again",
},
"not_interested": {
Delay: 90 * 24 * time.Hour, // 90 days
Action: "nurture_email",
},
}
Next Steps
- Lead Qualification - Qualify inbound leads
- Customer Support - Support agent
- Webhooks - Call completion events