EmailCheckerEmailChecker

Documentação · Go

Validar email em Go

Go tem net/http excelente na stdlib — não precisa de nenhuma biblioteca externa. O fluxo é em lote: POST com os emails, guarda o id e consulta até fechar. Os exemplos abaixo são puro Go, prontos pra usar em qualquer projeto.

Instalação (go mod)

bash
# Nenhuma dependência externa.
# Apenas:
go mod init seu-projeto

Passo 1 · POST /api/v1/batch

Enviar o lote

Endpoint assíncrono. Manda de 1 a 100.000 emails num POST e recebe 201 com o id do lote. O campo name é opcional e serve pra você achar o lote depois.

go
// main.go
package main

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"os"
	"time"
)

const baseURL = "https://app.emailchecker.email/api/v1"

// um client de pacote: reaproveita o pool de conexões
var httpClient = &http.Client{Timeout: 30 * time.Second}

type Email struct {
	Email  string `json:"email"`
	Result string `json:"result"` // deliverable | undeliverable | risky | unknown
	Reason string `json:"reason"`
	Score  int    `json:"score"`
	IsFree bool   `json:"is_free"`
}

type Batch struct {
	ID         string  `json:"id"`
	Status     string  `json:"status"`
	Size       int     `json:"size"`
	FinishedAt *string `json:"finished_at"`
	Emails     []Email `json:"emails"`
}

type envelope struct {
	Data  *Batch `json:"data"`
	Error string `json:"error"`
}

func do(method, path string, payload any) (*Batch, error) {
	var body io.Reader
	if payload != nil {
		b, err := json.Marshal(payload)
		if err != nil {
			return nil, err
		}
		body = bytes.NewReader(b)
	}

	req, err := http.NewRequest(method, baseURL+path, body)
	if err != nil {
		return nil, err
	}
	req.Header.Set("Authorization", "Bearer "+os.Getenv("EMAILCHECKER_API_KEY"))
	req.Header.Set("Content-Type", "application/json")

	resp, err := httpClient.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	// limite: 300 req/min e 10.000/hora por key
	if resp.StatusCode == http.StatusTooManyRequests {
		time.Sleep(time.Minute)
		return do(method, path, payload)
	}

	var env envelope
	if err := json.NewDecoder(resp.Body).Decode(&env); err != nil {
		return nil, err
	}
	if resp.StatusCode >= 400 || env.Data == nil {
		return nil, fmt.Errorf("emailchecker %d: %s", resp.StatusCode, env.Error)
	}
	return env.Data, nil
}

// submitBatch: POST /api/v1/batch — de 1 a 100.000 emails; name é opcional.
func submitBatch(emails []string, name string) (string, error) {
	payload := map[string]any{"emails": emails}
	if name != "" {
		payload["name"] = name
	}
	batch, err := do(http.MethodPost, "/batch", payload)
	if err != nil {
		return "", err
	}
	return batch.ID, nil
}

Passo 2 · GET /api/v1/batch/{id}

Consultar o resultado

Não enviamos webhook: o jeito de saber que terminou é consultar o lote até status virar completed. Só aí o array emails vem preenchido, com result, score, reason e os atributos isv_*. O limite é de 300 req/min e 10.000/hora por key — um poll a cada 5 segundos sobra.

go
// poll.go — não existe webhook: consulte o lote até ele fechar

var errTimeout = errors.New("lote não terminou dentro do tempo")

// waitForBatch: GET /api/v1/batch/{id} até status == "completed".
// 5s entre polls dá ~12 req/min, folgado nos 300 req/min da key.
func waitForBatch(id string, interval, timeout time.Duration) (*Batch, error) {
	deadline := time.Now().Add(timeout)

	for time.Now().Before(deadline) {
		batch, err := do(http.MethodGet, "/batch/"+id, nil)
		if err != nil {
			return nil, err
		}
		switch batch.Status {
		case "completed":
			return batch, nil
		case "failed", "cancelled":
			return nil, fmt.Errorf("lote %s", batch.Status)
		}
		time.Sleep(interval) // pending | processing
	}

	return nil, errTimeout
}

func main() {
	id, err := submitBatch([]string{"joao@empresa.com.br", "contato@outra.com.br"}, "minha-lista")
	if err != nil {
		panic(err)
	}
	fmt.Println("lote enviado:", id)

	batch, err := waitForBatch(id, 5*time.Second, 30*time.Minute)
	if err != nil {
		panic(err)
	}

	// Emails só vem preenchido com Status == "completed"
	for _, e := range batch.Emails {
		if e.Result == "deliverable" && e.Score >= 80 {
			fmt.Println(e.Email, e.Score, e.Reason, e.IsFree)
		}
	}
}

Boas práticas

Pitfalls

Armadilhas específicas de Go

Erros sutis que só aparecem em produção. Conheça antes de subir o código.

Esquecer defer resp.Body.Close() vaza conexões

Se você não fechar o resp.Body, a conexão não volta pro pool do Transport e num polling longo você esgota file descriptors com "too many open files". Faça defer resp.Body.Close() logo após checar err != nil — e nunca antes, porque resp é nil quando há erro.

Client.Timeout vs context.WithTimeout

O http.Client.Timeout cobre cada request mas não cancela o loop de polling. Pra um deadline que se propaga (cancelar ao fechar o handler HTTP que chamou), use http.NewRequestWithContext com context.WithTimeout e defer cancel(); só o Client.Timeout não aborta a goroutine chamadora.

json.Decode não falha em status de erro

O decoder lê o corpo independente do status; um 401 traz {"data":null,"error":"..."} e o Decode preenche a struct sem reclamar. Cheque resp.StatusCode >= 400 (ou env.Data == nil) antes de tratar como sucesso, e use o campo error do envelope pra logar a mensagem real.

Reusar o http.Client, não criar um por chamada

Instanciar &http.Client{} a cada request descarta o connection pooling do Transport e custa um handshake TLS por poll. Declare um client de pacote (var httpClient = &http.Client{Timeout: 30 * time.Second}) e reaproveite; o http.DefaultClient não tem timeout, então evite-o em produção.

Mais linguagens

Exemplos em outras linguagens

Comece agora

Pronto pra parar de mandar email pra endereço morto?

Comece grátis com 500 créditos. Sem cartão, sem compromisso.