# AI Voice Agents for Logistics & Delivery

> Build intelligent voice agents for logistics operations including shipment tracking, delivery scheduling, address confirmation, failed delivery handling, and proof of delivery collection. Integrate with Delhivery, BlueDart, and Shiprocket APIs.

Source: https://edesy.in/docs/voice-agent/industries/logistics

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

```json
{
  "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)

```json
{
  "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

```json
{
  "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:**

```go
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

```json
{
  "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:**

```go
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

```json
{
  "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:**

```go
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

```json
{
  "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

```json
{
  "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

```go
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

```go
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

```go
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

```go
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

```go
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

```go
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

```go
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

```go
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

```go
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

```go
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

```go
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

```sql
-- 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

```go
// 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

```go
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

```go
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](https://edesy.in/docs/voice-agent/examples/order-status) - Basic order tracking
- [Function Calling](https://edesy.in/docs/voice-agent/features/function-calling) - Tool integration guide
- [Indian Languages](https://edesy.in/docs/voice-agent/languages/indian-languages) - Regional language setup
- [Call Transfer](https://edesy.in/docs/voice-agent/features/call-transfer) - Human handoff
- [Webhooks](https://edesy.in/docs/voice-agent/features/webhooks) - Post-call integrations
