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.
| 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 |
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)
}
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)
}
}
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)
}
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")
}
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.
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.
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.
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.
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.
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.
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.
BounceZero: email verification API with 99.9% uptime SLA, sub-second response time, and 100 free credits to start.
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.
Deep-dive guides on how email verification and inbox placement work
272,446-domain census: DMARC gap, provider divide, catch-all rates
10.2M verifications: 12.3% of addresses are dead, and where they hide
826K re-verifications: only 19% of valid addresses survive 90 days
True catch-all is 1.4% - most of what looks catch-all is unprobeable providers
info@ bounces 4.5x more than personal addresses - measured, not guessed
The 3x invalid-rate gap that vanishes when you control for domain size
Explore other topics