philiprehberger-email_validator 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 66d9d94a5c449968eb64ab42f3c2a852b71ad9730f8135d6a7d43a39382c5fdd
4
+ data.tar.gz: 4e949ee122c5a9b8feb40b0ad4c76f069c02d661b3b1dd0603bc1e2b1c7d85c6
5
+ SHA512:
6
+ metadata.gz: a54fc05245f37e0f56e0f1477e856b2bf4beeb63e758f57e5e6b17f3b107df9be24911eca192002e9a42d300b8dff5825b0343e54acb3e170e4cd6b9209031b1
7
+ data.tar.gz: 67b174b8a5b4fe73ce3ef81498ebc8335b80f104b28bd99aed6c27884b277049a097d75220f45385a98a649c7324829f372692e77f6842b0a52ebb04f4224a00
data/CHANGELOG.md ADDED
@@ -0,0 +1,18 @@
1
+ # Changelog
2
+
3
+ All notable changes to this gem will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.1.0] - 2026-03-26
11
+
12
+ ### Added
13
+ - Initial release
14
+ - RFC 5322 email syntax validation with local part and domain rules
15
+ - MX record verification using Ruby stdlib Resolv
16
+ - Disposable email domain detection with built-in list of ~50 providers
17
+ - Role-based address detection (admin@, info@, support@, etc.)
18
+ - Result value object with errors, warnings, and valid? predicate
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 philiprehberger
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,112 @@
1
+ # philiprehberger-email_validator
2
+
3
+ [![Tests](https://github.com/philiprehberger/rb-email-validator/actions/workflows/ci.yml/badge.svg)](https://github.com/philiprehberger/rb-email-validator/actions/workflows/ci.yml)
4
+ [![Gem Version](https://badge.fury.io/rb/philiprehberger-email_validator.svg)](https://rubygems.org/gems/philiprehberger-email_validator)
5
+ [![License](https://img.shields.io/github/license/philiprehberger/rb-email-validator)](LICENSE)
6
+ [![Sponsor](https://img.shields.io/badge/sponsor-GitHub%20Sponsors-ec6cb9)](https://github.com/sponsors/philiprehberger)
7
+
8
+ RFC-compliant email validation with MX record verification
9
+
10
+ ## Requirements
11
+
12
+ - Ruby >= 3.1
13
+
14
+ ## Installation
15
+
16
+ Add to your Gemfile:
17
+
18
+ ```ruby
19
+ gem "philiprehberger-email_validator"
20
+ ```
21
+
22
+ Or install directly:
23
+
24
+ ```bash
25
+ gem install philiprehberger-email_validator
26
+ ```
27
+
28
+ ## Usage
29
+
30
+ ```ruby
31
+ require "philiprehberger/email_validator"
32
+
33
+ Philiprehberger::EmailValidator.valid?("user@example.com")
34
+ # => true
35
+
36
+ Philiprehberger::EmailValidator.valid?("not-an-email")
37
+ # => false
38
+ ```
39
+
40
+ ### Full Validation
41
+
42
+ ```ruby
43
+ result = Philiprehberger::EmailValidator.validate("user@example.com")
44
+ result.valid? # => true
45
+ result.errors # => []
46
+ result.warnings # => []
47
+
48
+ result = Philiprehberger::EmailValidator.validate("admin@example.com")
49
+ result.valid? # => true
50
+ result.warnings # => ["address appears to be role-based"]
51
+ ```
52
+
53
+ ### MX Record Verification
54
+
55
+ ```ruby
56
+ result = Philiprehberger::EmailValidator.validate("user@example.com", check_mx: true)
57
+ result.valid? # => true (if domain has MX/A records)
58
+
59
+ Philiprehberger::EmailValidator.mx_valid?("example.com")
60
+ # => true
61
+ ```
62
+
63
+ ### Disposable Domain Detection
64
+
65
+ ```ruby
66
+ Philiprehberger::EmailValidator.disposable?("user@mailinator.com")
67
+ # => true
68
+
69
+ result = Philiprehberger::EmailValidator.validate("user@mailinator.com", allow_disposable: false)
70
+ result.valid? # => false
71
+ result.errors # => ["disposable email domains are not allowed"]
72
+ ```
73
+
74
+ ### Role-Based Address Detection
75
+
76
+ ```ruby
77
+ Philiprehberger::EmailValidator.role_based?("info@example.com")
78
+ # => true
79
+
80
+ Philiprehberger::EmailValidator.role_based?("alice@example.com")
81
+ # => false
82
+ ```
83
+
84
+ ## API
85
+
86
+ | Method | Description |
87
+ |--------|-------------|
88
+ | `EmailValidator.valid?(email)` | Quick syntax check, returns boolean |
89
+ | `EmailValidator.validate(email, check_mx: false, allow_disposable: true)` | Full validation returning Result |
90
+ | `EmailValidator.mx_valid?(domain)` | Check if domain has MX or A records |
91
+ | `EmailValidator.disposable?(email)` | Check if email uses a disposable domain |
92
+ | `EmailValidator.role_based?(email)` | Detect role-based addresses (info@, admin@, etc.) |
93
+
94
+ ### `Result`
95
+
96
+ | Method | Description |
97
+ |--------|-------------|
98
+ | `#valid?` | True if no validation errors |
99
+ | `#errors` | Array of error message strings |
100
+ | `#warnings` | Array of warning message strings |
101
+
102
+ ## Development
103
+
104
+ ```bash
105
+ bundle install
106
+ bundle exec rspec
107
+ bundle exec rubocop
108
+ ```
109
+
110
+ ## License
111
+
112
+ [MIT](LICENSE)
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Philiprehberger
4
+ module EmailValidator
5
+ # Disposable (throwaway) email domain detection.
6
+ #
7
+ # Maintains a built-in list of commonly used disposable email providers.
8
+ # Useful for preventing sign-ups with temporary email addresses.
9
+ module Disposable
10
+ # Built-in list of common disposable email domains.
11
+ DOMAINS = Set.new(%w[
12
+ mailinator.com
13
+ guerrillamail.com
14
+ guerrillamail.de
15
+ guerrillamail.net
16
+ guerrillamail.org
17
+ tempmail.com
18
+ temp-mail.org
19
+ throwaway.email
20
+ sharklasers.com
21
+ guerrillamailblock.com
22
+ grr.la
23
+ dispostable.com
24
+ yopmail.com
25
+ yopmail.fr
26
+ trashmail.com
27
+ trashmail.me
28
+ trashmail.net
29
+ mailnesia.com
30
+ maildrop.cc
31
+ discard.email
32
+ mailcatch.com
33
+ fakeinbox.com
34
+ mailnull.com
35
+ tempail.com
36
+ tempr.email
37
+ einrot.com
38
+ getnada.com
39
+ jetable.org
40
+ mohmal.com
41
+ burpcollaborator.net
42
+ mailsac.com
43
+ harakirimail.com
44
+ tmail.ws
45
+ guerrillamail.info
46
+ mytemp.email
47
+ tempmailaddress.com
48
+ mailforspam.com
49
+ safetymail.info
50
+ trashymail.com
51
+ mailexpire.com
52
+ tempinbox.com
53
+ spamgourmet.com
54
+ mintemail.com
55
+ mailzilla.com
56
+ anonbox.net
57
+ binkmail.com
58
+ bobmail.info
59
+ chammy.info
60
+ deadaddress.com
61
+ despammed.com
62
+ devnullmail.com
63
+ dontreg.com
64
+ e4ward.com
65
+ emailigo.de
66
+ ]).freeze
67
+
68
+ class << self
69
+ # Check if an email address uses a known disposable domain.
70
+ #
71
+ # @param email [String] the email address to check
72
+ # @return [Boolean] true if the domain is in the disposable list
73
+ def disposable?(email)
74
+ return false unless email.is_a?(String)
75
+
76
+ domain = extract_domain(email)
77
+ return false if domain.nil?
78
+
79
+ DOMAINS.include?(domain.downcase)
80
+ end
81
+
82
+ # Check if a domain is in the disposable list.
83
+ #
84
+ # @param domain [String] the domain to check
85
+ # @return [Boolean]
86
+ def domain_disposable?(domain)
87
+ return false unless domain.is_a?(String)
88
+
89
+ DOMAINS.include?(domain.strip.downcase)
90
+ end
91
+
92
+ private
93
+
94
+ # Extract the domain from an email address.
95
+ #
96
+ # @param email [String]
97
+ # @return [String, nil]
98
+ def extract_domain(email)
99
+ parts = email.strip.split('@', 2)
100
+ return nil if parts.length != 2
101
+
102
+ parts[1]&.downcase
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'resolv'
4
+
5
+ module Philiprehberger
6
+ module EmailValidator
7
+ # MX record verification using Ruby's built-in Resolv library.
8
+ #
9
+ # Checks whether a domain has valid MX records, falling back to
10
+ # A record lookup as permitted by RFC 5321 section 5.1.
11
+ module MxCheck
12
+ # Default DNS timeout in seconds.
13
+ DEFAULT_TIMEOUT = 5
14
+
15
+ class << self
16
+ # Check if a domain has valid MX records.
17
+ #
18
+ # Falls back to checking A records if no MX records are found,
19
+ # as RFC 5321 permits mail delivery to the A record host.
20
+ #
21
+ # @param domain [String] the domain to check
22
+ # @param timeout [Integer] DNS query timeout in seconds
23
+ # @return [Boolean] true if the domain has MX or A records
24
+ def valid?(domain, timeout: DEFAULT_TIMEOUT)
25
+ return false if domain.nil? || domain.strip.empty?
26
+
27
+ resolver = Resolv::DNS.new
28
+ resolver.timeouts = timeout
29
+
30
+ mx_records = fetch_mx_records(resolver, domain)
31
+ return true unless mx_records.empty?
32
+
33
+ a_records = fetch_a_records(resolver, domain)
34
+ !a_records.empty?
35
+ rescue Resolv::ResolvError, Resolv::ResolvTimeout
36
+ false
37
+ ensure
38
+ resolver&.close
39
+ end
40
+
41
+ # Retrieve MX records for a domain.
42
+ #
43
+ # @param domain [String] the domain to look up
44
+ # @param timeout [Integer] DNS query timeout in seconds
45
+ # @return [Array<String>] list of MX hostnames sorted by preference
46
+ def mx_records(domain, timeout: DEFAULT_TIMEOUT)
47
+ return [] if domain.nil? || domain.strip.empty?
48
+
49
+ resolver = Resolv::DNS.new
50
+ resolver.timeouts = timeout
51
+
52
+ records = fetch_mx_records(resolver, domain)
53
+ records.sort_by(&:preference).map { |r| r.exchange.to_s }
54
+ rescue Resolv::ResolvError, Resolv::ResolvTimeout
55
+ []
56
+ ensure
57
+ resolver&.close
58
+ end
59
+
60
+ private
61
+
62
+ # @param resolver [Resolv::DNS]
63
+ # @param domain [String]
64
+ # @return [Array<Resolv::DNS::Resource::IN::MX>]
65
+ def fetch_mx_records(resolver, domain)
66
+ resolver.getresources(domain, Resolv::DNS::Resource::IN::MX)
67
+ rescue Resolv::ResolvError, Resolv::ResolvTimeout
68
+ []
69
+ end
70
+
71
+ # @param resolver [Resolv::DNS]
72
+ # @param domain [String]
73
+ # @return [Array<Resolv::DNS::Resource::IN::A>]
74
+ def fetch_a_records(resolver, domain)
75
+ resolver.getresources(domain, Resolv::DNS::Resource::IN::A)
76
+ rescue Resolv::ResolvError, Resolv::ResolvTimeout
77
+ []
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Philiprehberger
4
+ module EmailValidator
5
+ # Value object representing the outcome of an email validation.
6
+ #
7
+ # @example
8
+ # result = EmailValidator.validate("user@example.com")
9
+ # result.valid? # => true
10
+ # result.errors # => []
11
+ # result.warnings # => []
12
+ class Result
13
+ # @return [Array<String>] list of validation error messages
14
+ attr_reader :errors
15
+
16
+ # @return [Array<String>] list of non-fatal warning messages
17
+ attr_reader :warnings
18
+
19
+ # @param errors [Array<String>] validation error messages
20
+ # @param warnings [Array<String>] non-fatal warning messages
21
+ def initialize(errors: [], warnings: [])
22
+ @errors = errors.freeze
23
+ @warnings = warnings.freeze
24
+ freeze
25
+ end
26
+
27
+ # Whether the email passed all validation checks.
28
+ #
29
+ # @return [Boolean]
30
+ def valid?
31
+ @errors.empty?
32
+ end
33
+
34
+ # String representation for debugging.
35
+ #
36
+ # @return [String]
37
+ def to_s
38
+ if valid?
39
+ '#<EmailValidator::Result valid>'
40
+ else
41
+ "#<EmailValidator::Result invalid errors=#{@errors}>"
42
+ end
43
+ end
44
+
45
+ alias inspect to_s
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,142 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Philiprehberger
4
+ module EmailValidator
5
+ # RFC 5322 compliant email syntax validation.
6
+ #
7
+ # Validates local part rules, domain rules, and length limits
8
+ # according to the relevant RFCs (5321, 5322).
9
+ module Syntax
10
+ # Maximum total length of an email address (RFC 5321).
11
+ MAX_EMAIL_LENGTH = 254
12
+
13
+ # Maximum length of the local part (RFC 5321).
14
+ MAX_LOCAL_LENGTH = 64
15
+
16
+ # Maximum length of the domain part (RFC 5321).
17
+ MAX_DOMAIN_LENGTH = 253
18
+
19
+ # Maximum length of a single domain label.
20
+ MAX_LABEL_LENGTH = 63
21
+
22
+ # Characters allowed in the local part without quoting (RFC 5322 dot-atom).
23
+ LOCAL_CHAR_PATTERN = %r{\A[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+\z}
24
+
25
+ # Pattern for a valid domain label.
26
+ LABEL_PATTERN = /\A[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\z/
27
+
28
+ class << self
29
+ # Validate the syntax of an email address.
30
+ #
31
+ # @param email [String] the email address to validate
32
+ # @return [Array<String>] list of error messages (empty if valid)
33
+ def validate(email)
34
+ errors = []
35
+
36
+ return ['email must be a string'] unless email.is_a?(String)
37
+
38
+ stripped = email.strip
39
+
40
+ return ['email must not be empty'] if stripped.empty?
41
+
42
+ if stripped.length > MAX_EMAIL_LENGTH
43
+ errors << "email exceeds maximum length of #{MAX_EMAIL_LENGTH} characters"
44
+ end
45
+
46
+ parts = stripped.split('@', -1)
47
+
48
+ return ['email must contain an @ symbol'] if parts.length < 2
49
+
50
+ return ['email must contain exactly one @ symbol'] if parts.length > 2
51
+
52
+ local, domain = parts
53
+
54
+ errors.concat(validate_local(local))
55
+ errors.concat(validate_domain(domain))
56
+
57
+ errors
58
+ end
59
+
60
+ # Check if an email address has valid syntax.
61
+ #
62
+ # @param email [String] the email address to check
63
+ # @return [Boolean]
64
+ def valid?(email)
65
+ validate(email).empty?
66
+ end
67
+
68
+ private
69
+
70
+ # Validate the local part of an email address.
71
+ #
72
+ # @param local [String] the local part (before @)
73
+ # @return [Array<String>] list of error messages
74
+ def validate_local(local)
75
+ errors = []
76
+
77
+ if local.empty?
78
+ errors << 'local part must not be empty'
79
+ return errors
80
+ end
81
+
82
+ if local.length > MAX_LOCAL_LENGTH
83
+ errors << "local part exceeds maximum length of #{MAX_LOCAL_LENGTH} characters"
84
+ end
85
+
86
+ errors << 'local part must not start with a dot' if local.start_with?('.')
87
+
88
+ errors << 'local part must not end with a dot' if local.end_with?('.')
89
+
90
+ errors << 'local part must not contain consecutive dots' if local.include?('..')
91
+
92
+ errors << 'local part contains invalid characters' unless local.match?(LOCAL_CHAR_PATTERN)
93
+
94
+ errors
95
+ end
96
+
97
+ # Validate the domain part of an email address.
98
+ #
99
+ # @param domain [String] the domain part (after @)
100
+ # @return [Array<String>] list of error messages
101
+ def validate_domain(domain)
102
+ errors = []
103
+
104
+ if domain.empty?
105
+ errors << 'domain must not be empty'
106
+ return errors
107
+ end
108
+
109
+ if domain.length > MAX_DOMAIN_LENGTH
110
+ errors << "domain exceeds maximum length of #{MAX_DOMAIN_LENGTH} characters"
111
+ end
112
+
113
+ errors << 'domain must not start with a hyphen' if domain.start_with?('-')
114
+
115
+ errors << 'domain must not end with a hyphen' if domain.end_with?('-')
116
+
117
+ labels = domain.split('.')
118
+
119
+ errors << 'domain must contain at least two labels' if labels.length < 2
120
+
121
+ labels.each do |label|
122
+ if label.empty?
123
+ errors << 'domain must not contain empty labels'
124
+ next
125
+ end
126
+
127
+ if label.length > MAX_LABEL_LENGTH
128
+ errors << "domain label '#{label}' exceeds maximum length of #{MAX_LABEL_LENGTH} characters"
129
+ end
130
+
131
+ errors << "domain label '#{label}' contains invalid characters" unless label.match?(LABEL_PATTERN)
132
+ end
133
+
134
+ tld = labels.last
135
+ errors << 'top-level domain must not be all numeric' if tld&.match?(/\A\d+\z/)
136
+
137
+ errors
138
+ end
139
+ end
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Philiprehberger
4
+ module EmailValidator
5
+ VERSION = '0.1.0'
6
+ end
7
+ end
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
+
5
+ require_relative 'email_validator/version'
6
+ require_relative 'email_validator/result'
7
+ require_relative 'email_validator/syntax'
8
+ require_relative 'email_validator/mx_check'
9
+ require_relative 'email_validator/disposable'
10
+
11
+ module Philiprehberger
12
+ module EmailValidator
13
+ class Error < StandardError; end
14
+
15
+ # Role-based local parts that typically represent groups, not individuals.
16
+ ROLE_BASED_LOCALS = Set.new(%w[
17
+ abuse admin billing contact dev devnull ftp help hostmaster
18
+ info mail mailer-daemon marketing noc noreply no-reply
19
+ office postmaster press registrar remove root sales security
20
+ spam subscribe support sysadmin tech undisclosed-recipients
21
+ unsubscribe usenet uucp webmaster www
22
+ ]).freeze
23
+
24
+ class << self
25
+ # Quick syntax check for an email address.
26
+ #
27
+ # @param email [String] the email address to validate
28
+ # @return [Boolean] true if syntax is valid
29
+ def valid?(email)
30
+ Syntax.valid?(email)
31
+ end
32
+
33
+ # Full validation returning a Result object.
34
+ #
35
+ # @param email [String] the email address to validate
36
+ # @param check_mx [Boolean] whether to verify MX records (default: false)
37
+ # @param allow_disposable [Boolean] whether to allow disposable domains (default: true)
38
+ # @return [Result] validation result with errors and warnings
39
+ def validate(email, check_mx: false, allow_disposable: true)
40
+ errors = []
41
+ warnings = []
42
+
43
+ syntax_errors = Syntax.validate(email)
44
+ errors.concat(syntax_errors)
45
+
46
+ if syntax_errors.empty?
47
+ errors << 'disposable email domains are not allowed' if !allow_disposable && Disposable.disposable?(email)
48
+
49
+ warnings << 'address appears to be role-based' if role_based?(email)
50
+
51
+ if check_mx
52
+ domain = extract_domain(email)
53
+ errors << "domain '#{domain}' has no MX or A records" unless MxCheck.valid?(domain)
54
+ end
55
+ end
56
+
57
+ Result.new(errors: errors, warnings: warnings)
58
+ end
59
+
60
+ # Check if a domain has valid MX records.
61
+ #
62
+ # @param domain [String] the domain to check
63
+ # @return [Boolean] true if MX or A records exist
64
+ def mx_valid?(domain)
65
+ MxCheck.valid?(domain)
66
+ end
67
+
68
+ # Check if an email address uses a known disposable domain.
69
+ #
70
+ # @param email [String] the email address to check
71
+ # @return [Boolean] true if the domain is disposable
72
+ def disposable?(email)
73
+ Disposable.disposable?(email)
74
+ end
75
+
76
+ # Detect role-based email addresses (info@, admin@, support@, etc.).
77
+ #
78
+ # @param email [String] the email address to check
79
+ # @return [Boolean] true if the local part is role-based
80
+ def role_based?(email)
81
+ return false unless email.is_a?(String)
82
+
83
+ local = extract_local(email)
84
+ return false if local.nil?
85
+
86
+ ROLE_BASED_LOCALS.include?(local.downcase)
87
+ end
88
+
89
+ private
90
+
91
+ # Extract the domain part from an email address.
92
+ #
93
+ # @param email [String]
94
+ # @return [String, nil]
95
+ def extract_domain(email)
96
+ parts = email.strip.split('@', 2)
97
+ return nil if parts.length != 2
98
+
99
+ parts[1]
100
+ end
101
+
102
+ # Extract the local part from an email address.
103
+ #
104
+ # @param email [String]
105
+ # @return [String, nil]
106
+ def extract_local(email)
107
+ parts = email.strip.split('@', 2)
108
+ return nil if parts.length != 2
109
+
110
+ parts[0]
111
+ end
112
+ end
113
+ end
114
+ end
metadata ADDED
@@ -0,0 +1,58 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: philiprehberger-email_validator
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Philip Rehberger
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-03-27 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Validates email addresses with RFC 5322 syntax checking, MX record verification,
14
+ disposable domain detection, and role-based address identification.
15
+ email:
16
+ - me@philiprehberger.com
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - CHANGELOG.md
22
+ - LICENSE
23
+ - README.md
24
+ - lib/philiprehberger/email_validator.rb
25
+ - lib/philiprehberger/email_validator/disposable.rb
26
+ - lib/philiprehberger/email_validator/mx_check.rb
27
+ - lib/philiprehberger/email_validator/result.rb
28
+ - lib/philiprehberger/email_validator/syntax.rb
29
+ - lib/philiprehberger/email_validator/version.rb
30
+ homepage: https://github.com/philiprehberger/rb-email-validator
31
+ licenses:
32
+ - MIT
33
+ metadata:
34
+ homepage_uri: https://github.com/philiprehberger/rb-email-validator
35
+ source_code_uri: https://github.com/philiprehberger/rb-email-validator
36
+ changelog_uri: https://github.com/philiprehberger/rb-email-validator/blob/main/CHANGELOG.md
37
+ bug_tracker_uri: https://github.com/philiprehberger/rb-email-validator/issues
38
+ rubygems_mfa_required: 'true'
39
+ post_install_message:
40
+ rdoc_options: []
41
+ require_paths:
42
+ - lib
43
+ required_ruby_version: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: 3.1.0
48
+ required_rubygems_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: '0'
53
+ requirements: []
54
+ rubygems_version: 3.5.22
55
+ signing_key:
56
+ specification_version: 4
57
+ summary: RFC-compliant email validation with MX record verification
58
+ test_files: []