# Phone Validator Bulk

## Overview

[Bulks Phone Validator](https://tomba.io/bulks/phone-validator) verifies phone numbers at scale to ensure accuracy and validity for SMS campaigns, sales calling, and customer communication. Improve contact rates and reduce wasted outreach efforts with comprehensive phone validation.

### Key Features

- **Number Validation**: Verify phone number format, validity, and carrier information
- **Bulk Processing**: Validate up to 2,500 phone numbers per operation
- **Carrier Detection**: Identify mobile vs. landline numbers for appropriate outreach
- **Geographic Information**: Determine location and timezone data for numbers
- **Quality Scoring**: Get detailed validation status for each phone number
- **Export Options**: Download validated numbers with detailed metadata

### How Phone Validator Bulk Works

1. **Prepare Phone List**: Compile phone numbers requiring validation
2. **Upload Numbers**: Provide phone numbers via CSV file with country information
3. **Configure Validation**: Set validation parameters and quality requirements
4. **Process Validation**: Launch comprehensive phone number verification
5. **Export Results**: Download validated phone lists with quality scores

### Limitations

- Each Bulk is limited to 2,500 phone numbers
- Special or unexpected characters may be removed from your file
- Duplicate and invalid lines will not be imported

## Go SDK Integration

### Installation

```bash
go get github.com/tomba-io/go
```

### Basic Setup

```go
package main

import (
    "fmt"
    "log"
    "time"
    "encoding/csv"
    "os"

    "github.com/tomba-io/go/tomba"
    "github.com/tomba-io/go/tomba/models"
)

func main() {
    // Initialize Tomba client
    client := tomba.NewTomba("your-api-key", "your-secret-key")

    // Your phone validation code here
}
```

### Creating Phone Validator Bulk from CSV

```go
// Create phone validator bulk with column mapping
params := &models.BulkPhoneValidatorCreateParams{
    BulkCreateParams: models.BulkCreateParams{
        Name:      "Phone Validation - Customer Database",
        Delimiter: ",",
    },
    BulkPhoneValidatorParams: models.BulkPhoneValidatorParams{
        ColumnPhone:   2, // Phone number column (1-based)
        ColumnCountry: 3, // Country code column (optional but recommended)
    },
}

// Upload CSV and create validation bulk
response, err := client.CreatePhoneValidatorBulk(params, "/path/to/phone-list.csv")
if err != nil {
    log.Fatal("Failed to create phone validator bulk:", err)
}

bulkID := *response.Data.ID
fmt.Printf("Created phone validator bulk: %d\n", bulkID)
```

### Alternative: Phone Numbers Only CSV

```go
// When CSV contains only phone numbers without country codes
params := &models.BulkPhoneValidatorCreateParams{
    BulkCreateParams: models.BulkCreateParams{
        Name:      "Phone Validation - Simple List",
        Delimiter: ",",
    },
    BulkPhoneValidatorParams: models.BulkPhoneValidatorParams{
        ColumnPhone:   1, // Phone number in first column
        ColumnCountry: 0, // No country column
    },
}

response, err := client.CreatePhoneValidatorBulk(params, "simple-phone-list.csv")
if err != nil {
    log.Fatal("Failed to create validation bulk:", err)
}
```

### Launching and Monitoring Validation

```go
// Launch the validation process
launchResponse, err := client.LaunchBulk(models.BulkTypePhoneValidator, bulkID)
if err != nil {
    log.Fatal("Failed to launch phone validation:", err)
}

fmt.Println("Phone validation started!")

// Monitor progress with detailed status
startTime := time.Now()
for {
    progress, err := client.GetBulkProgress(models.BulkTypePhoneValidator, bulkID)
    if err != nil {
        log.Printf("Error checking progress: %v", err)
        time.Sleep(20 * time.Second)
        continue
    }

    elapsed := time.Since(startTime).Round(time.Second)
    fmt.Printf("Validation: %d%% complete (%d processed) - %v elapsed\n",
        progress.Progress,
        progress.Processed,
        elapsed,
    )

    if progress.Status {
        fmt.Println("Phone validation completed!")
        break
    }

    // Check every 20 seconds (validation is typically faster)
    time.Sleep(20 * time.Second)
}
```

### Retrieving Validation Results

```go
// Get detailed validation results
bulk, err := client.GetBulk(models.BulkTypePhoneValidator, bulkID)
if err != nil {
    log.Fatal("Failed to get bulk details:", err)
}

bulkInfo := bulk.Data[0]
fmt.Printf("Validation Summary:\n")
fmt.Printf("- Campaign: %s\n", bulkInfo.Name)
fmt.Printf("- Status: %v\n", bulkInfo.Status)
fmt.Printf("- Numbers Processed: %d\n", bulkInfo.Processed)

// Download all validation results
err = client.SaveBulkResults(
    models.BulkTypePhoneValidator,
    bulkID,
    "validation-results.csv",
    "full",
)
if err != nil {
    log.Fatal("Failed to download results:", err)
}

fmt.Println("Validation results saved to validation-results.csv")
```

### Downloading Segmented Results

```go
// Download only valid phone numbers
err = client.SaveBulkResults(
    models.BulkTypePhoneValidator,
    bulkID,
    "valid-phones.csv",
    "valid",
)
if err != nil {
    log.Printf("Error downloading valid phones: %v", err)
} else {
    fmt.Println("Valid phone numbers saved to valid-phones.csv")
}

// Download invalid or unverifiable phone numbers
err = client.SaveBulkResults(
    models.BulkTypePhoneValidator,
    bulkID,
    "invalid-phones.csv",
    "not_found",
)
if err != nil {
    log.Printf("Error downloading invalid phones: %v", err)
} else {
    fmt.Println("Invalid phone numbers saved to invalid-phones.csv")
}
```

### Managing Phone Validator Operations

```go
// List all phone validator bulks
params := &models.BulkGetParams{
    Page:      1,
    Limit:     10,
    Direction: "desc",
    Filter:    "all",
}

bulks, err := client.GetAllPhoneValidatorBulks(params)
if err != nil {
    log.Fatal("Failed to get phone validator bulks:", err)
}

fmt.Printf("Phone Validator Operations:\n")
for _, bulk := range bulks.Data {
    status := "In Progress"
    if bulk.Status {
        status = "Completed"
    }

    fmt.Printf("- %s (ID: %d)\n", bulk.Name, bulk.BulkID)
    fmt.Printf("  Status: %s | Progress: %d%% | Processed: %d\n",
        status, bulk.Progress, bulk.Processed)

    created := bulk.CreatedAt.Format("2006-01-02 15:04")
    fmt.Printf("  Created: %s\n", created)
    fmt.Println()
}

// Rename a validation bulk
renameParams := &models.BulkRenameParams{
    Name: "Updated Phone Validation Campaign",
}

_, err = client.RenameBulk(models.BulkTypePhoneValidator, bulkID, renameParams)
if err != nil {
    log.Fatal("Failed to rename bulk:", err)
}

// Archive completed validation
_, err = client.ArchiveBulk(models.BulkTypePhoneValidator, bulkID)
if err != nil {
    log.Fatal("Failed to archive bulk:", err)
}
```

### Advanced Validation Workflow with Analysis

```go
type PhoneValidationResult struct {
    OriginalNumber string
    FormattedNumber string
    Valid         bool
    LineType      string // "mobile", "landline", "voip", etc.
    Carrier       string
    Country       string
    CountryCode   string
    Region        string
    Timezone      string
    Risk          string // "low", "medium", "high"
}

func validatePhoneDatabase(client *tomba.Tomba, csvPath string, phoneCol, countryCol int) (*ValidationSummary, error) {
    // Step 1: Create validation bulk
    params := &models.BulkPhoneValidatorCreateParams{
        BulkCreateParams: models.BulkCreateParams{
            Name:      fmt.Sprintf("Phone Validation - %s", time.Now().Format("2006-01-02")),
            Delimiter: ",",
        },
        BulkPhoneValidatorParams: models.BulkPhoneValidatorParams{
            ColumnPhone:   phoneCol,
            ColumnCountry: countryCol,
        },
    }

    response, err := client.CreatePhoneValidatorBulk(params, csvPath)
    if err != nil {
        return nil, fmt.Errorf("failed to create validation bulk: %w", err)
    }

    bulkID := *response.Data.ID
    log.Printf("Created phone validation bulk: %d", bulkID)

    // Step 2: Launch validation
    _, err = client.LaunchBulk(models.BulkTypePhoneValidator, bulkID)
    if err != nil {
        return nil, fmt.Errorf("failed to launch validation: %w", err)
    }

    log.Println("Phone validation in progress...")

    // Step 3: Monitor with progress tracking
    timeout := time.After(30 * time.Minute)
    ticker := time.NewTicker(15 * time.Second)
    defer ticker.Stop()

    startTime := time.Now()

    for {
        select {
        case <-timeout:
            return nil, fmt.Errorf("validation timed out after 30 minutes")

        case <-ticker.C:
            progress, err := client.GetBulkProgress(models.BulkTypePhoneValidator, bulkID)
            if err != nil {
                log.Printf("Error checking progress: %v", err)
                continue
            }

            if progress.Progress%25 == 0 || progress.Status {
                elapsed := time.Since(startTime).Round(time.Second)
                log.Printf("Validation: %d%% (%d processed) - %v",
                    progress.Progress, progress.Processed, elapsed)
            }

            if progress.Status {
                // Step 4: Analyze and download results
                summary, err := analyzeValidationResults(client, bulkID, startTime)
                if err != nil {
                    return nil, fmt.Errorf("failed to analyze results: %w", err)
                }

                log.Printf("Phone validation completed! %d numbers processed in %v",
                    summary.TotalNumbers, summary.Duration.Round(time.Second))

                return summary, nil
            }
        }
    }
}

func analyzeValidationResults(client *tomba.Tomba, bulkID int64, startTime time.Time) (*ValidationSummary, error) {
    // Get bulk details
    bulk, err := client.GetBulk(models.BulkTypePhoneValidator, bulkID)
    if err != nil {
        return nil, err
    }

    info := bulk.Data[0]

    // Download and parse results
    timestamp := time.Now().Format("20060102-150405")
    allResultsFile := fmt.Sprintf("phone-validation-all-%s.csv", timestamp)
    validPhonesFile := fmt.Sprintf("phone-validation-valid-%s.csv", timestamp)

    // Download all results
    err = client.SaveBulkResults(models.BulkTypePhoneValidator, bulkID, allResultsFile, "full")
    if err != nil {
        log.Printf("Warning: Failed to save all results: %v", err)
    }

    // Download valid results
    err = client.SaveBulkResults(models.BulkTypePhoneValidator, bulkID, validPhonesFile, "valid")
    if err != nil {
        log.Printf("Warning: Failed to save valid results: %v", err)
    }

    // Parse results for analysis
    results, err := parseValidationResults(allResultsFile)
    if err != nil {
        log.Printf("Warning: Could not parse results for analysis: %v", err)
        results = []PhoneValidationResult{} // Continue with empty results
    }

    // Build summary
    summary := &ValidationSummary{
        BulkID:       bulkID,
        Name:         info.Name,
        TotalNumbers: info.Processed,
        Duration:     time.Since(startTime),
        AllResultsFile: allResultsFile,
        ValidPhonesFile: validPhonesFile,
    }

    // Analyze results
    if len(results) > 0 {
        summary.ValidNumbers = countValidNumbers(results)
        summary.LineTypes = analyzeLineTypes(results)
        summary.Countries = analyzeCountries(results)
        summary.Carriers = analyzeCarriers(results)
    }

    return summary, nil
}

func parseValidationResults(filename string) ([]PhoneValidationResult, error) {
    file, err := os.Open(filename)
    if err != nil {
        return nil, err
    }
    defer file.Close()

    reader := csv.NewReader(file)
    records, err := reader.ReadAll()
    if err != nil {
        return nil, err
    }

    var results []PhoneValidationResult

    // Skip header row
    for i := 1; i < len(records); i++ {
        record := records[i]
        if len(record) < 3 {
            continue
        }

        result := PhoneValidationResult{
            OriginalNumber: getCSVField(record, 0),
            Valid:         getCSVField(record, 1) == "valid",
            LineType:      getCSVField(record, 2),
        }

        // Parse additional fields if available
        if len(record) > 3 {
            result.FormattedNumber = getCSVField(record, 3)
        }
        if len(record) > 4 {
            result.Carrier = getCSVField(record, 4)
        }
        if len(record) > 5 {
            result.Country = getCSVField(record, 5)
        }
        if len(record) > 6 {
            result.CountryCode = getCSVField(record, 6)
        }

        results = append(results, result)
    }

    return results, nil
}

func getCSVField(record []string, index int) string {
    if index < len(record) {
        return strings.Trim(record[index], `"`)
    }
    return ""
}

func countValidNumbers(results []PhoneValidationResult) int {
    count := 0
    for _, result := range results {
        if result.Valid {
            count++
        }
    }
    return count
}

func analyzeLineTypes(results []PhoneValidationResult) map[string]int {
    types := make(map[string]int)
    for _, result := range results {
        if result.Valid && result.LineType != "" {
            types[result.LineType]++
        }
    }
    return types
}

func analyzeCountries(results []PhoneValidationResult) map[string]int {
    countries := make(map[string]int)
    for _, result := range results {
        if result.Valid && result.Country != "" {
            countries[result.Country]++
        }
    }
    return countries
}

func analyzeCarriers(results []PhoneValidationResult) map[string]int {
    carriers := make(map[string]int)
    for _, result := range results {
        if result.Valid && result.Carrier != "" {
            carriers[result.Carrier]++
        }
    }
    return carriers
}

// Summary structure for validation results
type ValidationSummary struct {
    BulkID          int64
    Name            string
    TotalNumbers    int
    ValidNumbers    int
    Duration        time.Duration
    AllResultsFile  string
    ValidPhonesFile string
    LineTypes       map[string]int
    Countries       map[string]int
    Carriers        map[string]int
}

func (s *ValidationSummary) String() string {
    validRate := float64(s.ValidNumbers) / float64(s.TotalNumbers) * 100

    result := fmt.Sprintf(`
Phone Validation Summary:
- Bulk ID: %d
- Name: %s
- Total Numbers: %d
- Valid Numbers: %d (%.1f%%)
- Duration: %v
- All Results: %s
- Valid Only: %s`,
        s.BulkID, s.Name, s.TotalNumbers, s.ValidNumbers, validRate,
        s.Duration.Round(time.Second), s.AllResultsFile, s.ValidPhonesFile)

    if len(s.LineTypes) > 0 {
        result += "\n\nLine Type Distribution:"
        for lineType, count := range s.LineTypes {
            percent := float64(count) / float64(s.ValidNumbers) * 100
            result += fmt.Sprintf("\n- %s: %d (%.1f%%)", lineType, count, percent)
        }
    }

    if len(s.Countries) > 0 {
        result += "\n\nTop Countries:"
        // Show top 5 countries
        type countryCount struct {
            country string
            count   int
        }
        var sorted []countryCount
        for country, count := range s.Countries {
            sorted = append(sorted, countryCount{country, count})
        }
        // Simple sort by count (descending)
        for i := 0; i < len(sorted)-1; i++ {
            for j := 0; j < len(sorted)-i-1; j++ {
                if sorted[j].count < sorted[j+1].count {
                    sorted[j], sorted[j+1] = sorted[j+1], sorted[j]
                }
            }
        }

        for i, cc := range sorted {
            if i >= 5 { // Top 5 only
                break
            }
            percent := float64(cc.count) / float64(s.ValidNumbers) * 100
            result += fmt.Sprintf("\n- %s: %d (%.1f%%)", cc.country, cc.count, percent)
        }
    }

    return result
}

// Usage example
func main() {
    client := tomba.NewTomba("your-api-key", "your-secret-key")

    // Validate phone database
    summary, err := validatePhoneDatabase(client, "phone-database.csv", 2, 3)
    if err != nil {
        log.Fatal("Phone validation failed:", err)
    }

    fmt.Println(summary)
}
```

### CSV File Format Examples

**With Country Codes**

```text
name,phone,country
John Doe,+1-555-0123,US
Jane Smith,+44-20-7123-4567,GB
Pierre Martin,+33-1-23-45-67-89,FR
Maria Garcia,+34-91-123-4567,ES
```

**Phone Numbers Only**

```text
phone
+1-555-0123
+44-20-7123-4567
+33-1-23-45-67-89
+34-91-123-4567
555-0123
```

**With Additional Context**

```text
customer_id,name,phone,country,source
12345,John Doe,+1-555-0123,US,Website
12346,Jane Smith,+44-20-7123-4567,GB,CRM Import
12347,Pierre Martin,+33-1-23-45-67-89,FR,Lead Gen
```

## Best Practices

- **Consistent Formatting**: Use consistent international phone number formatting
- **Country Information**: Include country codes or country columns for accurate validation
- **Regular Validation**: Validate phone lists regularly to maintain quality
- **Campaign Optimization**: Use validation results to improve calling and SMS campaigns
- **Compliance Checking**: Verify numbers comply with local telecommunications regulations
- **Batch Processing**: Process large lists in batches under 2,500 limit
- **Result Segmentation**: Separate valid, invalid, and risky numbers for targeted use
- **Quality Thresholds**: Set validation quality standards based on use case
- **Data Privacy**: Ensure phone validation complies with privacy regulations
- **Cost Optimization**: Track validation costs and optimize processing efficiency
