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
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": "jane@company.com",
"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 on the Email + Phone plan. 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 <jane@company.com>. 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 is included in the paid plans: Pro at $29.99/month and Email + Phone at $49.99/month. There are no per-call credits. 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.