Mailchk
Back to Blog
Guides7 min read

How to Validate Emails in Ruby on Rails with Mailchk

Feb 11, 2026

How to Validate Emails in Ruby on Rails with Mailchk

Rails makes custom validation easy with Active Model validators. This guide shows how to integrate Mailchk email validation using a service object, a custom validator, and controller-level checks.

Step 1: Service Object

# app/services/email_validator.rb
class EmailValidator
  API_URL = "https://api.mailchk.io/v1/check"

  def self.validate(email)
    new(email).validate
  end

  def initialize(email)
    @email = email
  end

  def validate
    cached = Rails.cache.read(cache_key)
    return cached if cached

    result = call_api
    Rails.cache.write(cache_key, result, expires_in: 24.hours)
    result
  end

  private

  def call_api
    uri = URI(API_URL)
    http = Net::HTTP.new(uri.host, uri.port)
    http.use_ssl = true

    request = Net::HTTP::Post.new(uri)
    request["Content-Type"] = "application/json"
    request["X-API-Key"] = Rails.application.credentials.mailchk_api_key
    request.body = { email: @email }.to_json

    response = http.request(request)
    JSON.parse(response.body)
  rescue StandardError
    { "valid" => true, "disposable" => false }
  end

  def cache_key
    "email_validation:#{Digest::MD5.hexdigest(@email)}"
  end
end

Step 2: Custom Active Model Validator

# app/validators/real_email_validator.rb
class RealEmailValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    return if value.blank?
    result = EmailValidator.validate(value)

    unless result["valid"]
      record.errors.add(attribute, "does not appear to be valid")
      return
    end

    if result["disposable"]
      record.errors.add(attribute, "cannot be a disposable address")
    end

    if options[:block_free] && result["free_email"]
      record.errors.add(attribute, "must be a work email address")
    end
  end
end

Step 3: Use in Your Model

# app/models/user.rb
class User < ApplicationRecord
  validates :email,
    presence: true,
    uniqueness: true,
    real_email: true

  # B2B: also block free providers
  # validates :email, real_email: { block_free: true }
end

Controller-Level Validation

class RegistrationsController < ApplicationController
  def create
    result = EmailValidator.validate(params[:email])

    if result["did_you_mean"]
      render json: { suggestion: result["did_you_mean"] }, status: 422
      return
    end

    if result["disposable"]
      render json: { error: "Disposable emails not allowed" }, status: 422
      return
    end

    @user = User.new(user_params)
    if @user.save
      render json: @user, status: :created
    else
      render json: { errors: @user.errors }, status: 422
    end
  end
end

Background Validation with Active Job

# app/jobs/validate_email_job.rb
class ValidateEmailJob < ApplicationJob
  queue_as :default

  def perform(user_id)
    user = User.find(user_id)
    result = EmailValidator.validate(user.email)

    if result["disposable"]
      user.update(email_status: "disposable")
    elsif result["valid"]
      user.update(email_status: "verified")
    else
      user.update(email_status: "invalid")
    end
  end
end

Best Practices

  • Cache with Rails.cache — use Redis in production.
  • Fail open — never block a signup because the API is temporarily unreachable.
  • Use Rails credentials — more secure than plain ENV vars for API keys.
  • Show typo suggestionsdid_you_mean converts better than a hard rejection.

Getting Started

Sign up for a free Mailchk account. 200 validations/month free, sub-50ms response time.

Ready to validate emails?

Start with 200 free validations. No credit card required.