Documentação · 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.
# Nenhuma dependência externa.
# Apenas:
go mod init seu-projetoPasso 1 · POST /api/v1/batch
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.
// 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}
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.
// 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)
}
}
}Pitfalls
Erros sutis que só aparecem em produção. Conheça antes de subir o código.
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.
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.
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.
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
Comece grátis com 500 créditos. Sem cartão, sem compromisso.