# Similar Domain Bulk

## Overview

[Bulks Similar](https://tomba.io/bulks/domain-similar) helps you discover companies and domains similar to your target prospects. This feature is invaluable for competitive analysis, market research, and expanding your total addressable market by finding lookalike companies.

### Key Features

- **Competitor Discovery**: Find domains similar to your target companies
- **Market Expansion**: Identify new prospects in similar industries or niches
- **Batch Processing**: Process up to 500 domains simultaneously
- **Industry Insights**: Discover companies in the same vertical or market segment
- **Email Discovery**: Find contact information for similar companies
- **Export Capabilities**: Download comprehensive results for further analysis

### How Similar Domain Bulk Works

1. **Input Reference Domains**: Provide domains of companies you want to find similar matches for
2. **Configure Search Parameters**: Set email type preferences and result limits
3. **Launch Analysis**: Start the similarity search algorithm
4. **Review Results**: Examine discovered similar domains and their contact information
5. **Export Data**: Download results for integration with your CRM or outreach tools

### Limitations

- Each Bulk is limited to 500 domains
- Additional rows will be skipped
- Some special or unexpected characters may be deleted in the file
- Invalid websites won't be imported
- Duplicate URLs won't be imported
- For better results, we use the domain name instead of the company name

## Go SDK Integration

### Installation

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

### Basic Setup

```go
package main

import (
    "fmt"
    "log"
    "time"
    "strings"
    "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 similar domain discovery code here
}
```

### Creating Similar Domain Bulk with Domain List

```go
// Reference domains for finding similar companies
referenceDomains := []string{
    "stripe.com",
    "square.com",
    "paypal.com",
    "adyen.com",
    "checkout.com",
}

params := &models.BulkCreateParams{
    Name:    "Similar Companies - Fintech Payment Processors",
    List:    strings.Join(referenceDomains, "\n"),
    Sources: true,   // Include sources for context
    Notifie: true,   // Notify when complete
}

// Create similar domain bulk operation
response, err := client.CreateBulk(models.BulkTypeSimilar, params)
if err != nil {
    log.Fatal("Failed to create similar domain bulk:", err)
}

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

### Creating Similar Domain Bulk from CSV File

```go
// Create similar domain bulk from CSV file
params := &models.BulkCreateParams{
    Name:      "Market Analysis - SaaS Competitors",
    Delimiter: ",",
    Column:    1,      // Domain column (1-based index)
    Sources:   true,
    Notifie:   true,
}

// Upload CSV with reference domains
response, err := client.CreateBulkWithFile(
    models.BulkTypeSimilar,
    params,
    "/path/to/reference-domains.csv",
)
if err != nil {
    log.Fatal("Failed to create bulk with file:", err)
}

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

### Single Similar Domain Query (for comparison)

```go
// Find similar domains for a single company
targetDomain := "shopify.com"

similarDomains, err := client.SimilarDomains(targetDomain)
if err != nil {
    log.Printf("Failed to find similar domains for %s: %v", targetDomain, err)
} else {
    fmt.Printf("Similar domains to %s:\n", targetDomain)
    for _, domain := range similarDomains.Data.Similar {
        fmt.Printf("- %s (Score: %d)\n", domain.Domain, domain.Score)
    }
}
```

### Launching and Monitoring Similar Domain Discovery

```go
// Launch the similar domain discovery
launchResponse, err := client.LaunchBulk(models.BulkTypeSimilar, bulkID)
if err != nil {
    log.Fatal("Failed to launch similar domain bulk:", err)
}

fmt.Println("Similar domain discovery started!")

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

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

    if progress.Status {
        fmt.Println("Similar domain discovery completed!")
        break
    }

    // Check every 45 seconds (similar domain discovery can take time)
    time.Sleep(45 * time.Second)
}
```

### Retrieving Similar Domain Results

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

bulkInfo := bulk.Data[0]
fmt.Printf("Similar Domain Discovery Results:\n")
fmt.Printf("- Campaign: %s\n", bulkInfo.Name)
fmt.Printf("- Status: %v\n", bulkInfo.Status)
fmt.Printf("- Reference Domains Processed: %d\n", bulkInfo.Processed)

if bulkInfo.TotalEmails != nil {
    fmt.Printf("- Contact Emails Found: %d\n", *bulkInfo.TotalEmails)
}

// Download all results with similar domains and contacts
err = client.SaveBulkResults(
    models.BulkTypeSimilar,
    bulkID,
    "similar-domains.csv",
    "full",
)
if err != nil {
    log.Fatal("Failed to download results:", err)
}

fmt.Println("Similar domain results saved to similar-domains.csv")
```

### Managing Similar Domain Operations

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

bulks, err := client.GetAllSimilarBulks(params)
if err != nil {
    log.Fatal("Failed to get similar domain bulks:", err)
}

fmt.Printf("Similar Domain 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)

    if bulk.TotalEmails != nil && *bulk.TotalEmails > 0 {
        fmt.Printf("  Emails Found: %d\n", *bulk.TotalEmails)
    }

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

// Archive old analyses
for _, bulk := range bulks.Data {
    if bulk.Status && time.Since(bulk.CreatedAt) > 14*24*time.Hour { // 14 days old
        _, err := client.ArchiveBulk(models.BulkTypeSimilar, bulk.BulkID)
        if err != nil {
            log.Printf("Failed to archive bulk %d: %v", bulk.BulkID, err)
        } else {
            fmt.Printf("Archived old analysis: %s\n", bulk.Name)
        }
    }
}
```

### Advanced Market Analysis Workflow

```go
type SimilarDomainResult struct {
    ReferenceDomain string
    SimilarDomain   string
    SimilarityScore int
    Industry        string
    CompanySize     string
    ContactEmails   []string
    Technologies    []string
}

type MarketAnalysis struct {
    ReferenceDomains    []string
    SimilarDomainsFound int
    ContactsDiscovered  int
    TopIndustries      map[string]int
    CompetitorDomains  []string
}

func performMarketAnalysis(client *tomba.Tomba, referenceDomains []string, industry string) (*MarketAnalysis, error) {
    // Validate domain count
    if len(referenceDomains) > 500 {
        log.Printf("Warning: Limiting reference domains from %d to 500", len(referenceDomains))
        referenceDomains = referenceDomains[:500]
    }

    log.Printf("Starting market analysis for %d reference domains in %s industry",
        len(referenceDomains), industry)

    // Step 1: Create similar domain bulk
    params := &models.BulkCreateParams{
        Name:    fmt.Sprintf("Market Analysis - %s - %s",
            strings.Title(industry), time.Now().Format("2006-01-02")),
        List:    strings.Join(referenceDomains, "\n"),
        Sources: true,
        Notifie: false, // Don't notify for analysis
    }

    response, err := client.CreateBulk(models.BulkTypeSimilar, params)
    if err != nil {
        return nil, fmt.Errorf("failed to create similar domain bulk: %w", err)
    }

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

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

    // Monitor with extended timeout for similar domain discovery
    timeout := time.After(90 * time.Minute)
    ticker := time.NewTicker(60 * time.Second)
    defer ticker.Stop()

    startTime := time.Now()

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

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

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

            if progress.Status {
                // Step 3: Analyze results
                return analyzeMarketResults(client, bulkID, referenceDomains, startTime)
            }
        }
    }
}

func analyzeMarketResults(client *tomba.Tomba, bulkID int64, referenceDomains []string, startTime time.Time) (*MarketAnalysis, error) {
    // Download results
    timestamp := time.Now().Format("20060102-150405")
    resultsFile := fmt.Sprintf("market-analysis-%s.csv", timestamp)

    err := client.SaveBulkResults(models.BulkTypeSimilar, bulkID, resultsFile, "full")
    if err != nil {
        return nil, fmt.Errorf("failed to download results: %w", err)
    }

    log.Printf("Downloaded market analysis results to: %s", resultsFile)

    // Parse and analyze results
    results, err := parseSimilarDomainResults(resultsFile)
    if err != nil {
        log.Printf("Warning: Could not parse results for detailed analysis: %v", err)
        // Continue with basic analysis from bulk info
        bulk, err := client.GetBulk(models.BulkTypeSimilar, bulkID)
        if err != nil {
            return nil, err
        }

        info := bulk.Data[0]
        analysis := &MarketAnalysis{
            ReferenceDomains: referenceDomains,
        }

        if info.TotalEmails != nil {
            analysis.ContactsDiscovered = *info.TotalEmails
        }

        return analysis, nil
    }

    // Detailed analysis
    analysis := &MarketAnalysis{
        ReferenceDomains:   referenceDomains,
        SimilarDomainsFound: len(results),
        TopIndustries:      make(map[string]int),
    }

    // Extract unique similar domains and analyze
    domainSet := make(map[string]bool)
    totalContacts := 0

    for _, result := range results {
        if result.SimilarDomain != "" {
            domainSet[result.SimilarDomain] = true
            totalContacts += len(result.ContactEmails)

            if result.Industry != "" {
                analysis.TopIndustries[result.Industry]++
            }
        }
    }

    // Convert domain set to slice
    for domain := range domainSet {
        analysis.CompetitorDomains = append(analysis.CompetitorDomains, domain)
    }

    analysis.SimilarDomainsFound = len(analysis.CompetitorDomains)
    analysis.ContactsDiscovered = totalContacts

    duration := time.Since(startTime)
    log.Printf("Market analysis completed in %v:", duration.Round(time.Second))
    log.Printf("- Reference domains: %d", len(analysis.ReferenceDomains))
    log.Printf("- Similar domains found: %d", analysis.SimilarDomainsFound)
    log.Printf("- Contact emails discovered: %d", analysis.ContactsDiscovered)

    if len(analysis.TopIndustries) > 0 {
        log.Printf("- Industries identified: %d", len(analysis.TopIndustries))
    }

    return analysis, nil
}

func parseSimilarDomainResults(filename string) ([]SimilarDomainResult, 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 []SimilarDomainResult

    // Parse CSV results (simplified - actual format may vary)
    for i := 1; i < len(records); i++ { // Skip header
        record := records[i]
        if len(record) < 2 {
            continue
        }

        result := SimilarDomainResult{
            ReferenceDomain: getCSVField(record, 0),
            SimilarDomain:   getCSVField(record, 1),
        }

        // Parse additional fields if available
        if len(record) > 2 {
            fmt.Sscanf(getCSVField(record, 2), "%d", &result.SimilarityScore)
        }
        if len(record) > 3 {
            result.Industry = getCSVField(record, 3)
        }
        if len(record) > 4 {
            result.CompanySize = getCSVField(record, 4)
        }
        if len(record) > 5 {
            emails := getCSVField(record, 5)
            if emails != "" {
                result.ContactEmails = strings.Split(emails, ";")
            }
        }

        results = append(results, result)
    }

    return results, nil
}

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

// Generate market report
func generateMarketReport(analysis *MarketAnalysis, outputFile string) error {
    file, err := os.Create(outputFile)
    if err != nil {
        return err
    }
    defer file.Close()

    fmt.Fprintf(file, "Market Analysis Report\n")
    fmt.Fprintf(file, "Generated: %s\n\n", time.Now().Format("2006-01-02 15:04:05"))

    fmt.Fprintf(file, "Reference Domains Analyzed: %d\n", len(analysis.ReferenceDomains))
    fmt.Fprintf(file, "Similar Domains Discovered: %d\n", analysis.SimilarDomainsFound)
    fmt.Fprintf(file, "Contact Emails Found: %d\n", analysis.ContactsDiscovered)

    if len(analysis.TopIndustries) > 0 {
        fmt.Fprintf(file, "\nIndustry Distribution:\n")
        for industry, count := range analysis.TopIndustries {
            fmt.Fprintf(file, "- %s: %d companies\n", industry, count)
        }
    }

    if len(analysis.CompetitorDomains) > 0 {
        fmt.Fprintf(file, "\nDiscovered Competitor Domains:\n")
        for i, domain := range analysis.CompetitorDomains {
            fmt.Fprintf(file, "%d. %s\n", i+1, domain)
            if i >= 49 { // Limit to top 50
                fmt.Fprintf(file, "... and %d more\n", len(analysis.CompetitorDomains)-50)
                break
            }
        }
    }

    return nil
}

// Complete market analysis workflow
func runCompetitorAnalysis(client *tomba.Tomba, csvPath, industry string) error {
    // Read reference domains from CSV
    file, err := os.Open(csvPath)
    if err != nil {
        return fmt.Errorf("failed to open CSV: %w", err)
    }
    defer file.Close()

    reader := csv.NewReader(file)
    records, err := reader.ReadAll()
    if err != nil {
        return fmt.Errorf("failed to read CSV: %w", err)
    }

    var referenceDomains []string
    for i, record := range records {
        if i == 0 || len(record) == 0 { // Skip header and empty rows
            continue
        }
        domain := strings.TrimSpace(record[0])
        if domain != "" {
            referenceDomains = append(referenceDomains, domain)
        }
    }

    if len(referenceDomains) == 0 {
        return fmt.Errorf("no valid reference domains found in CSV")
    }

    log.Printf("Starting competitor analysis with %d reference domains", len(referenceDomains))

    // Perform market analysis
    analysis, err := performMarketAnalysis(client, referenceDomains, industry)
    if err != nil {
        return fmt.Errorf("market analysis failed: %w", err)
    }

    // Generate report
    timestamp := time.Now().Format("20060102-150405")
    reportFile := fmt.Sprintf("market-report-%s.txt", timestamp)

    err = generateMarketReport(analysis, reportFile)
    if err != nil {
        log.Printf("Warning: Could not generate report file: %v", err)
    } else {
        log.Printf("Market analysis report saved to: %s", reportFile)
    }

    // Print summary
    expansionRate := float64(analysis.SimilarDomainsFound) / float64(len(analysis.ReferenceDomains)) * 100

    fmt.Printf("\nCompetitor Analysis Summary:\n")
    fmt.Printf("Reference Companies: %d\n", len(analysis.ReferenceDomains))
    fmt.Printf("Similar Companies Found: %d\n", analysis.SimilarDomainsFound)
    fmt.Printf("Market Expansion Rate: %.1fx\n", expansionRate/100)
    fmt.Printf("Contact Emails Discovered: %d\n", analysis.ContactsDiscovered)

    return nil
}

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

    err := runCompetitorAnalysis(client, "reference-companies.csv", "fintech")
    if err != nil {
        log.Fatal("Competitor analysis failed:", err)
    }
}
```

### CSV File Format Examples

**Simple Domain List**

```text
domain
stripe.com
square.com
paypal.com
adyen.com
checkout.com
```

**With Company Context**

```text
domain,company_name,industry,notes
stripe.com,Stripe,Fintech,Payment processor
square.com,Square,Fintech,POS solutions
paypal.com,PayPal,Fintech,Digital payments
adyen.com,Adyen,Fintech,Global payments
checkout.com,Checkout.com,Fintech,Payment gateway
```

**With Size and Region**

```text
domain,company,size,region,target_market
shopify.com,Shopify,Large,Global,E-commerce platform
bigcommerce.com,BigCommerce,Medium,US/EMEA,E-commerce platform
woocommerce.com,WooCommerce,Large,Global,WordPress commerce
magento.com,Magento,Large,Global,Enterprise commerce
```

## Best Practices

- **Quality Input**: Use well-established, representative domains as your reference points
- **Industry Focus**: Input domains from the same industry for more relevant results
- **Regular Updates**: Refresh your similar domain searches periodically as markets evolve
- **Cross-Reference**: Validate similarity results with manual research
- **Segment Results**: Organize similar domains by industry or company size
- **Batch Processing**: Process reference domains in batches under 500 limit
- **Market Research**: Use results for competitive analysis and market mapping
- **Contact Discovery**: Leverage found contacts for outreach and partnership opportunities
- **Technology Tracking**: Monitor technology stacks of similar companies
- **Opportunity Identification**: Use similar domains to identify market gaps and opportunities
