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.
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
Switch providers with a single config change. All return the same normalized statuses.
Rich sub-statuses including role-based, catch-all, abuse, and disposable detection.
zerobounce.net →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
Every validation follows a smart priority order to save API calls.
Malformed emails are rejected immediately — no cache lookup, no API call.
Once mailinator.com is marked disposable, every future address from that domain is blocked instantly.
Previously validated addresses are returned from cache without touching the API.
Only called when no cache is available. Result is stored for future lookups.
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]
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 | ✗ |