Fork me on GitHub
Ruby Gem

clean-email

Real email validation powered by your choice of provider — Reoon, ZeroBounce, or AbstractAPI — with smart domain-level caching to keep your costs low and your user base clean.

Uses HTTP APIs backed by continuously updated datasets, making it faster and more reliable than SMTP-based validators — and the only approach that can detect disposable and spamtrap addresses.

98%
test coverage
3
providers supported
MIT
license

Installation

Add the gem to your Rails app and run the generator.

Gemfile

gem "clean-email"

Terminal

bundle install
rails generate clean_email:install
rails db:migrate

config/initializers/clean_email.rb

CleanEmail.configure do |config|
  # Choose your provider: :reoon, :zerobounce, or :abstractapi
  config.provider        = :reoon
  config.reoon_api_key   = ENV["REOON_API_KEY"]

  # Enable caching (recommended)
  config.adapter         = CleanEmail::Adapters::ActiveRecord.new

  # Statuses to block (default shown)
  config.blocked_statuses = %w[invalid disposable spamtrap]
end

Providers

Switch providers with a single config change. All return the same normalized statuses.

Reoon default

Quick or power mode. Includes SMTP verification in power mode.

reoon.com →
ZeroBounce

Rich sub-statuses including role-based, catch-all, abuse, and disposable detection.

zerobounce.net →
AbstractAPI

Deliverability scoring with explicit disposable and free email flags.

abstractapi.com →

ZeroBounce

CleanEmail.configure do |config|
  config.provider           = :zerobounce
  config.zerobounce_api_key  = ENV["ZEROBOUNCE_API_KEY"]
  config.adapter             = CleanEmail::Adapters::ActiveRecord.new
end

AbstractAPI

CleanEmail.configure do |config|
  config.provider        = :abstractapi
  config.abstractapi_key  = ENV["ABSTRACTAPI_KEY"]
  config.adapter          = CleanEmail::Adapters::ActiveRecord.new
end

How it works

Every validation follows a smart priority order to save API calls.

Format check

Malformed emails are rejected immediately — no cache lookup, no API call.

Domain cache

Once mailinator.com is marked disposable, every future address from that domain is blocked instantly.

Email cache

Previously validated addresses are returned from cache without touching the API.

Provider API

Only called when no cache is available. Result is stored for future lookups.

Devise integration

Opt in with a single line in your User model. The API is only called on new records or when the email changes.

class User < ApplicationRecord
  devise :database_authenticatable, :registerable, ...

  validates :email, clean_email: true
end

Override statuses

clean_email: { blocked_statuses: %w[invalid] }

Custom message

clean_email: { message: "not accepted" }

Global default

config.blocked_statuses = %w[invalid]

Email statuses

All providers return a normalized status on the Result object — your code stays the same regardless of which provider you use.

Status Meaning valid?
valid Email exists and accepts messages
invalid Invalid or non-existent domain
disposable Temporary / throwaway service
spamtrap Anti-spam trap address
unknown Provider could not determine