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.