CheckDisposable Emailcheckdisposable.email
← All guidesRuby on Rails guide · Ruby

Block disposable email signups in Rails

Add an ActiveRecord validation on User. The validation runs before save, so disposable emails never become rows in your `users` table. Works with Devise or vanilla Rails auth.

The code

# config/initializers/disposable_email.rb
require "net/http"
require "json"

module DisposableEmail
  URL = "https://api.checkdisposable.email/v1/check"
  KEY = ENV.fetch("CDE_KEY")

  def self.disposable?(email)
    uri = URI("#{URL}?email=#{URI.encode_www_form_component(email)}")
    req = Net::HTTP::Get.new(uri)
    req["Authorization"] = "Bearer #{KEY}"
    res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true,
                         open_timeout: 2, read_timeout: 3) do |http|
      http.request(req)
    end
    return false unless res.is_a?(Net::HTTPSuccess)
    JSON.parse(res.body).fetch("is_disposable", false) == true
  rescue StandardError
    false # fail open
  end
end

# app/models/user.rb
class User < ApplicationRecord
  validate :no_disposable_email

  private

  def no_disposable_email
    return unless email_changed? && email.present?
    if DisposableEmail.disposable?(email)
      errors.add(:email, "is not allowed — please use a real address.")
    end
  end
end

Notes

Why on save, not the controller
Putting the check in the model means it runs from any code path that creates a User — controller actions, console, rake tasks, OAuth callbacks. One check, full coverage.
Devise compatibility
Devise uses the same User model, so the validation runs during registration without any further wiring.
Background jobs
For high-volume signup flows, move the check into an ActiveJob and use a soft "pending verification" state. For most apps the inline check is fine — 50ms is well below user-perceived latency.

Get a free API key

500 checks/month, no credit card. No credit card. 30 seconds.

Sign up free →