Key Takeaways

  • Rubys built-in Net::HTTP library provides everything needed to call the EmailVerifierAPI v2 endpoint without adding third-party HTTP dependencies to your Gemfile.
  • The service object pattern (Plain Old Ruby Object in app/services/) is the most idiomatic Rails approach for encapsulating API integrations, keeping controllers thin and logic reusable.
  • The API response includes status codes (passed, failed, unknown, transient) and boolean flags for disposable, role-based, free service, gibberish, and offensive addresses.
  • For production use, add timeout handling, graceful degradation when the API is unreachable, and Rails credential management for the API key.

Ruby on Rails developers building applications that collect email addresses need server-side validation that goes beyond ActiveRecord format checks and regex patterns. The built-in validates_format_of helper confirms that an email looks syntactically correct, but it cannot confirm whether the mailbox actually exists, whether the address belongs to a disposable email service, or whether it will accept delivery.

This guide walks through building a reusable service object that verifies email addresses against the EmailVerifierAPI v2 endpoint, integrating it with Rails controllers and ActiveRecord validations, and handling edge cases for production use.

Quick Start: curl Verification

100 free email verification credits to test the API before writing any Ruby code.

curl "https://emailverifierapi.com/v2/verify?key=YOUR_API_KEY&email=test@example.com"

Building the Service Object

Create app/services/email_verifier.rb with the following implementation:

require "net/http"
require "json"
require "uri"

class EmailVerifier
  BASE_URL = "https://emailverifierapi.com/v2/verify"

  def initialize(api_key: Rails.application.credentials.dig(:eva, :api_key))
    @api_key = api_key
  end

  def verify(email)
    uri = URI(BASE_URL)
    uri.query = URI.encode_www_form(key: @api_key, email: email)
    response = Net::HTTP.get_response(uri)

    if response.is_a?(Net::HTTPSuccess)
      JSON.parse(response.body, symbolize_names: true)
    else
      { status: "error", message: "HTTP #{response.code}" }
    end
  rescue Net::OpenTimeout, Net::ReadTimeout => e
    { status: "error", message: "Timeout: #{e.message}" }
  rescue StandardError => e
    { status: "error", message: e.message }
  end

  def valid?(email)
    verify(email)[:status] == "passed"
  end

  def disposable?(email)
    verify(email)[:isDisposable] == true
  end
end

The service follows three production patterns. First, the API key is loaded from Rails encrypted credentials rather than hardcoded or stored in environment variables. Second, timeouts are explicitly rescued to prevent hanging requests. Third, all exceptions are caught and returned as structured error responses so the calling code can handle failures gracefully.

Controller Integration

class RegistrationsController < ApplicationController
  def create
    verifier = EmailVerifier.new
    result = verifier.verify(params[:email])

    if result[:status] == "failed"
      render json: { error: "Email is not deliverable" }, status: 400
      return
    end

    if result[:isDisposable]
      render json: { error: "Please use a permanent email" }, status: 400
      return
    end

    @user = User.create!(user_params)
    render json: @user, status: 201
  end
end
Pro Tip For the full list of response fields and integration patterns, see the Ruby email verification integration page. The sub_status field provides granular detail like mailboxDoesNotExist, isCatchall, and isGreylisting for building nuanced acceptance policies.

ActiveRecord Custom Validator

For models that should always have verified email addresses, create a custom validator that calls the service object during the validation phase:

# app/validators/email_deliverability_validator.rb
class EmailDeliverabilityValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    result = EmailVerifier.new.verify(value)
    unless result[:status] == "passed"
      record.errors.add(attribute, "is not a deliverable email address")
    end
    if result[:isDisposable]
      record.errors.add(attribute, "cannot be a disposable email address")
    end
  end
end

# app/models/user.rb
class User < ApplicationRecord
  validates :email, presence: true,
    email_deliverability: true
end
16 verification checks run on every API call, from syntax validation to SMTP probing to disposable detection. Source: EmailVerifierAPI v2 verification pipeline

Understanding the Response

The status field returns: passed (deliverable), failed (undeliverable), unknown (inconclusive), or transient (temporary error, retry). Boolean flags include isDisposable, isRoleAccount, isFreeService, isGibberish, and isOffensive. The smtp_check field shows success or error, and sub_status gives granular detail such as mailboxExists, mailboxDoesNotExist, domainDoesNotExist, isCatchall, or isGreylisting.

Production Considerations

Credential management: Store the API key using rails credentials:edit under eva.api_key. Never commit API keys to source control or store them in plaintext configuration files.

Caching: For forms where users may resubmit the same email, cache verification results in Rails.cache with a 5-minute TTL to avoid duplicate API calls. Use the email as the cache key.

Background verification: For batch processing, use Active Job with Sidekiq or Delayed Job to verify addresses asynchronously. Add a sleep(0.15) between calls to respect rate limits. See the email verification integrations hub for additional language examples.

Batch Processing for List Cleaning

For cleaning existing user databases or processing imported lists, build a Rake task or background job that iterates through addresses with rate limiting.

Read addresses from your database, verify each one sequentially with a 150ms delay between calls, and write results back with all verification flags. A single-threaded processor running at 6 requests per second verifies approximately 21,600 addresses per hour, sufficient for most Rails applications.

For larger datasets, use Sidekiq or GoodJob with a dedicated queue for verification jobs. Create one job per batch of 100 addresses, with each job processing sequentially and respecting rate limits. This approach distributes work across available workers while preventing rate limit violations from concurrent requests.

After batch verification, analyze your result distribution. A typical user database shows 75-85% passed, 5-15% failed, 3-8% unknown, and 1-3% disposable. Store these results as columns on your User model (verified_status, verified_at, is_disposable, is_role_account) to enable ongoing segmentation and suppression.

Testing and Development

During development, mock the EmailVerifier service in your test suite using Minitest stubs or RSpec doubles. Create factory methods that return predefined verification results for each status type (passed, failed, unknown, transient) so your tests cover all possible outcomes without making real API calls.

For integration testing in staging environments, use your free API credits to verify against real mail servers. Write request specs that cover the full controller flow: submitting a registration form, calling the verification service, and asserting the correct response for both valid and invalid addresses.

Create a test helper that makes it easy to stub verification results across your entire test suite. This keeps your tests fast and deterministic while ensuring that every code path through your verification logic is exercised.

For performance profiling, measure the impact of adding verification to your signup flow. The API typically responds in 200-400ms, which is acceptable for form submissions but may need optimization for high-traffic registration pages. Consider implementing background verification with a "pending" user state that upgrades to "active" once verification completes asynchronously.

Handling Edge Cases

Catch-all domains: When sub_status returns isCatchall, the domain accepts all incoming email. Accept these addresses but store the flag and monitor bounce rates on first send. If a catch-all address bounces, suppress it immediately.

Free email filtering: For B2B applications, the isFreeService flag identifies consumer email providers (Gmail, Yahoo, Outlook). You might accept these for general signups but require a company domain for enterprise trial accounts. Branch your controller logic based on this flag.

Gibberish detection: The isGibberish flag catches bot-generated usernames like xk29fj4@gmail.com. Combined with isOffensive, these flags help filter out accounts that would never engage with your application, protecting your engagement metrics and brand safety.

Graceful degradation: If the API is temporarily unreachable, accept the email and flag it for background re-verification. Never block a legitimate user signup because of a transient network issue. The service object's rescue clause handles this by returning an error status that your controller can interpret as "accept provisionally."

Frequently Asked Questions

What Ruby version is required?

Ruby 2.7+ is supported. The code uses Net::HTTP and JSON, both part of the standard library with no external dependencies. For Rails applications, Rails 6.0+ is recommended. The service object pattern works with any Rails version.

How do I handle rate limits?

For real-time signup verification, the API responds in under 300ms, well within typical web request budgets. For batch processing, add a 150ms delay between requests using sleep(0.15) in your processing loop. Use a Semaphore or connection pool for concurrent processing with multiple threads.

Can I use this with Sinatra or Hanami instead of Rails?

Yes. The EmailVerifier service object uses only standard Ruby libraries (Net::HTTP, JSON, URI) with no Rails dependencies in the core logic. For non-Rails frameworks, replace Rails.application.credentials with your frameworks configuration mechanism or environment variables.