truelist-rails
Email validation for Rails, powered by Truelist.io.
Validate email deliverability in your Rails models with a single line:
validates :email, deliverable: true
Truelist checks whether an email address actually exists and can receive mail, catching typos, disposable addresses, and invalid mailboxes before they hit your database.
Start free — 100 validations + 10 enhanced credits, no credit card required. Get your API key →
Installation
Add to your Gemfile:
gem "truelist-rails"
Then run:
bundle install
Quick Start
1. Configure your API key
Set the TRUELIST_API_KEY environment variable, or run the install generator:
rails generate truelist:install
This creates config/initializers/truelist.rb where you can configure the gem.
2. Add the validator
class User < ApplicationRecord
validates :email, presence: true, deliverable: true
end
That's it. Invalid emails will now fail validation with a clear error message.
Configuration
Truelist.configure do |config|
# Your Truelist API key (required).
# Defaults to ENV["TRUELIST_API_KEY"].
config.api_key = ENV["TRUELIST_API_KEY"]
# API base URL. Change only for testing or proxying.
config.base_url = "https://api.truelist.io"
# Request timeout in seconds.
config.timeout = 10
# When true, raises Truelist::Error on API failures.
# When false (default), returns an "unknown" result on errors,
# allowing the validation to pass gracefully.
config.raise_on_error = false
# Whether "accept_all" emails (domains that accept all addresses) pass validation.
config.allow_risky = true
# Optional cache store for validation results.
# Accepts any Rails-compatible cache store.
config.cache_store = Rails.cache
# How long to cache validation results (in seconds).
config.cache_ttl = 1.hour
end
Validator Options
Basic usage
validates :email, deliverable: true
Reject accept_all emails
validates :email, deliverable: { allow_risky: false }
Custom error message
validates :email, deliverable: { message: "is not a valid email address" }
Combine options
validates :email, deliverable: { allow_risky: false, message: "doesn't look right" }
Use with other validators
validates :email, presence: true,
format: { with: URI::MailTo::EMAIL_REGEXP },
deliverable: true
The deliverable validator skips blank values, so pair it with presence: true if the field is required.
Working with Results Directly
Use the client to validate emails outside of model validations:
result = Truelist.validate("[email protected]")
result.state # => "ok", "email_invalid", "accept_all", or "unknown"
result.sub_state # => "email_ok", "is_disposable", "is_role", etc.
result.valid? # => true when state is "ok" (or "accept_all" with allow_risky)
result.invalid? # => true when state is "email_invalid"
result.accept_all? # => true when state is "accept_all"
result.unknown? # => true when state is "unknown"
result.email # => the validated email address
result.suggestion # => suggested correction, if available
result.domain # => email domain
result.canonical # => local part of the email
result.mx_record # => MX record for the domain
result.first_name # => first name, if available
result.last_name # => last name, if available
result.verified_at # => timestamp of verification
result.disposable? # => whether it's a disposable/temporary address (sub_state)
result.role? # => whether it's a role address (sub_state)
Account Info
client = Truelist::Client.new
account = client.account
account["email"] # => "[email protected]"
account["name"] # => "Team Lead"
account["uuid"] # => "a3828d19-..."
account["account"]["payment_plan"] # => "pro"
Sub-states
| Sub-state | Meaning |
|---|---|
email_ok |
Email is valid and deliverable |
is_disposable |
Disposable/temporary email |
is_role |
Role-based address (info@, admin@) |
failed_smtp_check |
SMTP check failed |
failed_mx_check |
Domain has no mail server |
failed_spam_trap |
Known spam trap address |
failed_no_mailbox |
Mailbox does not exist |
failed_greylisted |
Server temporarily rejected (greylisting) |
failed_syntax_check |
Email format is invalid |
unknown_error |
Could not determine status |
Caching
Enable caching to avoid redundant API calls for recently validated emails:
Truelist.configure do |config|
config.cache_store = Rails.cache
config.cache_ttl = 1.hour
end
The cache key is based on the lowercase, stripped email address. Any Rails-compatible cache store works (Redis, Memcached, file store, etc.).
Error Handling
By default, API errors (timeouts, rate limits, server errors) return an unknown result, allowing validation to pass. This prevents your forms from breaking when the API is unreachable.
To raise exceptions instead:
Truelist.configure do |config|
config.raise_on_error = true
end
Exception classes:
Truelist::Error-- base error classTruelist::ApiError-- unexpected API responsesTruelist::AuthenticationError-- invalid API key (401)Truelist::RateLimitError-- rate limit exceeded (429)
Testing
Stub the API in your tests to avoid real HTTP calls. With WebMock:
# spec/support/truelist.rb
RSpec.configure do |config|
config.before do
stub_request(:post, "https://api.truelist.io/api/v1/verify_inline")
.with(query: hash_including(email: /.+/))
.to_return(
status: 200,
body: {
emails: [{
address: "[email protected]",
email_state: "ok",
email_sub_state: "email_ok"
}]
}.to_json,
headers: { "Content-Type" => "application/json" }
)
end
end
Or stub at the client level:
allow(Truelist::Client).to receive(:new).and_return(
instance_double(Truelist::Client, validate: Truelist::Result.new(email: "[email protected]", state: "ok"))
)
Requirements
- Ruby >= 3.0
- Rails >= 7.0 (ActiveModel / ActiveSupport)
Development
git clone https://github.com/Truelist-io-Email-Validation/truelist-rails.git
cd truelist-rails
bundle install
bundle exec rspec
Getting Started
Sign up for a free Truelist account to get your API key. The free plan includes 100 validations and 10 enhanced credits — no credit card required.
License
Released under the MIT License.