Validation in the Model vs. Service Layer

In the Ruby on Rails ecosystem, the debate about where to place business logic is eternal. While Active Record validations are excellent for format checks (regex), external API calls should generally be abstracted away from the Model to keep the application fast and testable. Placing a synchronous HTTP request inside a `before_save` callback can lead to poor user experience if the network hangs.

This guide demonstrates how to build a `VerifierService` in Rails that utilizes the EmailVerifierAPI v2 endpoint. This approach ensures your database only stores high-quality contacts while keeping your controllers skinny and your models clean.

The Endpoint

We will be interacting with the standard v2 verification endpoint. Before writing the Ruby code, it is helpful to understand the expected payload and response using `curl`.

# Example Request
curl -X GET "https://www.emailverifierapi.com/v2/verify?apiKey=YOUR_API_KEY&email=support@emailverifierapi.com"

# Example Response
{
  "status": "valid",
  "score": 1.0,
  "disposable": false,
  "smtp_check": "success",
  "sub_status": "mailbox_exists",
  "free_provider": false
}

Step 1: Configuration

First, store your API key in your encrypted credentials file or environment variables. Never hardcode credentials.

# config/initializers/email_verifier.rb
EMAIL_VERIFIER_API_KEY = ENV.fetch("EMAIL_VERIFIER_API_KEY")

Step 2: Building the Service Object

We will use the standard `Net::HTTP` library to avoid adding heavy dependencies like Faraday, though you can use any HTTP client you prefer. Create `app/services/email_verification_service.rb`.

require "net/http"
require "json"

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

  def initialize(email)
    @email = email
  end

  def call
    return failure_response("missing_email") if @email.blank?

    uri = URI(BASE_URL)
    params = { apiKey: EMAIL_VERIFIER_API_KEY, email: @email }
    uri.query = URI.encode_www_form(params)

    begin
      response = Net::HTTP.get_response(uri)
      
      if response.is_a?(Net::HTTPSuccess)
        parse_result(response.body)
      else
        # Handle API errors (rate limits, downtime) gracefully
        failure_response("api_error")
      end
    rescue StandardError => e
      # Log error and fail open or closed depending on business logic
      Rails.logger.error("Email Validation Failed: #{e.message}")
      failure_response("connection_error")
    end
  end

  private

  def parse_result(body)
    data = JSON.parse(body)
    
    # Define your threshold for acceptance
    is_valid = data["status"] == "valid"
    is_risky = data["status"] == "risky"
    
    {
      success: true,
      valid: is_valid,
      risky: is_risky,
      disposable: data["disposable"],
      score: data["score"],
      raw: data
    }
  end

  def failure_response(reason)
    { success: false, error: reason }
  end
end

Step 3: Integration in the Controller

Now, invoke this service in your controller action. This is best done during the signup process.

# app/controllers/users_controller.rb
def create
  # 1. Instantiate the user
  @user = User.new(user_params)

  # 2. Verify Email
  verification = EmailVerificationService.new(@user.email).call

  if verification[:success]
    if verification[:disposable]
      @user.errors.add(:email, "Please use a permanent email address.")
      render :new and return
    elsif !verification[:valid] && !verification[:risky]
      @user.errors.add(:email, "This email address appears to be invalid.")
      render :new and return
    end
  end

  # 3. Save User if validation passes
  if @user.save
    redirect_to dashboard_path, notice: "Welcome!"
  else
    render :new
  end
end

Handling "Risky" Emails

Notice that in the controller logic above, we allow `risky` emails. These are often "Accept-All" domains common in B2B environments. Blocking them might cause you to lose legitimate leads. A smart strategy is to flag them in your database (`user.email_status = "risky"`) and treat them differently in your marketing automation (e.g., lower send frequency).

Conclusion

By abstracting the logic into a Service Object, your Rails application remains modular. Integrating EmailVerifierAPI adds a crucial layer of defense against bot signups and ensures your database integrity remains high from day one.