Validate emails in Rails models

An ActiveModel validator that fails an email address that cannot receive email, in any Rails model or form object. It is built on the official Ruby client library, checks each address once, and never stops a real person from signing up when the API cannot answer in time.

class User < ApplicationRecord
  validates :email, presence: true, deliverable_email: true
end

Install

The gem is not on RubyGems yet. Download it here and install it from a folder of your app:

  1. Download email-validation-rails-0.1.0.zip and unzip it into your app's vendor/ folder, so that you have vendor/email-validation-rails/.

  2. Add it to your Gemfile and run bundle install (Bundler installs the Ruby client library from RubyGems):

    gem "email-validation-rails", path: "vendor/email-validation-rails"
  3. Create an API key on the API keys page with the permission "Validate addresses", and give it a daily credit cap to limit what a busy form can spend. Set it in your environment as EV_API_KEY, or read it from your credentials in the initializer below; never put it in code. Each new address a visitor sends is a paid check, so also rate-limit the form's action per visitor (the rate limiting built into Rails 7.2 controllers, or Rack::Attack).

It needs ActiveModel 7.1 or newer and Ruby 3.2 or newer. It works in any class that includes ActiveModel::Validations, with or without Rails.

Use it

class User < ApplicationRecord
  validates :email, presence: true, deliverable_email: true

  # fail disposable addresses too, and refuse the form when the check cannot finish
  validates :work_email, deliverable_email: { fail_on: %w[invalid disposable], fail_open: false }, allow_blank: true

  # your own message
  validates :billing_email, deliverable_email: { message: "is not an address we can use" }, allow_blank: true
end

A malformed address is refused without a request.

Settings

All optional, in config/initializers/email_validation.rb:

EmailValidation::Rails.configure do |c|
  # default: ENV["EV_API_KEY"]
  c.api_key = Rails.application.credentials.ev_api_key
  # default: ENV["EV_BASE_URL"], else this
  c.base_url = "https://api.email-validation.org"
  c.fail_on = %w[invalid]
  c.block_disposable = false
  c.fail_open = true
  c.timeout = 5
  c.client_timeout = 8
  # c.cache defaults to Rails.cache
  c.cache_ttl = 3600
end
  • Which statuses fail. Only invalid by default. Set the block-disposable setting to true to fail disposable addresses as well. catch_all and unknown pass unless you add them: see what each status means.
  • When the check cannot finish (it timed out, the API could not be reached, it answered with a 5xx or 429 error, the account has no credits or no key is set), the address passes and a warning goes to Rails.logger with the error code and request id, never the address. With the fail-open setting at false, the field gets the error "could not be checked right now" instead.
  • EU data residency. Set the base URL to https://api-eu.email-validation.org; the same key works on both.

Speed and caching

The validator gives the API 5 seconds for the check (the API takes 5 to 15) and waits 8 seconds for the answer, without retrying: a slow mail server then answers unknown, which passes and is free.

Each result is kept in Rails.cache for an hour, under a SHA-256 hash of the trimmed, lower-cased address, so a form sent again after fixing another field is not checked or charged again. Only the status is kept, never the address, and unknown results are not kept.

Messages

The error types and their English messages are under errors.messages in the gem's locale file: translate or override them in your own locale files, or pass message: to the validator.

en:
  errors:
    messages:
      email_invalid: "cannot receive email. Please check it for typos"
      email_disposable: "must be a permanent address, not a disposable one"
      email_catch_all: "could not be confirmed. Please use another address"
      email_unknown: "could not be confirmed. Please try again or use another address"
      email_unavailable: "could not be checked right now. Please try again in a moment"

The messages never show the API's own error text.

Costs

Each completed check costs one credit. Results with the status unknown are free, and so are malformed addresses and the test addresses: try the validator with accepted@sandbox.email-validation.org (passes) and no_mailbox@sandbox.email-validation.org (fails) at no cost. See credits and billing.

Updated · Maintained by the email-validation.org team