framework

A work-email validator for Rails

A Rails EachValidator that checks emails with isBusinessEmail using Net::HTTP, adds a model error for personal or disposable emails, and fails open.

isBusinessEmail for Rails

Rails’ custom validators let you write validates :email, work_email: true on your signup model.

1. Credentials

bin/rails credentials:edit
isbusinessemail:
  api_key: ibe_live_…

2. The client

# app/services/is_business_email.rb
require "net/http"
require "json"

module IsBusinessEmail
  API = URI("https://api.isbusinessemail.com/v1/check")
  FAIL_OPEN = { "recommendation" => "allow", "fail_open" => true }.freeze

  def self.check(email, policy: "b2b")
    req = Net::HTTP::Post.new(API)
    req["Authorization"] = "Bearer #{Rails.application.credentials.dig(:isbusinessemail, :api_key)}"
    req["Content-Type"] = "application/json"
    req.body = { email: email, policy: policy }.to_json

    res = Net::HTTP.start(API.host, API.port, use_ssl: true, open_timeout: 2, read_timeout: 3) do |http|
      http.request(req)
    end
    return JSON.parse(res.body) if res.is_a?(Net::HTTPSuccess)

    Rails.logger.warn("isBusinessEmail error: HTTP #{res.code}")
    FAIL_OPEN
  rescue StandardError => e
    Rails.logger.warn("isBusinessEmail unreachable: #{e.class}")
    FAIL_OPEN
  end
end

3. The validator

# app/validators/work_email_validator.rb
class WorkEmailValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    return if value.blank?

    verdict = IsBusinessEmail.check(value, policy: options.fetch(:policy, "b2b"))
    record.email_verdict = verdict if record.respond_to?(:email_verdict=)

    return unless verdict["recommendation"] == "block"

    message = +"Please use your work email."
    message << " Did you mean #{verdict['did_you_mean']}?" if verdict["did_you_mean"]
    record.errors.add(attribute, :work_email_required, message: message)
  end
end

4. Use it

# app/models/user.rb
class User < ApplicationRecord
  attr_accessor :email_verdict

  validates :email, presence: true,
                    format: { with: URI::MailTo::EMAIL_REGEXP },
                    work_email: true, on: :create

  before_create do
    self.email_category = email_verdict&.dig("category")
    self.needs_review = email_verdict&.dig("recommendation") == "review"
  end
end

on: :create means existing users aren’t re-checked every time they save their profile.

Notes

  • Strict mode for some flows: work_email: { policy: "strict" }.
  • Validation runs on every valid? call. If you call it more than once per request, memoize the verdict per email.
  • Test with test addresses, or stub IsBusinessEmail.check with WebMock or RSpec doubles.

Official docs: Rails Guides (Active Record Validations). See also: Signup form guide.