Email verification API in Go
To verify an email in Go, parse it with net/mail, then send a GET request to https://simpleverifier.com/api/v1/verify with an Authorization: Bearer header using an http.Client that has a 30-second timeout. Decode the JSON status: valid, risky or invalid. On an error, log it and let the address through.
By SimpleVerifier Team
Before you start
- Create a key on the API keys page. Keys start with
sv_live_and are shown once. - Set
SIMPLEVERIFIER_KEYin the environment the binary runs in. - API access comes with Pro at $39/month (10,000 calls a month included, then $1.50 per 1,000), Business at $99/month (100,000 calls, then $1.00 per 1,000) and Agency at $249/month (300,000 calls, then $1.00 per 1,000). See pricing.
1. Add the package
Standard library only. The request uses the caller's context, so a cancelled HTTP request cancels the check too.
// Package simpleverifier checks email addresses with the SimpleVerifier API.
package simpleverifier
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/mail"
"net/url"
"os"
"time"
)
const apiURL = "https://simpleverifier.com/api/v1/verify"
var client = &http.Client{Timeout: 30 * time.Second}
// Result is the JSON body the verify endpoint returns.
type Result struct {
Email string `json:"email"`
Status string `json:"status"` // "valid", "risky" or "invalid"
Reason string `json:"reason"`
MailboxChecked bool `json:"mailbox_checked"`
}
// EmailStatus returns "valid", "risky" or "invalid". When the API could not
// answer it returns "" and an error; callers should let the address through.
func EmailStatus(ctx context.Context, email string) (string, error) {
addr, err := mail.ParseAddress(email)
if err != nil || addr.Address != email {
return "invalid", nil // fails the local syntax check, no API call made
}
req, err := http.NewRequestWithContext(ctx, http.MethodGet, apiURL+"?email="+url.QueryEscape(email), nil)
if err != nil {
return "", err
}
req.Header.Set("Authorization", "Bearer "+os.Getenv("SIMPLEVERIFIER_KEY"))
res, err := client.Do(req)
if err != nil {
return "", err // timeout or network error
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return "", fmt.Errorf("simpleverifier: HTTP %d", res.StatusCode) // 401, 402, 429, 503
}
var result Result
if err := json.NewDecoder(res.Body).Decode(&result); err != nil {
return "", err
}
return result.Status, nil
}2. Use it in a handler
The method pattern in HandleFunc needs Go 1.22 or later.
package main
import (
"log"
"net/http"
"strings"
"example.com/app/simpleverifier"
)
func signupHandler(w http.ResponseWriter, r *http.Request) {
email := strings.TrimSpace(r.FormValue("email"))
status, err := simpleverifier.EmailStatus(r.Context(), email)
if err != nil {
log.Printf("simpleverifier unavailable, accepting without a mailbox check: %v", err)
}
if status == "invalid" {
http.Error(w, "This email address cannot receive mail. Please check it.", http.StatusUnprocessableEntity)
return
}
// Save the signup here. status is "valid", "risky" or "" (API unavailable).
w.WriteHeader(http.StatusCreated)
}
func main() {
http.HandleFunc("POST /signup", signupHandler)
log.Fatal(http.ListenAndServe(":8080", nil))
}What the API returns
GET or POST https://simpleverifier.com/api/v1/verify with email runs the syntax, domain, disposable-domain and live mailbox checks in one call and returns:
{
"email": "[email protected]",
"status": "valid",
"reason": "Mailbox exists",
"mailbox_checked": true
}Every snippet above follows the same rule: only a successful response with status set to invalid blocks a person.
| Result | What it means | What to do |
|---|---|---|
| valid | The mail server confirmed the mailbox exists. | Accept. |
| risky | The domain is real but the mailbox couldn't be confirmed: catch-all domains, role addresses like info@, providers that hide mailbox status. | Accept on signup forms. Store the status and keep these out of cold campaigns. |
| invalid | Bad format, no mail server, a disposable domain, or the mailbox doesn't exist. | Reject and ask the person to check the address. |
| timeout, 429 or 503 | The check didn't finish: a slow mail server, too many calls this minute, or a brief outage. | Accept (fail open) and log it. Re-check later if it matters. |
| 401 or 402 | The key is wrong or revoked, or the subscription isn't active. | Accept, and alert yourself: every check is being skipped until it's fixed. |
Errors return JSON with an error message. Each key can make 120 calls a minute; for thousands of addresses, upload a CSV in the dashboard instead.
Phone numbers and spreadsheets
The same key works with /api/v1/phone?phone=…&country=… to validate a phone number and normalise it to E.164, with live carrier lookups when your plan has the $20/month phone add-on. The request and response are in the API reference. To check a column of addresses without code, use the Google Sheets add-on with the same key.
Common questions
- Why check addr.Address != email after net/mail.ParseAddress?
- ParseAddress also accepts display-name forms such as Jane <[email protected]>. Comparing the parsed address to the input rejects those, so only a bare address reaches the API.
- Can I verify several addresses concurrently?
- Yes, but keep the total under 120 calls a minute per key, for example with a time.Ticker or golang.org/x/time/rate. For large lists, upload a CSV in the dashboard instead.
- How much does the SimpleVerifier API cost?
- API access comes with Pro at $39/month (10,000 calls a month included, then $1.50 per 1,000), Business at $99/month (100,000 calls, then $1.00 per 1,000) and Agency at $249/month (300,000 calls, then $1.00 per 1,000). The per-1,000 price applies on monthly billing; on yearly billing the included calls are a monthly limit and further calls get HTTP 402 until the 1st (UTC). Starter has no API access. Each key can make 120 calls a minute.
- Should I reject risky addresses?
- Not on a signup or contact form. Risky means the mailbox couldn't be confirmed, which includes every address at a catch-all company domain. Accept it, store the status, and leave risky addresses out of cold outreach.
- What if the API is slow or down?
- Let the address through. A mailbox check can take several seconds when a mail server is slow, so cap the wait at about 30 seconds (less on a busy form) and treat a timeout, a 429 or a 503 as unknown rather than invalid.