AI Voice Agents for Marketing Campaigns
Deploy AI-powered voice agents for scalable marketing campaigns that drive engagement, collect valuable customer insights, and convert prospects while maintaining full regulatory compliance.
Use Cases Overview
| Use Case |
Description |
Typical Duration |
Best Time |
| Promotional Calls |
Share offers, discounts, limited-time deals |
45-90 seconds |
10 AM - 12 PM, 4-6 PM |
| Product Announcements |
Introduce new products/features to existing customers |
60-120 seconds |
11 AM - 1 PM |
| Survey Collection |
Gather feedback, NPS scores, market research |
2-4 minutes |
10 AM - 12 PM |
| Event Invitations |
Webinars, conferences, product launches |
60-90 seconds |
2-4 PM |
| Feedback Calls |
Post-purchase satisfaction, service reviews |
2-3 minutes |
24-48 hours post-interaction |
Agent Configuration
Marketing Campaign Agent
{
"agent": {
"name": "Marketing Campaign Agent",
"language": "en-US",
"llmProvider": "gemini-2.5",
"llmModel": "gemini-2.5-flash-lite",
"llmTemperature": 0.7,
"sttProvider": "deepgram",
"sttModel": "nova-3",
"sttConfig": {
"endpointing": 400,
"interimResults": true,
"utteranceEndMs": 1200
},
"ttsProvider": "cartesia",
"ttsVoice": "95856005-0332-41b0-935f-352e296aa0df",
"greetingMessage": "Hi {{contactName}}! This is {{agentName}} from {{companyName}}. I'm calling with {{campaignHook}}. Do you have a quick moment?",
"prompt": "...",
"allowInterruptions": true,
"endCallPhrases": ["not interested", "remove me", "do not call", "stop calling"],
"maxDuration": 180,
"webhooks": {
"url": "https://your-server.com/webhooks/marketing",
"events": ["call.started", "call.ended", "opt_out.requested"]
}
}
}
Multi-Language Campaign (Hindi)
{
"agent": {
"name": "Hindi Marketing Agent",
"language": "hi-IN",
"llmProvider": "gemini-2.5",
"llmModel": "gemini-2.5-flash-lite",
"sttProvider": "deepgram",
"sttModel": "nova-3",
"sttConfig": {
"language": "hi"
},
"ttsProvider": "azure",
"ttsVoice": "hi-IN-SwaraNeural",
"greetingMessage": "{{contactName}} ji, namaste! Main {{agentName}} bol rahi hoon {{companyName}} se. {{campaignHook}} ke baare mein baat karni thi. Kya aapke paas ek minute hai?",
"prompt": "..."
}
}
System Prompt Templates
You are a friendly marketing representative for {{companyName}}. Your goal is to share an exclusive offer with customers and gauge their interest.
## Campaign Details
- Campaign: {{campaignName}}
- Offer: {{offerDetails}}
- Valid Until: {{offerExpiry}}
- Promo Code: {{promoCode}}
## Customer Context
- Name: {{contactName}}
- Customer Since: {{customerSince}}
- Last Purchase: {{lastPurchase}}
- Segment: {{customerSegment}}
## Call Script
### Opening (10 seconds)
Get permission to continue. Be warm and respectful of their time.
"Hi {{contactName}}! This is {{agentName}} from {{companyName}}.
I'm reaching out with a special offer for our valued customers.
Do you have 30 seconds?"
### Value Proposition (20 seconds)
Share the offer clearly and concisely.
"As a thank you for being with us since {{customerSince}}, we're offering you
{{offerDetails}}. This is an exclusive offer just for customers like you."
### Interest Check
Ask if they're interested without being pushy.
"Does this sound like something you'd find useful?"
### Positive Response
If interested, provide clear next steps.
"Great! You can use code {{promoCode}} at checkout.
Shall I send the details to your registered email/phone?"
### Negative Response
Respect their decision and exit gracefully.
"No problem at all! Just wanted to make sure you knew about it.
Have a great day, {{contactName}}!"
## Key Rules
1. NEVER pressure or manipulate
2. Accept "no" gracefully on first decline
3. Keep total call under 90 seconds
4. Always offer opt-out if requested
5. Don't repeat the same pitch twice
6. Be conversational, not scripted
7. If they say "not interested" or "remove me", immediately offer opt-out
## DND Handling
If customer requests to be removed:
"Absolutely! I'll remove you from our call list right away.
My apologies for any inconvenience. Have a great day!"
-> Call opt_out_customer function immediately
-> End call gracefully
Survey Collection Prompt
You are conducting a customer satisfaction survey for {{companyName}}. Your goal is to collect honest feedback in a conversational manner.
## Survey Details
- Survey: {{surveyName}}
- Questions: {{questionCount}} questions
- Estimated Time: {{estimatedTime}}
- Incentive: {{surveyIncentive}}
## Customer Context
- Name: {{contactName}}
- Recent Interaction: {{recentInteraction}}
- Interaction Date: {{interactionDate}}
## Survey Flow
### Opening
"Hi {{contactName}}! This is {{agentName}} from {{companyName}}.
We value your feedback on {{recentInteraction}}.
Would you have {{estimatedTime}} for a quick survey? {{surveyIncentive}}"
### Questions
Ask each question naturally, acknowledging their responses.
{{surveyQuestions}}
### Rating Scale Explanation
For rating questions, explain the scale clearly:
"On a scale of 1 to 10, where 1 is very unsatisfied and 10 is extremely satisfied..."
### Follow-up on Low Scores
If score < 7: "I appreciate your honesty. Could you tell me what we could do better?"
### Closing
"Thank you so much for your time, {{contactName}}!
Your feedback helps us serve you better.
{{closingMessage}}"
## Guidelines
1. One question at a time
2. Acknowledge each response before moving on
3. Don't argue with negative feedback
4. If they want to elaborate, let them
5. Record all verbatim comments
6. Thank them regardless of feedback sentiment
Event Invitation Prompt
You are inviting customers to {{eventName}} for {{companyName}}.
## Event Details
- Event: {{eventName}}
- Date: {{eventDate}}
- Time: {{eventTime}}
- Format: {{eventFormat}} (webinar/in-person/hybrid)
- Topic: {{eventTopic}}
- Speakers: {{eventSpeakers}}
- Registration Link: {{registrationUrl}}
## Customer Context
- Name: {{contactName}}
- Interest Areas: {{interestAreas}}
- Past Event Attendance: {{pastAttendance}}
## Call Script
### Opening
"Hi {{contactName}}! This is {{agentName}} from {{companyName}}.
I'm calling about our upcoming {{eventFormat}} on {{eventTopic}}.
Given your interest in {{interestAreas}}, I thought you'd find it valuable.
Do you have a minute?"
### Event Pitch
"We're hosting {{eventName}} on {{eventDate}} at {{eventTime}}.
{{eventSpeakers}} will be covering {{eventTopic}}.
{{eventHighlight}}"
### Interest Check
"Would you be interested in joining us?"
### Registration
If interested:
"Great! I can register you right now. Can I confirm your email is {{customerEmail}}?"
OR
"I'll send you the registration link. You can sign up at your convenience."
### Not Interested
"No problem! Would you like me to keep you posted about future events on {{interestAreas}}?"
## Guidelines
1. Highlight relevance to their interests
2. Keep it brief - they can learn more online
3. Make registration easy
4. Offer calendar invite option
5. Respect time zones for event times
Campaign Management
{
"type": "function",
"function": {
"name": "record_campaign_response",
"description": "Record customer response to marketing campaign",
"parameters": {
"type": "object",
"properties": {
"contact_id": {
"type": "string",
"description": "Customer contact ID"
},
"campaign_id": {
"type": "string",
"description": "Campaign identifier"
},
"response": {
"type": "string",
"enum": ["interested", "not_interested", "callback", "already_purchased", "wrong_number"],
"description": "Customer response"
},
"interest_level": {
"type": "integer",
"minimum": 1,
"maximum": 10,
"description": "Interest level 1-10"
},
"follow_up_needed": {
"type": "boolean",
"description": "Whether follow-up is needed"
},
"notes": {
"type": "string",
"description": "Additional notes from conversation"
}
},
"required": ["contact_id", "campaign_id", "response"]
}
}
}
Opt-Out Handling
{
"type": "function",
"function": {
"name": "opt_out_customer",
"description": "Add customer to Do Not Call list and stop all marketing communications",
"parameters": {
"type": "object",
"properties": {
"contact_id": {
"type": "string",
"description": "Customer contact ID"
},
"phone_number": {
"type": "string",
"description": "Phone number to add to DNC"
},
"opt_out_type": {
"type": "string",
"enum": ["all_calls", "marketing_only", "specific_campaign"],
"description": "Type of opt-out requested"
},
"reason": {
"type": "string",
"description": "Reason for opt-out if provided"
},
"campaign_id": {
"type": "string",
"description": "Campaign ID if specific campaign opt-out"
}
},
"required": ["contact_id", "phone_number", "opt_out_type"]
}
}
}
Survey Response Collection
{
"type": "function",
"function": {
"name": "record_survey_response",
"description": "Record customer survey response",
"parameters": {
"type": "object",
"properties": {
"survey_id": {
"type": "string",
"description": "Survey identifier"
},
"contact_id": {
"type": "string",
"description": "Customer contact ID"
},
"question_id": {
"type": "string",
"description": "Question identifier"
},
"response_type": {
"type": "string",
"enum": ["rating", "multiple_choice", "open_text", "yes_no"],
"description": "Type of response"
},
"response_value": {
"type": "string",
"description": "The actual response"
},
"sentiment": {
"type": "string",
"enum": ["positive", "neutral", "negative"],
"description": "Detected sentiment"
}
},
"required": ["survey_id", "contact_id", "question_id", "response_value"]
}
}
}
Event Registration
{
"type": "function",
"function": {
"name": "register_for_event",
"description": "Register customer for an event",
"parameters": {
"type": "object",
"properties": {
"event_id": {
"type": "string",
"description": "Event identifier"
},
"contact_id": {
"type": "string",
"description": "Customer contact ID"
},
"email": {
"type": "string",
"description": "Email for confirmation"
},
"send_calendar_invite": {
"type": "boolean",
"description": "Whether to send calendar invite"
},
"reminder_preference": {
"type": "string",
"enum": ["email", "sms", "both", "none"],
"description": "Reminder notification preference"
}
},
"required": ["event_id", "contact_id"]
}
}
}
{
"type": "function",
"function": {
"name": "send_follow_up",
"description": "Send follow-up SMS or email with offer details",
"parameters": {
"type": "object",
"properties": {
"contact_id": {
"type": "string",
"description": "Customer contact ID"
},
"channel": {
"type": "string",
"enum": ["sms", "email", "whatsapp"],
"description": "Communication channel"
},
"template": {
"type": "string",
"enum": ["offer_details", "event_registration", "survey_link", "promo_code"],
"description": "Message template to use"
},
"personalization": {
"type": "object",
"description": "Custom variables for template"
}
},
"required": ["contact_id", "channel", "template"]
}
}
}
package handlers
import (
"context"
"fmt"
"time"
)
// OptOutHandler handles DNC requests with compliance logging
func OptOutHandler(params map[string]any) (string, error) {
contactID := params["contact_id"].(string)
phoneNumber := params["phone_number"].(string)
optOutType := params["opt_out_type"].(string)
// Add to internal DNC list immediately
err := dncService.AddToDNCList(DNCEntry{
PhoneNumber: phoneNumber,
ContactID: contactID,
OptOutType: optOutType,
Timestamp: time.Now(),
Source: "voice_agent",
})
if err != nil {
log.Error("Failed to add to DNC", "error", err)
return "I'll make sure to update our records.", err
}
// Log for compliance audit
auditLog.Record(AuditEntry{
Action: "OPT_OUT",
ContactID: contactID,
PhoneNumber: phoneNumber,
Type: optOutType,
Timestamp: time.Now(),
})
// Cancel any scheduled campaigns for this contact
campaignService.CancelScheduledCalls(contactID)
return "Done. I've removed you from our call list. You won't receive any more marketing calls from us. I apologize for any inconvenience.", nil
}
// RecordCampaignResponse tracks campaign engagement
func RecordCampaignResponse(params map[string]any) (string, error) {
response := CampaignResponse{
ContactID: params["contact_id"].(string),
CampaignID: params["campaign_id"].(string),
Response: params["response"].(string),
Timestamp: time.Now(),
}
if interestLevel, ok := params["interest_level"].(float64); ok {
response.InterestLevel = int(interestLevel)
}
if notes, ok := params["notes"].(string); ok {
response.Notes = notes
}
if followUp, ok := params["follow_up_needed"].(bool); ok {
response.FollowUpNeeded = followUp
}
err := campaignDB.SaveResponse(response)
if err != nil {
return "", err
}
// Update campaign metrics in real-time
metrics.IncrementResponse(response.CampaignID, response.Response)
// Trigger follow-up workflow if needed
if response.FollowUpNeeded {
workflowEngine.TriggerFollowUp(response)
}
return "Response recorded successfully.", nil
}
// RecordSurveyResponse handles survey data collection
func RecordSurveyResponse(params map[string]any) (string, error) {
surveyResponse := SurveyResponse{
SurveyID: params["survey_id"].(string),
ContactID: params["contact_id"].(string),
QuestionID: params["question_id"].(string),
ResponseValue: params["response_value"].(string),
Timestamp: time.Now(),
}
if responseType, ok := params["response_type"].(string); ok {
surveyResponse.ResponseType = responseType
}
if sentiment, ok := params["sentiment"].(string); ok {
surveyResponse.Sentiment = sentiment
}
err := surveyDB.SaveResponse(surveyResponse)
if err != nil {
return "", err
}
// For NPS surveys, trigger alerts on detractor scores
if surveyResponse.ResponseType == "rating" {
score := parseRating(surveyResponse.ResponseValue)
if score <= 6 {
alertService.NotifyDetractor(surveyResponse)
}
}
return "Thank you for that feedback.", nil
}
Compliance Framework
Regulatory Requirements
India (TRAI/NCPR)
// TRAI Compliance for India
type TRAICompliance struct {
// National Customer Preference Register
NCPREnabled bool
// Calling hours: 9 AM - 9 PM (customer's local time)
CallingHoursStart int // 9
CallingHoursEnd int // 21
// Maximum calls per day per number
MaxCallsPerDay int // 3
// Mandatory scrubbing against NCPR before campaign
NCPRScrubRequired bool
// Cool-off period after opt-out (days)
OptOutCoolOffDays int // 0 (immediate)
// Registration requirements
HeaderRegistered bool
TemplateApproved bool
}
func validateTRAICompliance(contact *Contact, campaign *Campaign) error {
// Check NCPR registration
if isOnNCPR(contact.Phone) {
return errors.New("contact is registered on NCPR")
}
// Validate calling hours
localTime := getLocalTime(contact.Timezone)
if localTime.Hour() < 9 || localTime.Hour() >= 21 {
return errors.New("outside permitted calling hours (9 AM - 9 PM)")
}
// Check daily call limit
callsToday := getCallCountToday(contact.Phone)
if callsToday >= 3 {
return errors.New("daily call limit reached for this number")
}
// Verify sender header registration
if !isHeaderRegistered(campaign.SenderID) {
return errors.New("sender header not registered with TRAI")
}
return nil
}
United States (TCPA)
// TCPA Compliance for US
type TCPACompliance struct {
// Prior express written consent required for marketing
ConsentRequired bool
// Calling hours: 8 AM - 9 PM (recipient's local time)
CallingHoursStart int // 8
CallingHoursEnd int // 21
// National Do Not Call Registry check
DNDRegistryCheck bool
// Internal DNC list maintenance (minimum 5 years)
InternalDNCRetentionYears int // 5
// Abandoned call rate < 3%
MaxAbandonedCallRate float64 // 0.03
// Caller ID requirements
CallerIDRequired bool
CallerIDAccurate bool
}
func validateTCPACompliance(contact *Contact, campaign *Campaign) error {
// Verify prior express consent
consent, err := getMarketingConsent(contact.ID)
if err != nil || !consent.Valid {
return errors.New("no valid marketing consent on file")
}
// Check National DNC Registry
if isOnNationalDNC(contact.Phone) {
return errors.New("contact is on National DNC Registry")
}
// Check internal DNC list
if isOnInternalDNC(contact.Phone) {
return errors.New("contact is on internal DNC list")
}
// Validate calling hours
localTime := getLocalTime(contact.Timezone)
if localTime.Hour() < 8 || localTime.Hour() >= 21 {
return errors.New("outside permitted calling hours (8 AM - 9 PM)")
}
return nil
}
European Union (GDPR + ePrivacy)
// GDPR/ePrivacy Compliance for EU
type GDPRCompliance struct {
// Explicit opt-in consent required
ExplicitConsentRequired bool
// Purpose limitation
ConsentPurposeSpecific bool
// Right to be forgotten
DataDeletionSupported bool
// Consent record retention
ConsentRecordRetentionYears int // 7
// Country-specific calling hours
CallingHoursByCountry map[string]CallingHours
}
func validateGDPRCompliance(contact *Contact, campaign *Campaign) error {
// Verify explicit opt-in consent
consent, err := getGDPRConsent(contact.ID, campaign.Purpose)
if err != nil || !consent.ExplicitOptIn {
return errors.New("no explicit GDPR consent for this purpose")
}
// Verify consent hasn't been withdrawn
if consent.Withdrawn {
return errors.New("consent has been withdrawn")
}
// Check country-specific regulations
if contact.Country == "DE" { // Germany - stricter rules
if !consent.DoubleOptIn {
return errors.New("double opt-in required for Germany")
}
}
return nil
}
DND/DNC List Management
// Comprehensive DNC Management
type DNCManager struct {
db *sql.DB
cache *redis.Client
ncprClient *NCPRClient // India
ftcClient *FTCDNCClient // US
}
// Pre-campaign scrubbing
func (m *DNCManager) ScrubCampaignList(contacts []Contact, region string) ([]Contact, []Contact) {
var eligible, blocked []Contact
for _, contact := range contacts {
blocked_reason := m.checkDNCStatus(contact, region)
if blocked_reason == "" {
eligible = append(eligible, contact)
} else {
contact.BlockedReason = blocked_reason
blocked = append(blocked, contact)
}
}
return eligible, blocked
}
func (m *DNCManager) checkDNCStatus(contact Contact, region string) string {
// Check internal DNC first (fastest)
if m.isOnInternalDNC(contact.Phone) {
return "internal_dnc"
}
// Check regional registries
switch region {
case "IN":
if m.ncprClient.IsRegistered(contact.Phone) {
return "ncpr_registered"
}
case "US":
if m.ftcClient.IsOnRegistry(contact.Phone) {
return "ftc_dnc_registry"
}
}
// Check opt-out history
if m.hasOptedOut(contact.ID) {
return "previous_optout"
}
return ""
}
// Real-time opt-out processing
func (m *DNCManager) ProcessOptOut(request OptOutRequest) error {
// Add to internal DNC immediately
err := m.db.Exec(`
INSERT INTO dnc_list (phone_number, contact_id, opt_out_type, source, created_at)
VALUES ($1, $2, $3, $4, NOW())
ON CONFLICT (phone_number) DO UPDATE SET
opt_out_type = EXCLUDED.opt_out_type,
updated_at = NOW()
`, request.PhoneNumber, request.ContactID, request.OptOutType, request.Source)
if err != nil {
return err
}
// Invalidate cache
m.cache.Del(context.Background(), "dnc:"+request.PhoneNumber)
// Notify all campaign systems
m.broadcastOptOut(request)
return nil
}
Consent Management
// Consent tracking and validation
type ConsentRecord struct {
ContactID string
Purpose string // "marketing", "surveys", "product_updates"
ConsentType string // "explicit", "implicit", "double_optin"
ConsentMethod string // "web_form", "verbal", "written", "sms"
ConsentText string // Exact consent language shown
Timestamp time.Time
ExpiresAt *time.Time // Some jurisdictions require renewal
Withdrawn bool
WithdrawnAt *time.Time
IPAddress string // For web consent
CallRecording string // For verbal consent (recording URL)
}
func validateConsent(contactID string, purpose string, region string) (*ConsentRecord, error) {
consent, err := db.GetConsent(contactID, purpose)
if err != nil {
return nil, fmt.Errorf("no consent record found")
}
// Check if consent is still valid
if consent.Withdrawn {
return nil, fmt.Errorf("consent withdrawn on %v", consent.WithdrawnAt)
}
// Check expiration (if applicable)
if consent.ExpiresAt != nil && time.Now().After(*consent.ExpiresAt) {
return nil, fmt.Errorf("consent expired on %v", consent.ExpiresAt)
}
// Region-specific validation
switch region {
case "EU":
if consent.ConsentType != "explicit" {
return nil, fmt.Errorf("explicit consent required for EU")
}
case "US":
if consent.ConsentMethod == "verbal" && consent.CallRecording == "" {
return nil, fmt.Errorf("verbal consent requires call recording")
}
}
return consent, nil
}
Campaign Metrics
type CampaignMetrics struct {
// Reach Metrics
TotalContacts int
ContactsAttempted int
ContactsReached int
ReachRate float64 // ContactsReached / ContactsAttempted
// Engagement Metrics
CallsAnswered int
AnswerRate float64
AvgCallDuration time.Duration
ListenThroughRate float64 // % who listened to full message
// Response Metrics
PositiveResponses int
NegativeResponses int
CallbacksRequested int
ResponseRate float64
// Conversion Metrics
Conversions int
ConversionRate float64 // Conversions / ContactsReached
RevenueGenerated float64
CostPerConversion float64
// Compliance Metrics
OptOuts int
OptOutRate float64
Complaints int
ComplianceViolations int
// Survey-Specific (if applicable)
SurveyCompletions int
CompletionRate float64
AverageNPS float64
}
func calculateCampaignMetrics(campaignID string) *CampaignMetrics {
calls := db.GetCampaignCalls(campaignID)
metrics := &CampaignMetrics{
TotalContacts: len(calls),
}
var totalDuration time.Duration
for _, call := range calls {
metrics.ContactsAttempted++
if call.Status == "answered" {
metrics.CallsAnswered++
metrics.ContactsReached++
totalDuration += call.Duration
switch call.Outcome {
case "interested", "converted":
metrics.PositiveResponses++
case "not_interested":
metrics.NegativeResponses++
case "callback":
metrics.CallbacksRequested++
case "opt_out":
metrics.OptOuts++
}
if call.Converted {
metrics.Conversions++
metrics.RevenueGenerated += call.ConversionValue
}
}
}
// Calculate rates
if metrics.ContactsAttempted > 0 {
metrics.ReachRate = float64(metrics.ContactsReached) / float64(metrics.ContactsAttempted)
metrics.AnswerRate = float64(metrics.CallsAnswered) / float64(metrics.ContactsAttempted)
}
if metrics.ContactsReached > 0 {
metrics.ConversionRate = float64(metrics.Conversions) / float64(metrics.ContactsReached)
metrics.OptOutRate = float64(metrics.OptOuts) / float64(metrics.ContactsReached)
metrics.ResponseRate = float64(metrics.PositiveResponses+metrics.NegativeResponses) / float64(metrics.ContactsReached)
}
if metrics.CallsAnswered > 0 {
metrics.AvgCallDuration = totalDuration / time.Duration(metrics.CallsAnswered)
}
if metrics.Conversions > 0 {
campaignCost := getCampaignCost(campaignID)
metrics.CostPerConversion = campaignCost / float64(metrics.Conversions)
}
return metrics
}
Real-Time Dashboard
// Streaming metrics for live dashboards
type LiveCampaignStats struct {
CampaignID string `json:"campaign_id"`
ActiveCalls int `json:"active_calls"`
CallsThisHour int `json:"calls_this_hour"`
AnswerRateToday float64 `json:"answer_rate_today"`
ConversionsToday int `json:"conversions_today"`
OptOutsToday int `json:"opt_outs_today"`
LastUpdated time.Time `json:"last_updated"`
}
func streamCampaignMetrics(campaignID string, ch chan<- LiveCampaignStats) {
ticker := time.NewTicker(5 * time.Second)
defer ticker.Stop()
for range ticker.C {
stats := LiveCampaignStats{
CampaignID: campaignID,
ActiveCalls: getActiveCalls(campaignID),
CallsThisHour: getCallsInWindow(campaignID, time.Hour),
AnswerRateToday: calculateTodayAnswerRate(campaignID),
ConversionsToday: getTodayConversions(campaignID),
OptOutsToday: getTodayOptOuts(campaignID),
LastUpdated: time.Now(),
}
ch <- stats
}
}
Benchmark Targets
| Metric |
Poor |
Average |
Good |
Excellent |
| Answer Rate |
< 15% |
15-25% |
25-35% |
> 35% |
| Listen-Through Rate |
< 40% |
40-60% |
60-80% |
> 80% |
| Conversion Rate |
< 1% |
1-3% |
3-5% |
> 5% |
| Opt-Out Rate |
> 5% |
3-5% |
1-3% |
< 1% |
| Avg Call Duration |
< 30s |
30-60s |
60-90s |
90-120s |
| Survey Completion |
< 30% |
30-50% |
50-70% |
> 70% |
Campaign Orchestration
Campaign Setup API
# Create a new marketing campaign
curl -X POST https://api.edesy.in/v1/campaigns \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Summer Sale 2025",
"type": "promotional",
"agent_id": "marketing_agent_v2",
"schedule": {
"start_date": "2025-06-01",
"end_date": "2025-06-30",
"calling_hours": {
"start": "10:00",
"end": "18:00",
"timezone": "customer_local"
},
"days_of_week": ["monday", "tuesday", "wednesday", "thursday", "friday"],
"calls_per_hour": 100,
"max_attempts_per_contact": 3
},
"compliance": {
"region": "IN",
"scrub_dnc": true,
"consent_required": true
},
"variables": {
"campaignName": "Summer Sale",
"offerDetails": "flat 30% off on all products",
"promoCode": "SUMMER30",
"offerExpiry": "June 30th"
}
}'
# Upload contacts with segmentation
curl -X POST https://api.edesy.in/v1/campaigns/camp_123/contacts \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"contacts": [
{
"phone": "+919876543210",
"name": "Rahul Sharma",
"email": "[email protected]",
"segment": "high_value",
"language": "hi-IN",
"timezone": "Asia/Kolkata",
"custom_variables": {
"lastPurchase": "May 15, 2025",
"customerSince": "2023"
}
}
],
"dedupe": true,
"validate_phones": true
}'
Start Campaign
# Launch campaign with rate limiting
curl -X POST https://api.edesy.in/v1/campaigns/camp_123/start \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{
"mode": "gradual",
"ramp_up_minutes": 30,
"initial_rate": 10,
"target_rate": 100
}'
Best Practices
1. Respect Customer Time
// Best calling windows by region and segment
var optimalCallingWindows = map[string]map[string]CallingWindow{
"IN": {
"professional": {Start: "10:00", End: "12:00", Days: []string{"Mon", "Tue", "Wed", "Thu"}},
"homemaker": {Start: "11:00", End: "13:00", Days: []string{"Mon", "Tue", "Wed", "Thu", "Fri"}},
"senior": {Start: "10:00", End: "11:30", Days: []string{"Mon", "Wed", "Fri"}},
},
"US": {
"professional": {Start: "11:00", End: "13:00", Days: []string{"Tue", "Wed", "Thu"}},
"consumer": {Start: "17:00", End: "19:00", Days: []string{"Mon", "Tue", "Wed", "Thu"}},
},
}
func getOptimalCallTime(contact *Contact) time.Time {
window := optimalCallingWindows[contact.Region][contact.Segment]
return findNextAvailableSlot(window, contact.Timezone)
}
2. Personalization
// Dynamic message personalization
func personalizeMessage(template string, contact *Contact, campaign *Campaign) string {
// Basic variable replacement
message := strings.ReplaceAll(template, "{{contactName}}", contact.FirstName)
message = strings.ReplaceAll(message, "{{companyName}}", campaign.CompanyName)
// Segment-specific messaging
switch contact.Segment {
case "high_value":
message = strings.ReplaceAll(message, "{{exclusiveOffer}}", "as one of our VIP customers")
case "at_risk":
message = strings.ReplaceAll(message, "{{exclusiveOffer}}", "we miss you and wanted to share")
default:
message = strings.ReplaceAll(message, "{{exclusiveOffer}}", "we have something special")
}
// Time-aware greetings
hour := time.Now().In(contact.Timezone).Hour()
var greeting string
switch {
case hour < 12:
greeting = "Good morning"
case hour < 17:
greeting = "Good afternoon"
default:
greeting = "Good evening"
}
message = strings.ReplaceAll(message, "{{greeting}}", greeting)
return message
}
3. Graceful Exit Strategies
## Exit Scenarios
### Immediate Opt-Out Request
Customer: "Don't call me again" / "Remove me from your list"
Response: "Absolutely, I've removed you from our list. My apologies for the inconvenience. Have a great day!"
Action: Call opt_out_customer immediately, end call.
### Busy Customer
Customer: "I'm busy right now"
Response: "No problem! Would tomorrow around the same time work better, or would you prefer I don't call back?"
Action: If callback requested, schedule. If not, mark as "not_interested" and respect decision.
### Not the Right Person
Customer: "I'm not the right person for this"
Response: "My apologies! Could you point me to the right person, or would you prefer I don't call this number?"
Action: If referral given, note it. Otherwise, mark as "wrong_contact".
### Hostile Customer
Customer: [Angry/abusive]
Response: "I sincerely apologize for any inconvenience. I'll make sure you're not contacted again. Have a good day."
Action: Opt-out immediately, flag for review, end call gracefully.
4. A/B Testing
{
"campaign": {
"id": "summer_promo_2025",
"ab_test": {
"enabled": true,
"variants": [
{
"id": "control",
"weight": 34,
"greeting": "Hi {{contactName}}! This is {{agentName}} from {{companyName}} with a special offer for you."
},
{
"id": "urgency",
"weight": 33,
"greeting": "Hi {{contactName}}! This is {{agentName}} from {{companyName}}. I'm calling because we have a limited-time offer ending this week."
},
{
"id": "social_proof",
"weight": 33,
"greeting": "Hi {{contactName}}! This is {{agentName}} from {{companyName}}. Thousands of customers are loving our summer sale - wanted to make sure you knew about it."
}
],
"success_metric": "conversion_rate",
"minimum_sample_size": 1000,
"statistical_significance": 0.95
}
}
}
5. Frequency Capping
// Prevent contact fatigue
type FrequencyCapConfig struct {
MaxCallsPerDay int // Default: 1
MaxCallsPerWeek int // Default: 3
MaxCallsPerMonth int // Default: 6
MinDaysBetweenCalls int // Default: 2
CooldownAfterOptOut time.Duration // Default: forever
}
func canContactToday(contactID string, config FrequencyCapConfig) (bool, string) {
// Check daily cap
callsToday := db.GetCallCountToday(contactID)
if callsToday >= config.MaxCallsPerDay {
return false, "daily_cap_reached"
}
// Check weekly cap
callsThisWeek := db.GetCallCountThisWeek(contactID)
if callsThisWeek >= config.MaxCallsPerWeek {
return false, "weekly_cap_reached"
}
// Check minimum days between calls
lastCall := db.GetLastCallTime(contactID)
if lastCall != nil {
daysSinceLastCall := time.Since(*lastCall).Hours() / 24
if daysSinceLastCall < float64(config.MinDaysBetweenCalls) {
return false, "too_soon_since_last_call"
}
}
return true, ""
}
6. Quality Monitoring
// Automated quality checks
type QualityMetrics struct {
SentimentScore float64 // -1 to 1
ComplianceScore float64 // 0 to 100
ScriptAdherence float64 // 0 to 100
CustomerSatisfaction float64 // 1 to 5
}
func analyzeCallQuality(callID string) *QualityMetrics {
transcript := getTranscript(callID)
metrics := &QualityMetrics{}
// Sentiment analysis
metrics.SentimentScore = nlp.AnalyzeSentiment(transcript)
// Compliance check
metrics.ComplianceScore = checkComplianceInTranscript(transcript)
// Script adherence
metrics.ScriptAdherence = compareToScript(transcript, expectedScript)
// Flag calls that need review
if metrics.SentimentScore < -0.3 || metrics.ComplianceScore < 80 {
flagForReview(callID, metrics)
}
return metrics
}
Next Steps