Email Verification API - Go / Golang 2026 | BounceZero
BlogDeveloper Guides

Email Verification API
Go / Golang 2026

Four Go code patterns for integrating the BounceZero email verification API: a minimal single-address check with net/http, a goroutine worker pool for bulk lists, context timeout patterns, and a Gin middleware for blocking invalid emails at signup.

By BounceZero Team |July 2026 |Go 1.22+

API reference

Parameter Value
Endpoint https://api.bouncezero.io/v1/verify
Method GET
Auth header X-Api-Key: YOUR_KEY
Query param email=user%40example.com
Response JSON: classification, score, is_deliverable, is_catch_all, is_disposable
Classification valid - invalid - catch_all - risky - unknown
Rate limit 10 req/s default; 100/s on Pro

1. Single address check (net/http)

No third-party dependencies. Standard library only.

package main

import (
    "context"
    "encoding/json"
    "fmt"
    "net/http"
    "net/url"
    "time"
)

const apiKey = "YOUR_BOUNCEZERO_KEY"

type VerifyResult struct {
    Email          string  `json:"email"`
    Classification string  `json:"classification"`
    Score          float64 `json:"score"`
    IsDeliverable  bool    `json:"is_deliverable"`
    IsCatchAll     bool    `json:"is_catch_all"`
    IsDisposable   bool    `json:"is_disposable"`
}

func verifyEmail(ctx context.Context, email string) (VerifyResult, error) {
    endpoint := "https://api.bouncezero.io/v1/verify?email=" + url.QueryEscape(email)

    req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
    if err != nil {
        return VerifyResult{}, err
    }
    req.Header.Set("X-Api-Key", apiKey)

    client := &http.Client{Timeout: 15 * time.Second}
    resp, err := client.Do(req)
    if err != nil {
        return VerifyResult{}, err
    }
    defer resp.Body.Close()

    var result VerifyResult
    if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
        return VerifyResult{}, err
    }
    return result, nil
}

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
    defer cancel()

    result, err := verifyEmail(ctx, "[email protected]")
    if err != nil {
        fmt.Printf("Error: %v\n", err)
        return
    }
    fmt.Printf("Classification: %s | Deliverable: %v | Score: %.2f\n",
        result.Classification, result.IsDeliverable, result.Score)
}

2. Bulk verification with goroutine worker pool

Fan out across N workers; rate-limited to stay within API quota.

package main

import (
    "context"
    "fmt"
    "sync"
    "time"
)

type job struct {
    index int
    email string
}

type result struct {
    index  int
    email  string
    result VerifyResult
    err    error
}

func bulkVerify(ctx context.Context, emails []string, workers int) []result {
    jobs    := make(chan job, len(emails))
    results := make(chan result, len(emails))

    // Start workers
    var wg sync.WaitGroup
    for i := 0; i < workers; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            for j := range jobs {
                // Honour rate limit: 10 req/s = 100ms between requests per worker
                time.Sleep(100 * time.Millisecond)
                res, err := verifyEmail(ctx, j.email)
                results <- result{index: j.index, email: j.email, result: res, err: err}
            }
        }()
    }

    // Enqueue jobs
    for i, email := range emails {
        jobs <- job{index: i, email: email}
    }
    close(jobs)

    // Wait then close results
    go func() {
        wg.Wait()
        close(results)
    }()

    // Collect
    out := make([]result, 0, len(emails))
    for r := range results {
        out = append(out, r)
    }
    return out
}

func main() {
    emails := []string{
        "[email protected]",
        "[email protected]",
        "invalid@@broken.com",
    }

    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
    defer cancel()

    // 5 workers - adjust to stay within your plan's rate limit
    results := bulkVerify(ctx, emails, 5)

    for _, r := range results {
        if r.err != nil {
            fmt.Printf("[%d] %s - ERROR: %v\n", r.index, r.email, r.err)
            continue
        }
        fmt.Printf("[%d] %s - %s (deliverable: %v)\n",
            r.index, r.email, r.result.Classification, r.result.IsDeliverable)
    }
}

3. Context deadline and retry with backoff

Handles transient 429 rate-limit responses with exponential backoff.

func verifyWithRetry(ctx context.Context, email string, maxRetries int) (VerifyResult, error) {
    delay := 500 * time.Millisecond
    for attempt := 0; attempt <= maxRetries; attempt++ {
        result, err := verifyEmail(ctx, email)
        if err == nil {
            return result, nil
        }
        if ctx.Err() != nil {
            return VerifyResult{}, ctx.Err() // context cancelled or deadline exceeded
        }
        if attempt < maxRetries {
            select {
            case <-time.After(delay):
                delay *= 2 // exponential backoff
            case <-ctx.Done():
                return VerifyResult{}, ctx.Err()
            }
        }
    }
    return VerifyResult{}, fmt.Errorf("max retries exceeded for %s", email)
}

4. Gin middleware - block invalid emails at signup

Reject registrations with invalid or disposable email addresses before they reach your database.

package main

import (
    "context"
    "net/http"
    "time"

    "github.com/gin-gonic/gin"
)

// EmailVerifyMiddleware rejects invalid/disposable emails on signup endpoints.
func EmailVerifyMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        var body struct {
            Email string `json:"email" binding:"required,email"`
        }
        if err := c.ShouldBindJSON(&body); err != nil {
            c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "invalid request"})
            return
        }

        ctx, cancel := context.WithTimeout(c.Request.Context(), 10*time.Second)
        defer cancel()

        result, err := verifyEmail(ctx, body.Email)
        if err != nil {
            // Fail open: don't block signup on API timeout
            c.Set("email_verification_skipped", true)
            c.Next()
            return
        }

        if result.IsDisposable {
            c.AbortWithStatusJSON(http.StatusUnprocessableEntity, gin.H{
                "error": "Disposable email addresses are not allowed.",
            })
            return
        }
        if result.Classification == "invalid" {
            c.AbortWithStatusJSON(http.StatusUnprocessableEntity, gin.H{
                "error": "This email address does not appear to exist.",
            })
            return
        }

        c.Set("email_classification", result.Classification)
        c.Next()
    }
}

func main() {
    r := gin.Default()
    r.POST("/signup", EmailVerifyMiddleware(), func(c *gin.Context) {
        c.JSON(http.StatusCreated, gin.H{"message": "Account created"})
    })
    r.Run(":8080")
}

Best practices in Go

Reuse http.Client

Create a single &http.Client{} at package level rather than inside each request. net/http reuses TCP connections when the client is reused, reducing latency on bulk calls.

Pre-validate with net/mail

Run net/mail.ParseAddress(email) before the API call to reject obvious invalids locally at zero latency. Saves 5-10% of API calls on typical user input.

Fail open on timeout

In signup flows, if the API call times out, allow the signup and flag the address for post-signup verification. Never block a user because of an API timeout.

Context propagation

Always pass the request context (c.Request.Context() in Gin) into verifyEmail so that if the HTTP request is cancelled, the API call cancels too.

New to verification? Start with the complete email verification guide.

Frequently Asked Questions

How do I verify email addresses in Go?

Use net/http to call the BounceZero API: build a GET request to /v1/verify?email=addr with your API key in the X-Api-Key header, decode the JSON response, and check the classification field. For bulk verification, use a goroutine worker pool with a buffered channel to fan out requests concurrently.

Should I validate email addresses in Go before verifying them?

Yes - pre-validate with net/mail.ParseAddress() or a basic format check to reject obvious invalids before the API call. This saves 5-15% of API calls on typical user-submitted lists. Pre-filter missing @, invalid TLD, and empty strings locally before hitting the remote endpoint.

What is the best Go library for email verification?

For production Go apps, a REST API approach (net/http + BounceZero) is more reliable than local SMTP libraries because: (1) it handles provider-specific rules for Gmail, Microsoft 365, Yahoo; (2) it avoids IP reputation risk from your own server making SMTP probes; (3) it handles catch-all detection and greylist retry logic. Local libraries like ‘emailverifier’ can do basic MX/SMTP checks but lack provider-specific intelligence.

The API your Go service is calling.

BounceZero: email verification API with 99.9% uptime SLA, sub-second response time, and 100 free credits to start.

AL

Written by

Ayoub Lebda

Founder, BounceZero - Email-infrastructure engineer

Ayoub built BounceZero's 5-stage validation pipeline, its dedicated BGP-announced IP infrastructure, and the Patroni HA PostgreSQL cluster behind every verification. Previously built high-volume email delivery infrastructure. Trained at 1337 Benguerir (École 42 network, 2019). Open-source: bgp_analyzer.

Email verification & deliverability explained

Deep-dive guides on how email verification and inbox placement work