VerifyAnyEmail Ruby SDK
Official Ruby client for the VerifyAnyEmail email verification API — real-time single and bulk verification, catch-all / disposable / role detection, and signed batch webhooks.
- Zero runtime dependencies — built on the Ruby standard library
(
net/http,json,openssl,uri). No Faraday, no ActiveSupport. - Ruby 2.7+
- HTTPS with Bearer authentication and a real request timeout.
Installation
Install the gem directly:
gem install verifyanyemail
Or add it to your Gemfile:
gem "verifyanyemail"
then:
bundle install
Quick start
require "verifyanyemail"
client = VerifyAnyEmail::Client.new(ENV["VAE_API_KEY"])
result = client.verify("name@example.com")
result.status # => "deliverable"
result.sub_status # => "valid_mailbox"
result.score # => 98
result.safe_to_send? # => true
result.deliverable? # => true
result.suggestion # => nil (or a "did you mean" domain)
result.to_h # => the raw result Hash
Configuration
client = VerifyAnyEmail::Client.new(
ENV["VAE_API_KEY"],
base_url: "https://api.verifyany.email", # default
timeout: 30 # seconds, default
)
Verify options
# DNS-only (skip the live SMTP probe):
client.verify("name@example.com", smtp_probe: false)
# Skip catch-all detection:
client.verify("name@example.com", catch_all_detection: false)
The returned VerifyAnyEmail::VerificationResult exposes:
| Reader | Description | |||
|---|---|---|---|---|
email |
Address as submitted | |||
normalized |
Normalized address | |||
account / domain |
Local part / domain | |||
status |
deliverable \ |
undeliverable \ |
risky \ |
unknown |
sub_status |
Finer reason (e.g. valid_mailbox, catch_all) |
|||
score |
Confidence 0–100 | |||
suggestion |
"Did you mean" domain correction | |||
safe_to_send |
Whether sending is recommended | |||
checks |
Hash of individual checks | |||
duration_ms |
Verification time in ms | |||
checked_at |
ISO-8601 timestamp | |||
to_h |
The raw result Hash |
Boolean helpers: deliverable?, undeliverable?, risky?, unknown?,
safe_to_send?, plus per-check helpers syntax?, has_mx_records?,
null_mx?, smtp_connectable?, mailbox_exists?, catch_all?,
role_based?, disposable?, free_provider?, possible_typo?, gibberish?.
Batch verification
Submit a list for asynchronous processing, then poll for status/results (or
receive a signed batch.completed webhook).
# 1. Submit
resp = client.verify_batch(
["a@example.com", "b@example.com"],
name: "newsletter-2026-07",
webhook_url: "https://yourapp.com/webhooks/vae" # optional
)
batch_id = resp.dig("batch", "id")
# 2. Poll status
status = client.get_batch(batch_id)
status.dig("batch", "status") # => "queued" | "processing" | "completed" | "failed"
status.dig("batch", "processed") # => 1
status.dig("batch", "summary") # => { "deliverable" => .., "undeliverable" => .., ... }
# 3. Fetch results (paginated)
page = client.get_batch_results(batch_id, page: 1, page_size: 100)
page["total"] # => 2
page["results"] # => [ { "email" => ..., "status" => ..., "score" => .. }, ... ]
Webhooks
When a batch finishes, VerifyAnyEmail POSTs a batch.completed event to your
webhookUrl with an X-VAE-Signature: sha256=<hmac> header. Verify it against
your webhook secret (dashboard → Settings) using the class method — it performs
a length-safe, constant-time comparison and tolerates the sha256= prefix.
raw_body = request.body.read # the EXACT raw bytes — do not re-serialize
signature = request.env["HTTP_X_VAE_SIGNATURE"]
secret = ENV["VAE_WEBHOOK_SECRET"]
if VerifyAnyEmail::Client.verify_webhook_signature(raw_body, signature, secret)
event = JSON.parse(raw_body)
# event["event"] => "batch.completed"
# event["data"] => { "batchId" => ..., "total" => .., "deliverable" => .., ... }
# ... enqueue processing, then return 2xx ...
else
# reject: return 400/401
end
Rails controller snippet
class WebhooksController < ApplicationController
# Signature is computed over the raw body, so skip CSRF and read it verbatim.
skip_before_action :verify_authenticity_token
def vae
raw = request.raw_post
ok = VerifyAnyEmail::Client.verify_webhook_signature(
raw,
request.headers["X-VAE-Signature"],
Rails.application.credentials.dig(:vae, :webhook_secret)
)
return head(:unauthorized) unless ok
event = JSON.parse(raw)
ProcessBatchCompletedJob.perform_later(event["data"]) if event["event"] == "batch.completed"
head :ok
end
end
Test mode (0 credits)
Any address at @test.verifyany.email returns a deterministic result without
charging a credit or probing a real mailbox — handy for CI and local dev:
client.verify("deliverable@test.verifyany.email").status # => "deliverable"
client.verify("undeliverable@test.verifyany.email").status # => "undeliverable"
client.verify("risky@test.verifyany.email").status # => "risky"
client.verify("unknown@test.verifyany.email").status # => "unknown"
Error handling
Every non-2xx response raises VerifyAnyEmail::Error, which carries the HTTP
status, the machine-readable code (error field from the JSON body), and a
human-readable message.
begin
client.verify("name@example.com")
rescue VerifyAnyEmail::Error => e
e.status # => 402
e.code # => "insufficient_credits"
e. # => "You have run out of credits."
case e.status
when 401 then # missing/invalid API key (also e.unauthorized?)
when 402 then # insufficient credits (also e.payment_required?)
when 413 then # batch too large for plan
when 429 then # rate limited (also e.rate_limited?)
end
end
Network/transport failures also raise VerifyAnyEmail::Error with
code == "network_error".
Keep your API key server-side
Your API key is a secret. Use this SDK only from server-side code (Rails controllers, background jobs, scripts). Never embed it in a browser, mobile app, or any client shipped to end users — anyone who obtains the key can spend your credits. Load it from an environment variable or your secrets manager.
License
MIT © 2026 VerifyAnyEmail