Automate customer communication across the delivery lifecycle with intelligent voice agents. Handle shipment tracking, delivery scheduling, address confirmation, and failed delivery resolution at scale.
Use Cases#
1. Shipment Tracking#
Proactive and reactive order tracking for customers.
Customer: "Where is my order?"
Agent: "I found your shipment. Your order ORD-78234 is currently in transit
at the Bengaluru hub. Expected delivery is tomorrow by 6 PM.
Would you like me to send a tracking link via SMS?"
2. Delivery Scheduling#
Schedule and reschedule deliveries based on customer availability.
Agent: "Hi, this is Maya from QuickShip. We have a delivery scheduled for
tomorrow between 10 AM and 2 PM. Will someone be available to receive it?"
Customer: "No, I'll be at work. Can you deliver after 6 PM?"
Agent: "I've rescheduled your delivery to tomorrow between 6 PM and 9 PM.
Is there anything else I can help with?"
3. Address Confirmation#
Verify delivery addresses before dispatch to reduce failed deliveries.
Agent: "I'm calling to confirm the delivery address for your order.
I have: Flat 402, Sunrise Apartments, MG Road, Bengaluru 560001.
Is this correct?"
Customer: "The flat number is wrong. It's 502, not 402."
Agent: "Thank you! I've updated the address to Flat 502, Sunrise Apartments.
Your delivery is scheduled for tomorrow."
4. Failed Delivery Handling#
Resolve failed deliveries and reschedule with customers.
Agent: "Hi, we attempted delivery of your order today but couldn't reach you.
Would you like to reschedule for tomorrow, or should we hold it at
our pickup center on Anna Nagar?"
Customer: "Please reschedule for tomorrow morning."
Agent: "Done! I've scheduled delivery between 9 AM and 12 PM tomorrow.
Please ensure someone is available to receive it."
5. Proof of Delivery Collection#
Collect delivery confirmations and feedback via voice.
Agent: "This is a confirmation call for your order ORD-91234 delivered today.
Did you receive the package in good condition?"
Customer: "Yes, I got it."
Agent: "Great! On a scale of 1 to 5, how would you rate your delivery experience?"
Customer: "4"
Agent: "Thank you for your feedback! Is there anything we could improve?"
Agent Configuration#
Standard Logistics Agent#
{
"agent": {
"name": "Logistics Assistant",
"language": "en-IN",
"llmProvider": "gemini-2.5",
"llmModel": "gemini-2.5-flash-lite",
"llmTemperature": 0.3,
"sttProvider": "deepgram",
"sttModel": "nova-3",
"sttConfig": {
"endpointing": 400,
"interimResults": true,
"keywords": ["AWB:2", "tracking:2", "delivery:2", "address:2"]
},
"ttsProvider": "azure",
"ttsVoice": "en-IN-NeerjaNeural",
"ttsConfig": {
"rate": "+5%",
"pitch": "default"
},
"greetingMessage": "Hello! This is Maya from QuickShip logistics. How can I help you with your delivery today?",
"allowInterruptions": true,
"interruptionThreshold": 0.6,
"prompt": "...",
"tools": [],
"webhooks": {
"url": "https://your-server.com/webhooks/logistics",
"events": ["call.ended", "call.extracted"]
}
}
}
Multilingual Configuration (Hindi + English)#
{
"agent": {
"name": "Hinglish Delivery Agent",
"language": "hi-IN",
"llmProvider": "gemini",
"llmModel": "gemini-2.0-flash",
"llmTemperature": 0.5,
"sttProvider": "deepgram",
"sttModel": "nova-3",
"sttConfig": {
"language": "hi",
"model": "nova-3",
"keywords": ["AWB:2", "parcel:2", "delivery:2"]
},
"ttsProvider": "azure",
"ttsVoice": "hi-IN-SwaraNeural",
"greetingMessage": "Namaste! Main QuickShip ki taraf se Maya bol rahi hoon. Aapke delivery ke baare mein kaise madad kar sakti hoon?"
}
}
System Prompt#
You are Maya, a professional and efficient delivery assistant for QuickShip Logistics.
## Your Role
- Help customers track their shipments
- Schedule and reschedule deliveries
- Confirm and update delivery addresses
- Handle failed delivery inquiries
- Collect delivery confirmations and feedback
## Conversation Guidelines
### Response Style
- Be concise and clear (1-2 sentences max)
- Speak naturally, like a helpful delivery executive
- Always confirm changes before ending the call
- Use the customer's name when available
### Information Gathering
- Ask for AWB number or order ID to look up shipments
- Verify phone number if AWB not available (last 4 digits)
- Confirm address details by reading them back
- Never ask for more than one piece of information at a time
### Handling Common Scenarios
**Tracking Inquiries:**
1. Look up the shipment status
2. Provide current location and ETA
3. Offer to send SMS tracking link
4. Ask if they need anything else
**Delivery Reschedule:**
1. Confirm the order details
2. Ask for preferred date and time slot
3. Check slot availability using tool
4. Confirm the new schedule clearly
5. Send SMS confirmation
**Address Updates:**
1. Read out the current address on file
2. Listen carefully to corrections
3. Repeat the updated address back
4. Confirm the change was saved
**Failed Delivery:**
1. Apologize for the inconvenience
2. Explain the attempt details
3. Offer reschedule or pickup options
4. Confirm next steps clearly
## Escalation Criteria
Transfer to human agent if:
- Customer requests human assistance
- Customer is upset or frustrated
- Shipment shows "exception" or "lost" status
- Complex address issues requiring verification
- Requests for refunds or compensation
## Localization
- Use "delivery executive" not "driver"
- Use "parcel" or "shipment" interchangeably
- Time slots in 12-hour format with AM/PM
- Dates as "15th December" format
## DO NOT
- Make promises about exact delivery times
- Provide refund estimates
- Share delivery executive personal details
- Discuss pricing or charges
Tools for Delivery Management#
1. Track Shipment#
{
"type": "function",
"function": {
"name": "track_shipment",
"description": "Get real-time tracking status of a shipment by AWB number or order ID",
"parameters": {
"type": "object",
"properties": {
"awb_number": {
"type": "string",
"description": "Air Waybill number (e.g., DEL123456789)"
},
"order_id": {
"type": "string",
"description": "Order ID from e-commerce platform"
},
"phone_last_4": {
"type": "string",
"description": "Last 4 digits of customer phone for verification"
}
}
}
}
}
Tool Handler:
func handleTrackShipment(params map[string]any) (string, error) {
var shipment *Shipment
var err error
if awb, ok := params["awb_number"].(string); ok && awb != "" {
shipment, err = courierService.TrackByAWB(awb)
} else if orderID, ok := params["order_id"].(string); ok && orderID != "" {
shipment, err = courierService.TrackByOrderID(orderID)
} else {
return "I need either an AWB number or order ID to track your shipment.", nil
}
if err != nil || shipment == nil {
return "I couldn't find that shipment. Please verify the tracking number.", nil
}
return formatShipmentStatus(shipment), nil
}
func formatShipmentStatus(s *Shipment) string {
var response strings.Builder
response.WriteString(fmt.Sprintf("Your shipment %s is ", s.AWB))
switch s.Status {
case "booked":
response.WriteString("booked and awaiting pickup from the seller.")
case "picked_up":
response.WriteString(fmt.Sprintf("picked up and on its way to our %s facility.", s.CurrentLocation))
case "in_transit":
response.WriteString(fmt.Sprintf("in transit at our %s hub.", s.CurrentLocation))
case "out_for_delivery":
response.WriteString(fmt.Sprintf("out for delivery! Expected by %s today.", s.ETA.Format("3 PM")))
case "delivered":
response.WriteString(fmt.Sprintf("delivered on %s.", s.DeliveredAt.Format("January 2 at 3:04 PM")))
case "failed_attempt":
response.WriteString(fmt.Sprintf("marked as delivery attempted on %s. Reason: %s.",
s.LastAttempt.Format("January 2"), s.FailureReason))
case "rto":
response.WriteString("being returned to the seller.")
default:
response.WriteString(fmt.Sprintf("in %s status.", s.Status))
}
if s.Status != "delivered" && s.Status != "rto" && !s.ETA.IsZero() {
response.WriteString(fmt.Sprintf(" Expected delivery: %s.", s.ETA.Format("Monday, January 2")))
}
return response.String()
}
2. Reschedule Delivery#
{
"type": "function",
"function": {
"name": "reschedule_delivery",
"description": "Reschedule a delivery to a new date and time slot",
"parameters": {
"type": "object",
"properties": {
"awb_number": {
"type": "string",
"description": "AWB number of the shipment"
},
"new_date": {
"type": "string",
"description": "New delivery date (YYYY-MM-DD)"
},
"time_slot": {
"type": "string",
"enum": ["morning", "afternoon", "evening"],
"description": "Preferred time slot: morning (9AM-12PM), afternoon (12PM-4PM), evening (4PM-9PM)"
}
},
"required": ["awb_number", "new_date", "time_slot"]
}
}
}
Tool Handler:
func handleRescheduleDelivery(params map[string]any) (string, error) {
awb := params["awb_number"].(string)
newDate := params["new_date"].(string)
timeSlot := params["time_slot"].(string)
// Parse and validate date
deliveryDate, err := time.Parse("2006-01-02", newDate)
if err != nil {
return "I couldn't understand that date. Please specify a date like tomorrow or December 15th.", nil
}
// Check if date is valid (not in past, within 7 days)
today := time.Now().Truncate(24 * time.Hour)
maxDate := today.AddDate(0, 0, 7)
if deliveryDate.Before(today) {
return "I can only schedule deliveries for today or future dates.", nil
}
if deliveryDate.After(maxDate) {
return "I can only schedule deliveries within the next 7 days.", nil
}
// Check slot availability
available, err := courierService.CheckSlotAvailability(awb, deliveryDate, timeSlot)
if !available {
return fmt.Sprintf("Unfortunately, the %s slot on %s is not available. Would you like to try a different time?",
timeSlot, deliveryDate.Format("January 2")), nil
}
// Reschedule
result, err := courierService.RescheduleDelivery(awb, deliveryDate, timeSlot)
if err != nil {
return "I couldn't reschedule the delivery right now. Let me transfer you to someone who can help.", nil
}
slotTimes := map[string]string{
"morning": "9 AM to 12 PM",
"afternoon": "12 PM to 4 PM",
"evening": "4 PM to 9 PM",
}
return fmt.Sprintf("I've rescheduled your delivery to %s between %s. You'll receive an SMS confirmation shortly.",
deliveryDate.Format("Monday, January 2"), slotTimes[timeSlot]), nil
}
3. Update Delivery Address#
{
"type": "function",
"function": {
"name": "update_delivery_address",
"description": "Update the delivery address for a shipment",
"parameters": {
"type": "object",
"properties": {
"awb_number": {
"type": "string",
"description": "AWB number of the shipment"
},
"flat_number": {
"type": "string",
"description": "Flat/House number"
},
"building_name": {
"type": "string",
"description": "Building or apartment name"
},
"street": {
"type": "string",
"description": "Street address"
},
"landmark": {
"type": "string",
"description": "Nearby landmark"
},
"city": {
"type": "string",
"description": "City name"
},
"pincode": {
"type": "string",
"description": "6-digit postal code"
}
},
"required": ["awb_number"]
}
}
}
Tool Handler:
func handleUpdateAddress(params map[string]any) (string, error) {
awb := params["awb_number"].(string)
// Get current shipment
shipment, err := courierService.GetShipment(awb)
if err != nil {
return "I couldn't find that shipment.", nil
}
// Check if address can be updated
if shipment.Status == "out_for_delivery" || shipment.Status == "delivered" {
return "Sorry, the address cannot be updated as the shipment is already out for delivery or delivered.", nil
}
// Build updated address
address := shipment.Address
if flat, ok := params["flat_number"].(string); ok && flat != "" {
address.FlatNumber = flat
}
if building, ok := params["building_name"].(string); ok && building != "" {
address.Building = building
}
if street, ok := params["street"].(string); ok && street != "" {
address.Street = street
}
if landmark, ok := params["landmark"].(string); ok && landmark != "" {
address.Landmark = landmark
}
if city, ok := params["city"].(string); ok && city != "" {
address.City = city
}
if pincode, ok := params["pincode"].(string); ok && pincode != "" {
if !isValidPincode(pincode) {
return "That doesn't seem like a valid pincode. Please provide a 6-digit postal code.", nil
}
address.Pincode = pincode
}
// Update address
err = courierService.UpdateAddress(awb, address)
if err != nil {
return "I couldn't update the address right now. Please try again later.", nil
}
return fmt.Sprintf("I've updated the delivery address to: %s. Is there anything else I can help with?",
formatAddress(address)), nil
}
func formatAddress(a *Address) string {
parts := []string{}
if a.FlatNumber != "" {
parts = append(parts, a.FlatNumber)
}
if a.Building != "" {
parts = append(parts, a.Building)
}
if a.Street != "" {
parts = append(parts, a.Street)
}
if a.City != "" && a.Pincode != "" {
parts = append(parts, fmt.Sprintf("%s %s", a.City, a.Pincode))
}
return strings.Join(parts, ", ")
}
4. Get Delivery Slots#
{
"type": "function",
"function": {
"name": "get_available_slots",
"description": "Get available delivery time slots for a date",
"parameters": {
"type": "object",
"properties": {
"awb_number": {
"type": "string",
"description": "AWB number of the shipment"
},
"date": {
"type": "string",
"description": "Date to check availability (YYYY-MM-DD)"
}
},
"required": ["awb_number", "date"]
}
}
}
5. Collect Delivery Confirmation#
{
"type": "function",
"function": {
"name": "confirm_delivery",
"description": "Record delivery confirmation from customer",
"parameters": {
"type": "object",
"properties": {
"awb_number": {
"type": "string",
"description": "AWB number of the shipment"
},
"received_by": {
"type": "string",
"description": "Name of person who received the delivery"
},
"condition": {
"type": "string",
"enum": ["good", "damaged", "partial"],
"description": "Package condition on receipt"
},
"rating": {
"type": "integer",
"minimum": 1,
"maximum": 5,
"description": "Customer satisfaction rating (1-5)"
},
"feedback": {
"type": "string",
"description": "Additional feedback from customer"
}
},
"required": ["awb_number", "condition"]
}
}
}
Courier System Integration#
Delhivery Integration#
package delhivery
import (
"encoding/json"
"fmt"
"net/http"
"net/url"
)
type Client struct {
apiKey string
baseURL string
client *http.Client
}
func NewClient(apiKey string) *Client {
return &Client{
apiKey: apiKey,
baseURL: "https://track.delhivery.com/api/v1",
client: &http.Client{Timeout: 10 * time.Second},
}
}
// Track shipment by AWB
func (c *Client) Track(awb string) (*ShipmentStatus, error) {
url := fmt.Sprintf("%s/packages/json/?waybill=%s&token=%s",
c.baseURL, awb, c.apiKey)
resp, err := c.client.Get(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var result DelhiveryTrackResponse
json.NewDecoder(resp.Body).Decode(&result)
if len(result.ShipmentData) == 0 {
return nil, fmt.Errorf("shipment not found")
}
return mapDelhiveryStatus(result.ShipmentData[0]), nil
}
// Create pickup request
func (c *Client) CreatePickup(req PickupRequest) (*PickupResponse, error) {
// Implementation
}
// Update delivery address
func (c *Client) UpdateAddress(awb string, address Address) error {
// Implementation
}
type DelhiveryTrackResponse struct {
ShipmentData []struct {
Shipment struct {
Status struct {
Status string `json:"Status"`
StatusDateTime string `json:"StatusDateTime"`
StatusLocation string `json:"StatusLocation"`
} `json:"Status"`
ExpectedDeliveryDate string `json:"ExpectedDeliveryDate"`
Destination string `json:"Destination"`
} `json:"Shipment"`
} `json:"ShipmentData"`
}
func mapDelhiveryStatus(data ShipmentData) *ShipmentStatus {
status := &ShipmentStatus{
AWB: data.AWB,
CurrentLocation: data.Shipment.Status.StatusLocation,
RawStatus: data.Shipment.Status.Status,
}
// Map Delhivery status to standard status
switch data.Shipment.Status.Status {
case "Manifested":
status.Status = "booked"
case "In Transit":
status.Status = "in_transit"
case "Out for Delivery":
status.Status = "out_for_delivery"
case "Delivered":
status.Status = "delivered"
case "Undelivered":
status.Status = "failed_attempt"
default:
status.Status = "in_transit"
}
return status
}
BlueDart Integration#
package bluedart
import (
"encoding/xml"
"fmt"
"net/http"
)
type Client struct {
licenseKey string
loginID string
baseURL string
client *http.Client
}
func NewClient(licenseKey, loginID string) *Client {
return &Client{
licenseKey: licenseKey,
loginID: loginID,
baseURL: "https://nettrack.bluedart.com/ver_1.7",
client: &http.Client{Timeout: 10 * time.Second},
}
}
// Track shipment
func (c *Client) Track(awb string) (*ShipmentStatus, error) {
soapRequest := fmt.Sprintf(`<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<GetShipmentTrackingDetails xmlns="http://bluedart.com/">
<AWBNo>%s</AWBNo>
<LicenceKey>%s</LicenceKey>
<LoginID>%s</LoginID>
</GetShipmentTrackingDetails>
</soap:Body>
</soap:Envelope>`, awb, c.licenseKey, c.loginID)
req, _ := http.NewRequest("POST", c.baseURL+"/TrackingService.svc",
strings.NewReader(soapRequest))
req.Header.Set("Content-Type", "text/xml; charset=utf-8")
req.Header.Set("SOAPAction", "http://bluedart.com/GetShipmentTrackingDetails")
resp, err := c.client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
// Parse SOAP response and map to standard status
return parseBluedarResponse(resp.Body)
}
// Schedule delivery slot
func (c *Client) ScheduleDelivery(awb string, date time.Time, slot string) error {
// BlueDart slot scheduling implementation
}
Shiprocket Integration#
package shiprocket
import (
"encoding/json"
"fmt"
"net/http"
)
type Client struct {
token string
baseURL string
client *http.Client
}
func NewClient(email, password string) (*Client, error) {
client := &Client{
baseURL: "https://apiv2.shiprocket.in/v1/external",
client: &http.Client{Timeout: 10 * time.Second},
}
// Authenticate and get token
token, err := client.authenticate(email, password)
if err != nil {
return nil, err
}
client.token = token
return client, nil
}
// Track by AWB
func (c *Client) Track(awb string) (*ShipmentStatus, error) {
req, _ := http.NewRequest("GET",
fmt.Sprintf("%s/courier/track/awb/%s", c.baseURL, awb), nil)
req.Header.Set("Authorization", "Bearer "+c.token)
resp, err := c.client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var result ShiprocketTrackResponse
json.NewDecoder(resp.Body).Decode(&result)
return mapShiprocketStatus(result), nil
}
// Track by Order ID
func (c *Client) TrackByOrderID(orderID string) (*ShipmentStatus, error) {
req, _ := http.NewRequest("GET",
fmt.Sprintf("%s/orders/show/%s", c.baseURL, orderID), nil)
req.Header.Set("Authorization", "Bearer "+c.token)
resp, err := c.client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var result ShiprocketOrderResponse
json.NewDecoder(resp.Body).Decode(&result)
if result.Data.AWBCode == "" {
return nil, fmt.Errorf("order not shipped yet")
}
return c.Track(result.Data.AWBCode)
}
// Update delivery address
func (c *Client) UpdateAddress(orderID string, address Address) error {
payload := map[string]any{
"order_id": orderID,
"shipping_address": address.Street,
"shipping_city": address.City,
"shipping_pincode": address.Pincode,
"shipping_state": address.State,
}
body, _ := json.Marshal(payload)
req, _ := http.NewRequest("POST", c.baseURL+"/orders/address/update",
bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+c.token)
req.Header.Set("Content-Type", "application/json")
resp, err := c.client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("failed to update address")
}
return nil
}
Unified Courier Interface#
package courier
type CourierProvider interface {
Track(awb string) (*ShipmentStatus, error)
UpdateAddress(awb string, address Address) error
RescheduleDelivery(awb string, date time.Time, slot string) error
GetDeliverySlots(awb string, date time.Time) ([]TimeSlot, error)
}
type MultiCourierService struct {
providers map[string]CourierProvider
}
func NewMultiCourierService() *MultiCourierService {
return &MultiCourierService{
providers: map[string]CourierProvider{
"delhivery": delhivery.NewClient(os.Getenv("DELHIVERY_API_KEY")),
"bluedart": bluedart.NewClient(os.Getenv("BLUEDART_LICENSE"), os.Getenv("BLUEDART_LOGIN")),
"shiprocket": shiprocket.NewClient(os.Getenv("SHIPROCKET_EMAIL"), os.Getenv("SHIPROCKET_PASSWORD")),
},
}
}
// Auto-detect courier from AWB prefix
func (s *MultiCourierService) DetectCourier(awb string) string {
prefixes := map[string]string{
"1": "delhivery", // Delhivery AWBs start with numbers
"2": "delhivery",
"D": "delhivery",
"3": "bluedart", // BlueDart AWBs
"SF": "shiprocket", // Shiprocket self-ship
}
for prefix, courier := range prefixes {
if strings.HasPrefix(awb, prefix) {
return courier
}
}
return "shiprocket" // Default fallback
}
func (s *MultiCourierService) Track(awb string) (*ShipmentStatus, error) {
courier := s.DetectCourier(awb)
provider := s.providers[courier]
status, err := provider.Track(awb)
if err != nil {
// Try other providers as fallback
for name, p := range s.providers {
if name == courier {
continue
}
if status, err = p.Track(awb); err == nil {
return status, nil
}
}
return nil, fmt.Errorf("shipment not found in any courier system")
}
status.Courier = courier
return status, nil
}
Multi-Language Support#
Regional Language Configuration#
var LogisticsLanguageConfigs = map[string]LanguageConfig{
"hi-IN": {
Language: "hi-IN",
STTProvider: "deepgram",
STTModel: "nova-3",
TTSProvider: "azure",
TTSVoice: "hi-IN-SwaraNeural",
LLMProvider: "gemini",
Greeting: "Namaste! Main QuickShip logistics se Maya bol rahi hoon. Aapke delivery ke baare mein kaise madad karun?",
},
"ta-IN": {
Language: "ta-IN",
STTProvider: "deepgram",
STTModel: "nova-3",
TTSProvider: "azure",
TTSVoice: "ta-IN-PallaviNeural",
LLMProvider: "gemini",
Greeting: "Vanakkam! Naan QuickShip logistics-il irundhu Maya pesugiren. Ungal delivery patriya eppadi udhava mudiyum?",
},
"te-IN": {
Language: "te-IN",
STTProvider: "google",
STTModel: "chirp_2",
TTSProvider: "azure",
TTSVoice: "te-IN-ShrutiNeural",
LLMProvider: "gemini",
Greeting: "Namaskaram! Nenu QuickShip logistics nundi Maya matladutunnanu. Mee delivery gurinchi ela sahaayam cheyagalanu?",
},
"bn-IN": {
Language: "bn-IN",
STTProvider: "deepgram",
STTModel: "nova-3",
TTSProvider: "azure",
TTSVoice: "bn-IN-TanishaaNeural",
LLMProvider: "gemini",
Greeting: "Namaskar! Ami QuickShip logistics theke Maya bolchi. Apnar delivery niye ki bhabe sahajyo korte pari?",
},
"mr-IN": {
Language: "mr-IN",
STTProvider: "google",
STTModel: "chirp_2",
TTSProvider: "azure",
TTSVoice: "mr-IN-AarohiNeural",
LLMProvider: "gemini",
Greeting: "Namaskar! Mi QuickShip logistics madhun Maya bolte ahe. Tumchya delivery babtit kashi madad karu?",
},
"gu-IN": {
Language: "gu-IN",
STTProvider: "google",
STTModel: "chirp_2",
TTSProvider: "azure",
TTSVoice: "gu-IN-DhwaniNeural",
LLMProvider: "gemini",
Greeting: "Namaskar! Hu QuickShip logistics thi Maya bolu chhu. Tamari delivery mate kem madad kari shaku?",
},
"kn-IN": {
Language: "kn-IN",
STTProvider: "google",
STTModel: "chirp_2",
TTSProvider: "azure",
TTSVoice: "kn-IN-SapnaNeural",
LLMProvider: "gemini",
Greeting: "Namaskara! Naanu QuickShip logistics inda Maya maatanaaduttiddene. Nimma delivery bagge hege sahaaya maadali?",
},
}
Language Detection and Switching#
type MultilingualDeliveryAgent struct {
configs map[string]LanguageConfig
currentLang string
session *Session
}
func (a *MultilingualDeliveryAgent) DetectLanguage(transcript string) string {
// Check for language indicators
languagePatterns := map[string][]string{
"hi-IN": {"kya", "haan", "nahi", "delivery", "parcel", "address"},
"ta-IN": {"enna", "illa", "aama", "irukku", "delivery"},
"te-IN": {"emi", "kaadu", "avunu", "delivery", "address"},
"bn-IN": {"ki", "na", "haan", "delivery", "thik"},
}
for lang, patterns := range languagePatterns {
for _, pattern := range patterns {
if strings.Contains(strings.ToLower(transcript), pattern) {
return lang
}
}
}
return "en-IN" // Default to English
}
func (a *MultilingualDeliveryAgent) SwitchLanguage(lang string) error {
config, ok := a.configs[lang]
if !ok {
return fmt.Errorf("language %s not supported", lang)
}
a.currentLang = lang
a.session.UpdateSTT(config.STTProvider, config.STTModel)
a.session.UpdateTTS(config.TTSProvider, config.TTSVoice)
a.session.AddSystemContext(fmt.Sprintf(
"User prefers %s. Respond in %s from now on.",
lang, config.LanguageName,
))
return nil
}
Localized Response Templates#
var DeliveryStatusTemplates = map[string]map[string]string{
"en-IN": {
"in_transit": "Your shipment is in transit at our %s hub. Expected delivery: %s.",
"out_for_delivery": "Great news! Your package is out for delivery and should arrive by %s today.",
"delivered": "Your package was delivered on %s.",
"failed_attempt": "We attempted delivery on %s but couldn't reach you. Reason: %s.",
},
"hi-IN": {
"in_transit": "Aapka shipment hamare %s hub par hai. Delivery ka anuman: %s.",
"out_for_delivery": "Acha news! Aapka parcel delivery ke liye nikal chuka hai, aaj %s tak pahunch jayega.",
"delivered": "Aapka package %s ko deliver ho gaya.",
"failed_attempt": "%s ko delivery ka prayas kiya gaya lekin aap tak nahi pahunch sake. Karan: %s.",
},
"ta-IN": {
"in_transit": "Ungal shipment engal %s hub-il irukkiradhu. Ethirpaarkkum delivery: %s.",
"out_for_delivery": "Nalla seythi! Ungal parcel delivery-kku purappattu vittathu, inru %s-kku varum.",
"delivered": "Ungal package %s andru deliver seyyappattathu.",
"failed_attempt": "%s andru delivery muyarchi seyyappattathu aanaal ungalai adaiya mudiyavillai. Kaaranam: %s.",
},
}
func (a *MultilingualDeliveryAgent) FormatStatus(status *ShipmentStatus) string {
templates := DeliveryStatusTemplates[a.currentLang]
if templates == nil {
templates = DeliveryStatusTemplates["en-IN"]
}
template := templates[status.Status]
if template == "" {
template = templates["in_transit"]
}
return fmt.Sprintf(template, status.CurrentLocation, status.ETA.Format("January 2"))
}
Outbound Campaign Management#
Delivery Notification Campaign#
type DeliveryNotificationCampaign struct {
AgentID string
ShipmentIDs []string
CallType string // "scheduling", "confirmation", "failed_delivery"
MaxRetries int
RetryInterval time.Duration
}
func (c *DeliveryNotificationCampaign) Execute(ctx context.Context) (*CampaignResult, error) {
results := &CampaignResult{
TotalCalls: len(c.ShipmentIDs),
SuccessfulCalls: 0,
FailedCalls: 0,
Outcomes: make(map[string]int),
}
for _, shipmentID := range c.ShipmentIDs {
shipment, _ := courierService.GetShipment(shipmentID)
// Prepare call variables
variables := map[string]string{
"awb_number": shipment.AWB,
"customer_name": shipment.CustomerName,
"delivery_date": shipment.ETA.Format("Monday, January 2"),
"delivery_slot": shipment.TimeSlot,
"current_address": formatAddress(shipment.Address),
}
// Initiate call
call, err := voiceAgent.Call(ctx, CallRequest{
AgentID: c.AgentID,
To: shipment.CustomerPhone,
Variables: variables,
})
if err != nil {
results.FailedCalls++
continue
}
// Wait for call completion
outcome := <-call.Done()
results.Outcomes[outcome]++
if outcome == "success" || outcome == "rescheduled" {
results.SuccessfulCalls++
} else {
results.FailedCalls++
// Schedule retry if needed
if outcome == "no_answer" && call.Attempts < c.MaxRetries {
scheduleRetry(shipmentID, c.RetryInterval)
}
}
}
return results, nil
}
Batch Processing#
func ProcessDailyDeliveryNotifications(ctx context.Context) error {
// Get shipments scheduled for tomorrow
tomorrow := time.Now().AddDate(0, 0, 1)
shipments, _ := courierService.GetShipmentsForDate(tomorrow)
// Group by notification type
confirmations := filterByStatus(shipments, "in_transit")
failedDeliveries := filterByStatus(shipments, "failed_attempt")
// Run confirmation calls (morning)
confirmCampaign := &DeliveryNotificationCampaign{
AgentID: "delivery_confirmation_agent",
ShipmentIDs: getAWBs(confirmations),
CallType: "confirmation",
MaxRetries: 2,
RetryInterval: 2 * time.Hour,
}
go confirmCampaign.Execute(ctx)
// Run failed delivery calls (afternoon)
time.AfterFunc(6*time.Hour, func() {
failedCampaign := &DeliveryNotificationCampaign{
AgentID: "failed_delivery_agent",
ShipmentIDs: getAWBs(failedDeliveries),
CallType: "failed_delivery",
MaxRetries: 3,
RetryInterval: 1 * time.Hour,
}
failedCampaign.Execute(ctx)
})
return nil
}
Metrics and Analytics#
Key Performance Indicators#
type LogisticsMetrics struct {
// Call Metrics
TotalCalls int `json:"total_calls"`
SuccessfulCalls int `json:"successful_calls"`
FailedCalls int `json:"failed_calls"`
AverageCallDuration time.Duration `json:"avg_call_duration"`
// Delivery Metrics
DeliveriesConfirmed int `json:"deliveries_confirmed"`
AddressesUpdated int `json:"addresses_updated"`
DeliveriesRescheduled int `json:"deliveries_rescheduled"`
// Customer Reach
CustomerReachRate float64 `json:"customer_reach_rate"` // Answered / Total
FirstAttemptSuccess float64 `json:"first_attempt_success"` // Success on first try
// Delivery Success
DeliverySuccessRate float64 `json:"delivery_success_rate"` // Delivered / Attempted
RTORate float64 `json:"rto_rate"` // Returns / Total
// Language Distribution
LanguageBreakdown map[string]int `json:"language_breakdown"`
// Time-based
PeakCallHours []int `json:"peak_call_hours"`
AverageResponseTime float64 `json:"avg_response_time_ms"`
}
func CollectLogisticsMetrics(startDate, endDate time.Time) *LogisticsMetrics {
metrics := &LogisticsMetrics{
LanguageBreakdown: make(map[string]int),
}
calls := getCallsInRange(startDate, endDate)
for _, call := range calls {
metrics.TotalCalls++
metrics.AverageCallDuration += call.Duration
if call.Outcome == "success" {
metrics.SuccessfulCalls++
} else {
metrics.FailedCalls++
}
// Track language usage
metrics.LanguageBreakdown[call.Language]++
// Track delivery outcomes
for _, action := range call.Actions {
switch action.Type {
case "delivery_confirmed":
metrics.DeliveriesConfirmed++
case "address_updated":
metrics.AddressesUpdated++
case "delivery_rescheduled":
metrics.DeliveriesRescheduled++
}
}
}
// Calculate rates
if metrics.TotalCalls > 0 {
metrics.CustomerReachRate = float64(metrics.SuccessfulCalls) / float64(metrics.TotalCalls) * 100
metrics.AverageCallDuration /= time.Duration(metrics.TotalCalls)
}
// Calculate delivery success rate
deliveries := getDeliveriesInRange(startDate, endDate)
delivered := countByStatus(deliveries, "delivered")
rto := countByStatus(deliveries, "rto")
if len(deliveries) > 0 {
metrics.DeliverySuccessRate = float64(delivered) / float64(len(deliveries)) * 100
metrics.RTORate = float64(rto) / float64(len(deliveries)) * 100
}
return metrics
}
Dashboard Metrics#
type DashboardData struct {
// Real-time metrics
ActiveCalls int `json:"active_calls"`
CallsToday int `json:"calls_today"`
SuccessRateToday float64 `json:"success_rate_today"`
// Delivery metrics
PendingDeliveries int `json:"pending_deliveries"`
OutForDeliveryToday int `json:"out_for_delivery"`
DeliveredToday int `json:"delivered_today"`
FailedAttemptsToday int `json:"failed_attempts_today"`
// Trends (7-day)
DailyCallVolume []int `json:"daily_call_volume"`
DailySuccessRate []float64 `json:"daily_success_rate"`
DailyDeliveryRate []float64 `json:"daily_delivery_rate"`
}
// Webhook handler for real-time updates
func handleDeliveryWebhook(w http.ResponseWriter, r *http.Request) {
var event WebhookEvent
json.NewDecoder(r.Body).Decode(&event)
switch event.Type {
case "call.ended":
updateCallMetrics(event)
case "delivery.rescheduled":
updateDeliveryMetrics(event)
case "address.updated":
trackAddressUpdate(event)
}
w.WriteHeader(http.StatusOK)
}
Analytics Queries#
-- Customer reach rate by time of day
SELECT
EXTRACT(HOUR FROM created_at) as hour,
COUNT(*) as total_calls,
SUM(CASE WHEN outcome = 'answered' THEN 1 ELSE 0 END) as answered,
ROUND(SUM(CASE WHEN outcome = 'answered' THEN 1 ELSE 0 END)::DECIMAL / COUNT(*) * 100, 2) as reach_rate
FROM voice_calls
WHERE call_type = 'delivery_notification'
AND created_at > NOW() - INTERVAL '30 days'
GROUP BY EXTRACT(HOUR FROM created_at)
ORDER BY reach_rate DESC;
-- Delivery success rate by courier
SELECT
courier,
COUNT(*) as total_shipments,
SUM(CASE WHEN status = 'delivered' THEN 1 ELSE 0 END) as delivered,
SUM(CASE WHEN status = 'rto' THEN 1 ELSE 0 END) as rto,
ROUND(SUM(CASE WHEN status = 'delivered' THEN 1 ELSE 0 END)::DECIMAL / COUNT(*) * 100, 2) as success_rate
FROM shipments
WHERE created_at > NOW() - INTERVAL '30 days'
GROUP BY courier
ORDER BY success_rate DESC;
-- Language preference by region
SELECT
shipping_state,
language,
COUNT(*) as call_count
FROM voice_calls v
JOIN shipments s ON v.awb_number = s.awb_number
WHERE v.created_at > NOW() - INTERVAL '30 days'
GROUP BY shipping_state, language
ORDER BY shipping_state, call_count DESC;
Best Practices#
1. Optimal Call Timing#
// Best times to reach customers by segment
var OptimalCallTimes = map[string]TimeWindow{
"residential": {Start: 10, End: 12, Evening: TimeWindow{Start: 18, End: 20}},
"business": {Start: 10, End: 17},
"premium": {Start: 9, End: 21}, // Flexible timing for premium customers
}
func GetBestCallTime(customer *Customer) time.Time {
segment := customer.Segment
if segment == "" {
segment = "residential"
}
window := OptimalCallTimes[segment]
now := time.Now()
// If within morning window, call now
if now.Hour() >= window.Start && now.Hour() < window.End {
return now
}
// If evening window exists and we're in it
if window.Evening.Start > 0 && now.Hour() >= window.Evening.Start && now.Hour() < window.Evening.End {
return now
}
// Schedule for next available window
nextWindow := time.Date(now.Year(), now.Month(), now.Day(), window.Start, 0, 0, 0, now.Location())
if now.Hour() >= window.End {
nextWindow = nextWindow.AddDate(0, 0, 1)
}
return nextWindow
}
2. Retry Strategy#
type RetryConfig struct {
MaxAttempts int
InitialDelay time.Duration
MaxDelay time.Duration
BackoffFactor float64
RetryableErrors []string
}
var DeliveryCallRetryConfig = RetryConfig{
MaxAttempts: 3,
InitialDelay: 30 * time.Minute,
MaxDelay: 4 * time.Hour,
BackoffFactor: 2.0,
RetryableErrors: []string{"no_answer", "busy", "voicemail"},
}
func ShouldRetry(outcome string, attempt int) (bool, time.Duration) {
config := DeliveryCallRetryConfig
if attempt >= config.MaxAttempts {
return false, 0
}
retryable := false
for _, err := range config.RetryableErrors {
if outcome == err {
retryable = true
break
}
}
if !retryable {
return false, 0
}
delay := config.InitialDelay * time.Duration(math.Pow(config.BackoffFactor, float64(attempt-1)))
if delay > config.MaxDelay {
delay = config.MaxDelay
}
return true, delay
}
3. Error Handling#
func handleDeliveryToolError(toolName string, err error, session *Session) string {
log.Error().Err(err).Str("tool", toolName).Msg("Tool execution failed")
switch toolName {
case "track_shipment":
return "I'm having trouble looking up that shipment right now. " +
"Can you please verify the tracking number, or would you like me to transfer you to an agent?"
case "reschedule_delivery":
return "I couldn't reschedule the delivery at the moment. " +
"Let me transfer you to someone who can help with this."
case "update_address":
return "I'm unable to update the address right now. " +
"For security, I'll need to transfer you to verify this change."
default:
return "I encountered an issue. Let me connect you with a human agent."
}
}
Next Steps#
- Order Status Agent - Basic order tracking
- Function Calling - Tool integration guide
- Indian Languages - Regional language setup
- Call Transfer - Human handoff
- Webhooks - Post-call integrations