1. Home
  2. API docs
  3. Rails

Validate email addresses in Rails

To validate an email in Rails beyond its format, write an ActiveModel::EachValidator that calls the SimpleVerifier API and adds an error only when the status is invalid. Use it with validates :email, deliverable_email: true, limited to records where the email changed, after a URI::MailTo::EMAIL_REGEXP format check.

By SimpleVerifier Team

Before you start

  • Create a key on the API keys page. Keys start with sv_live_ and are shown once.
  • Set SIMPLEVERIFIER_KEY in the environment, or store it in credentials and swap ENV.fetch for Rails.application.credentials.simpleverifier_key.
  • API access is included in the Pro plan ($29.99/month) and the Email + Phone plan ($49.99/month). See pricing.

1. Add the API helper

Save the module from the Ruby guide as app/lib/simple_verifier.rb. Zeitwerk maps that file name to the SimpleVerifier constant.

app/lib/simple_verifier.rb
# simple_verifier.rb — Ruby 2.7+, standard library only
require "json"
require "net/http"
require "openssl"
require "uri"

module SimpleVerifier
  API_URL = "https://simpleverifier.com/api/v1/verify"
  EMAIL_SHAPE = /\A[^@\s]+@[^@\s]+\.[^@\s]+\z/

  # Returns "valid", "risky" or "invalid", or nil when the API could not answer.
  def self.email_status(email)
    address = email.to_s.strip
    return "invalid" unless EMAIL_SHAPE.match?(address) # no API call made

    uri = URI(API_URL)
    uri.query = URI.encode_www_form(email: address)
    request = Net::HTTP::Get.new(uri)
    request["Authorization"] = "Bearer #{ENV.fetch("SIMPLEVERIFIER_KEY")}"

    response = Net::HTTP.start(uri.host, uri.port, use_ssl: true, open_timeout: 5, read_timeout: 30) do |http|
      http.request(request)
    end
    return nil unless response.is_a?(Net::HTTPOK) # 401, 402, 429, 503: fail open

    JSON.parse(response.body)["status"]
  rescue Net::OpenTimeout, Net::ReadTimeout, SocketError, SystemCallError,
         OpenSSL::SSL::SSLError, JSON::ParserError => e
    warn "SimpleVerifier unavailable: #{e.class}"
    nil
  end
end

2. Write the validator

Rails finds deliverable_email: true by looking up DeliverableEmailValidator.

app/validators/deliverable_email_validator.rb
# app/validators/deliverable_email_validator.rb
# Uses SimpleVerifier.email_status, saved as app/lib/simple_verifier.rb
class DeliverableEmailValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    return if value.blank?
    return unless SimpleVerifier.email_status(value) == "invalid"

    record.errors.add(attribute, options[:message] || "cannot receive mail. Please check it")
  end
end

3. Use it on a model

will_save_change_to_email? skips the API call when a user updates their name or password, so an address is only checked when it's new or changed.

app/models/user.rb
# app/models/user.rb
class User < ApplicationRecord
  validates :email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP }
  # Only call the API when the address is new or has changed.
  validates :email, deliverable_email: true, if: :will_save_change_to_email?
end

4. Or on a form object

The validator works on any ActiveModel::Model, such as a contact form that isn't saved to a table.

app/models/contact_message.rb
# app/models/contact_message.rb — a form object with no database table
class ContactMessage
  include ActiveModel::Model

  attr_accessor :email, :body

  validates :email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP }
  validates :email, deliverable_email: true
  validates :body, presence: true
end

What the API returns

GET or POST https://simpleverifier.com/api/v1/verify with email runs the syntax, domain, disposable-domain and live mailbox checks in one call and returns:

{
  "email": "jane@company.com",
  "status": "valid",
  "reason": "Mailbox exists",
  "mailbox_checked": true
}

Every snippet above follows the same rule: only a successful response with status set to invalid blocks a person.

ResultWhat it meansWhat to do
validThe mail server confirmed the mailbox exists.Accept.
riskyThe domain is real but the mailbox couldn't be confirmed: catch-all domains, role addresses like info@, providers that hide mailbox status.Accept on signup forms. Store the status and keep these out of cold campaigns.
invalidBad format, no mail server, a disposable domain, or the mailbox doesn't exist.Reject and ask the person to check the address.
timeout, 429 or 503The check didn't finish: a slow mail server, too many calls this minute, or a brief outage.Accept (fail open) and log it. Re-check later if it matters.
401 or 402The key is wrong or revoked, or the subscription isn't active.Accept, and alert yourself: every check is being skipped until it's fixed.

Errors return JSON with an error message. Each key can make 120 calls a minute; for thousands of addresses, upload a CSV in the dashboard instead.

Phone numbers and spreadsheets

The same key works with /api/v1/phone?phone=…&country=… to validate a phone number and normalise it to E.164, with live carrier lookups on the Email + Phone plan. The request and response are in the API reference. To check a column of addresses without code, use the Google Sheets add-on with the same key.

Common questions

Will this slow down my test suite?
Stub the call in tests, for example allow(SimpleVerifier).to receive(:email_status).and_return("valid") in RSpec. Tests shouldn't depend on a network call or use your key.
Can I run the check in a background job instead?
Yes. Drop the validator, save the user, then call SimpleVerifier.email_status from an Active Job and flag the account if it returns invalid. Use the validator when you want people to fix a typo before they leave the form.
How much does the SimpleVerifier API cost?
API access is included in the paid plans: Pro at $29.99/month and Email + Phone at $49.99/month. There are no per-call credits. Each key can make 120 calls a minute.
Should I reject risky addresses?
Not on a signup or contact form. Risky means the mailbox couldn't be confirmed, which includes every address at a catch-all company domain. Accept it, store the status, and leave risky addresses out of cold outreach.
What if the API is slow or down?
Let the address through. A mailbox check can take several seconds when a mail server is slow, so cap the wait at about 30 seconds (less on a busy form) and treat a timeout, a 429 or a 503 as unknown rather than invalid.

Ready to clean your email and phone lists?

Start verifying emails and phone numbers today and boost your deliverability.

Get Started

Cancel anytime • No setup required