Rails · Ruby

Block disposable email in Rails

Custom ActiveModel validator on User#email. Works with Devise, Sorcery, or hand-rolled signup controllers.

The code

# app/validators/not_disposable_email_validator.rb
class NotDisposableEmailValidator < ActiveModel::EachValidator
  PIISEND_URL = "https://api.piisend.com/api/v1/intelligence/email"

  def validate_each(record, attribute, value)
    return if value.blank?

    begin
      response = HTTParty.post(
        PIISEND_URL,
        headers: {
          "Authorization" => "Bearer #{ENV['PIISEND_API_KEY']}",
          "Content-Type" => "application/json",
        },
        body: { email: value }.to_json,
        timeout: 5,
      )
      if response.success? && response.parsed_response.dig("signals", "disposable")
        record.errors.add(attribute, "Please use a permanent email address.")
      end
    rescue StandardError
      # fail open
    end
  end
end

# app/models/user.rb
class User < ApplicationRecord
  validates :email, not_disposable_email: true
end

API key

Set `PIISEND_API_KEY` in credentials or ENV — use `dotenv-rails` locally.

Fail-open vs fail-closed

Rescue block skips validation on errors.

Pair with verification

Add the validator before Devise's `:validatable` or inside a custom RegistrationsController.

Send verification email with Piisend

After blocking disposable addresses, send OTP and verification mail through the same Piisend account. One API key for intelligence checks and transactional sends.