Module: Philiprehberger::EmailValidator

Defined in:
lib/philiprehberger/email_validator.rb,
lib/philiprehberger/email_validator/result.rb,
lib/philiprehberger/email_validator/syntax.rb,
lib/philiprehberger/email_validator/version.rb,
lib/philiprehberger/email_validator/mx_check.rb,
lib/philiprehberger/email_validator/disposable.rb,
lib/philiprehberger/email_validator/normalizer.rb,
lib/philiprehberger/email_validator/configuration.rb,
lib/philiprehberger/email_validator/typo_suggester.rb,
lib/philiprehberger/email_validator/domain_info_extractor.rb

Defined Under Namespace

Modules: Disposable, DomainInfoExtractor, MxCheck, Normalizer, Syntax, TypoSuggester Classes: Configuration, Error, Result

Constant Summary collapse

ROLE_BASED_LOCALS =

Role-based local parts that typically represent groups, not individuals.

Set.new(%w[
  abuse admin billing contact dev devnull ftp help hostmaster
  info mail mailer-daemon marketing noc noreply no-reply
  office postmaster press registrar remove root sales security
  spam subscribe support sysadmin tech undisclosed-recipients
  unsubscribe usenet uucp webmaster www
]).freeze
VERSION =
'0.5.0'

Class Method Summary collapse

Class Method Details

.batch_validate(emails, **opts) ⇒ Hash{String => Result}

Validate multiple emails and return a hash mapping each email to its Result.

Parameters:

  • emails (Array<String>)

    the email addresses to validate

  • opts (Hash)

    options passed to validate (check_mx:, allow_disposable:)

Returns:

  • (Hash{String => Result})

    hash of email => Result pairs



78
79
80
81
82
# File 'lib/philiprehberger/email_validator.rb', line 78

def batch_validate(emails, **opts)
  emails.to_h do |email|
    [email, validate(email, **opts)]
  end
end

.canonical_equal?(a, b) ⇒ Boolean

Compare two email addresses after normalization.

Returns false rather than raising if either input is invalid.

Parameters:

  • a (String)

    first email address

  • b (String)

    second email address

Returns:

  • (Boolean)

    true if both normalize to the same address



159
160
161
162
163
# File 'lib/philiprehberger/email_validator.rb', line 159

def canonical_equal?(a, b)
  Normalizer.normalize(a) == Normalizer.normalize(b)
rescue Error, StandardError
  false
end

.configurationConfiguration

The current configuration instance.

Returns:



139
140
141
# File 'lib/philiprehberger/email_validator.rb', line 139

def configuration
  @configuration ||= Configuration.new
end

.configure {|Configuration| ... } ⇒ void

This method returns an undefined value.

Configure the email validator.

Yields:



125
126
127
# File 'lib/philiprehberger/email_validator.rb', line 125

def configure
  yield configuration
end

.disposable?(email) ⇒ Boolean

Check if an email address uses a known disposable domain.

Parameters:

  • email (String)

    the email address to check

Returns:

  • (Boolean)

    true if the domain is disposable



104
105
106
# File 'lib/philiprehberger/email_validator.rb', line 104

def disposable?(email)
  disposable_domain?(email)
end

.domain_info(email, check_mx: false) ⇒ Hash

Extract domain information from an email address.

Parameters:

  • email (String)

    the email address to analyze

  • check_mx (Boolean) (defaults to: false)

    whether to look up MX records (default: false)

Returns:

  • (Hash)

    { domain:, tld:, mx_records: }

Raises:

  • (Error)

    if format is invalid



225
226
227
# File 'lib/philiprehberger/email_validator.rb', line 225

def domain_info(email, check_mx: false)
  DomainInfoExtractor.extract(email, check_mx: check_mx)
end

.extract_tag(email) ⇒ String?

Extract the sub-address tag (the portion after the first ‘+` in the local part) from an email address.

Only the first ‘+` separates the user from the tag, so `’user+a+b@gmail.com’‘ yields the tag `’a+b’‘.

Parameters:

  • email (String)

    the email address to inspect

Returns:

  • (String, nil)

    the tag portion, or nil if no tag is present or the input is not a valid email



174
175
176
177
178
179
180
181
182
183
184
185
# File 'lib/philiprehberger/email_validator.rb', line 174

def extract_tag(email)
  return nil unless email.is_a?(String)

  local = extract_local(email)
  domain = extract_domain(email)
  return nil if local.nil? || domain.nil? || local.empty? || domain.empty?

  parts = local.split('+', 2)
  return nil if parts.length != 2

  parts[1]
end

.mx_valid?(domain) ⇒ Boolean

Check if a domain has valid MX records.

Parameters:

  • domain (String)

    the domain to check

Returns:

  • (Boolean)

    true if MX or A records exist



96
97
98
# File 'lib/philiprehberger/email_validator.rb', line 96

def mx_valid?(domain)
  MxCheck.valid?(domain)
end

.normalize(email) ⇒ String

Normalize an email address.

Parameters:

  • email (String)

    the email address to normalize

Returns:

  • (String)

    the normalized email address

Raises:

  • (Error)

    if format is invalid



148
149
150
# File 'lib/philiprehberger/email_validator.rb', line 148

def normalize(email)
  Normalizer.normalize(email)
end

.reset_configuration!void

This method returns an undefined value.

Reset configuration to defaults.



132
133
134
# File 'lib/philiprehberger/email_validator.rb', line 132

def reset_configuration!
  @configuration = Configuration.new
end

.role_based?(email) ⇒ Boolean

Detect role-based email addresses (info@, admin@, support@, etc.).

Parameters:

  • email (String)

    the email address to check

Returns:

  • (Boolean)

    true if the local part is role-based



112
113
114
115
116
117
118
119
# File 'lib/philiprehberger/email_validator.rb', line 112

def role_based?(email)
  return false unless email.is_a?(String)

  local = extract_local(email)
  return false if local.nil?

  ROLE_BASED_LOCALS.include?(local.downcase)
end

.strip_tag(email) ⇒ String

Return the email with any sub-address tag (‘+tag`) removed from the local part. Domain case is preserved.

Only the first ‘+` separates the user from the tag, so `’user+a+b@gmail.com’‘ becomes `’user@gmail.com’‘.

For invalid input (non-string, missing ‘@`, empty local or domain), the original value is returned unchanged — matching the defensive behavior of `canonical_equal?`.

Parameters:

  • email (String)

    the email address to strip

Returns:

  • (String)

    the email without a ‘+tag`, or the original value if the input is invalid



200
201
202
203
204
205
206
207
208
209
# File 'lib/philiprehberger/email_validator.rb', line 200

def strip_tag(email)
  return email unless email.is_a?(String)

  local = extract_local(email)
  domain = extract_domain(email)
  return email if local.nil? || domain.nil? || local.empty? || domain.empty?

  stripped_local = local.split('+', 2).first
  "#{stripped_local}@#{domain}"
end

.suggest(email) ⇒ Hash?

Suggest a corrected email if the domain appears to be a typo.

Parameters:

  • email (String)

    the email address to check

Returns:

  • (Hash, nil)

    { original:, suggested: } or nil if no suggestion



215
216
217
# File 'lib/philiprehberger/email_validator.rb', line 215

def suggest(email)
  TypoSuggester.suggest(email)
end

.valid?(email) ⇒ Boolean

Quick syntax check for an email address.

Parameters:

  • email (String)

    the email address to validate

Returns:

  • (Boolean)

    true if syntax is valid



33
34
35
# File 'lib/philiprehberger/email_validator.rb', line 33

def valid?(email)
  Syntax.valid?(email)
end

.valid_all?(emails) ⇒ Boolean

Check if all emails in an array are valid.

Parameters:

  • emails (Array<String>)

    the email addresses to check

Returns:

  • (Boolean)

    true only if all emails are valid



88
89
90
# File 'lib/philiprehberger/email_validator.rb', line 88

def valid_all?(emails)
  emails.all? { |email| valid?(email) }
end

.validate(email, check_mx: false, allow_disposable: true) ⇒ Result

Full validation returning a Result object.

Parameters:

  • email (String)

    the email address to validate

  • check_mx (Boolean) (defaults to: false)

    whether to verify MX records (default: false)

  • allow_disposable (Boolean) (defaults to: true)

    whether to allow disposable domains (default: true)

Returns:

  • (Result)

    validation result with errors and warnings



43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/philiprehberger/email_validator.rb', line 43

def validate(email, check_mx: false, allow_disposable: true)
  errors = []
  warnings = []

  syntax_errors = Syntax.validate(email)
  errors.concat(syntax_errors)

  if syntax_errors.empty?
    errors << 'disposable email domains are not allowed' if !allow_disposable && disposable_domain?(email)

    warnings << 'address appears to be role-based' if role_based?(email)

    if check_mx
      domain = extract_domain(email)
      errors << "domain '#{domain}' has no MX or A records" unless MxCheck.valid?(domain)
    end
  end

  Result.new(errors: errors, warnings: warnings)
end

.validate_all(emails, **opts) ⇒ Array<Result>

Validate an array of email addresses.

Parameters:

  • emails (Array<String>)

    the email addresses to validate

  • opts (Hash)

    options passed to validate (check_mx:, allow_disposable:)

Returns:

  • (Array<Result>)

    array of Result objects



69
70
71
# File 'lib/philiprehberger/email_validator.rb', line 69

def validate_all(emails, **opts)
  emails.map { |email| validate(email, **opts) }
end