AI Voice Agents for Logistics & Delivery
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", "delivery.rescheduled", "address.updated"]
}
}
}
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