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.message # => "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