truelist 0.2.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 +7 -0
- data/LICENSE +21 -0
- data/README.md +288 -0
- data/lib/truelist/account_info.rb +21 -0
- data/lib/truelist/client.rb +180 -0
- data/lib/truelist/configuration.rb +20 -0
- data/lib/truelist/errors.rb +19 -0
- data/lib/truelist/result.rb +58 -0
- data/lib/truelist/version.rb +5 -0
- data/lib/truelist.rb +28 -0
- metadata +57 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 4cc95465750e1a4ac521ef820c07005f72cfacb9f02a4970aefd2b64aace82ca
|
|
4
|
+
data.tar.gz: 8679e804ef330c865a60ffaf0a02ffa6a437cb8854f36b8731dd666ec2218381
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: b3fd1de9cde0aa2f490b46e583ef5dfde91359afe658d8374441a44f7edc500a6212ae7acfa4b731829090cc158c62ca5a851032ccf1ebcf1c80ca779fbddd25
|
|
7
|
+
data.tar.gz: a667a27d4f64fb1715aedcc97fc993d9feafca53c22ad0b88efd6082acb06f4b7c6795f524d0011db4a5870d665f4640d44037c8e8712c0cc926ce6fe11ee778
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Truelist
|
|
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,288 @@
|
|
|
1
|
+
# truelist
|
|
2
|
+
|
|
3
|
+
[](https://truelist.io/pricing)
|
|
4
|
+
Ruby SDK for [Truelist.io](https://truelist.io) email validation.
|
|
5
|
+
|
|
6
|
+
[](https://badge.fury.io/rb/truelist)
|
|
7
|
+
[](https://github.com/Truelist-io-Email-Validation/truelist-ruby/actions/workflows/ci.yml)
|
|
8
|
+
|
|
9
|
+
Verify email deliverability with a pure Ruby client. Zero runtime dependencies. Works anywhere Ruby runs -- no Rails required.
|
|
10
|
+
|
|
11
|
+
> **Start free** — 100 validations + 10 enhanced credits, no credit card required.
|
|
12
|
+
> [Get your API key →](https://app.truelist.io/signup?utm_source=github&utm_medium=readme&utm_campaign=free-plan&utm_content=truelist-ruby)
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
Add to your Gemfile:
|
|
17
|
+
|
|
18
|
+
```ruby
|
|
19
|
+
gem "truelist"
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Then run:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
bundle install
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Or install directly:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
gem install truelist
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Quick Start
|
|
35
|
+
|
|
36
|
+
```ruby
|
|
37
|
+
require "truelist"
|
|
38
|
+
|
|
39
|
+
# Set your API key (or use TRUELIST_API_KEY env var)
|
|
40
|
+
Truelist.configure do |config|
|
|
41
|
+
config.api_key = "your-api-key"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Validate an email
|
|
45
|
+
result = Truelist.validate("user@example.com")
|
|
46
|
+
result.valid? # => true
|
|
47
|
+
result.state # => "ok"
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Client Usage
|
|
51
|
+
|
|
52
|
+
### Validate an email
|
|
53
|
+
|
|
54
|
+
```ruby
|
|
55
|
+
client = Truelist::Client.new(api_key: "your-api-key")
|
|
56
|
+
|
|
57
|
+
result = client.validate("user@example.com")
|
|
58
|
+
result.state # => "ok"
|
|
59
|
+
result.sub_state # => "email_ok"
|
|
60
|
+
result.valid? # => true
|
|
61
|
+
result.domain # => "example.com"
|
|
62
|
+
result.verified_at # => "2026-02-21T10:00:00.000Z"
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Account info
|
|
66
|
+
|
|
67
|
+
```ruby
|
|
68
|
+
account = client.account
|
|
69
|
+
account.email # => "you@company.com"
|
|
70
|
+
account.name # => "Team Lead"
|
|
71
|
+
account.payment_plan # => "pro"
|
|
72
|
+
account.admin? # => true
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
The `account` method respects `raise_on_error`. When disabled (default), transient errors (429, 5xx, timeouts) return `nil` instead of raising. Authentication errors (401) always raise regardless of this setting.
|
|
76
|
+
|
|
77
|
+
### Shortcut methods
|
|
78
|
+
|
|
79
|
+
Read `TRUELIST_API_KEY` from your environment automatically:
|
|
80
|
+
|
|
81
|
+
```ruby
|
|
82
|
+
result = Truelist.validate("user@example.com")
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Configuration
|
|
86
|
+
|
|
87
|
+
```ruby
|
|
88
|
+
Truelist.configure do |config|
|
|
89
|
+
# Your Truelist API key (required).
|
|
90
|
+
# Defaults to ENV["TRUELIST_API_KEY"].
|
|
91
|
+
config.api_key = ENV["TRUELIST_API_KEY"]
|
|
92
|
+
|
|
93
|
+
# API base URL. Change only for testing or proxying.
|
|
94
|
+
config.base_url = "https://api.truelist.io"
|
|
95
|
+
|
|
96
|
+
# Request timeout in seconds.
|
|
97
|
+
config.timeout = 10
|
|
98
|
+
|
|
99
|
+
# Max retry attempts on 429/5xx errors.
|
|
100
|
+
config.max_retries = 2
|
|
101
|
+
|
|
102
|
+
# When true, raises Truelist::Error on API failures.
|
|
103
|
+
# When false (default), returns an "unknown" result on transient errors,
|
|
104
|
+
# allowing your code to handle failures gracefully.
|
|
105
|
+
config.raise_on_error = false
|
|
106
|
+
end
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
You can also pass configuration per-client:
|
|
110
|
+
|
|
111
|
+
```ruby
|
|
112
|
+
client = Truelist::Client.new(
|
|
113
|
+
api_key: "different-key",
|
|
114
|
+
base_url: "https://custom.api.com",
|
|
115
|
+
timeout: 30,
|
|
116
|
+
max_retries: 5
|
|
117
|
+
)
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Error Handling
|
|
121
|
+
|
|
122
|
+
By default, transient errors (timeouts, rate limits, server errors) return a `Result` with `error?: true` and `state: "unknown"`. This prevents your application from breaking when the API is unreachable.
|
|
123
|
+
|
|
124
|
+
**Authentication errors (401) always raise**, regardless of the `raise_on_error` setting.
|
|
125
|
+
|
|
126
|
+
To raise exceptions on all errors:
|
|
127
|
+
|
|
128
|
+
```ruby
|
|
129
|
+
Truelist.configure do |config|
|
|
130
|
+
config.raise_on_error = true
|
|
131
|
+
end
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Exception classes:
|
|
135
|
+
|
|
136
|
+
- `Truelist::Error` -- base error class
|
|
137
|
+
- `Truelist::AuthenticationError` -- invalid API key (401)
|
|
138
|
+
- `Truelist::RateLimitError` -- rate limit exceeded (429)
|
|
139
|
+
- `Truelist::ApiError` -- unexpected API responses (includes `status` and `body` attributes)
|
|
140
|
+
|
|
141
|
+
## Retries
|
|
142
|
+
|
|
143
|
+
The client automatically retries on 429 (rate limit) and 5xx (server error) responses with exponential backoff. Configure with `max_retries` (default: 2).
|
|
144
|
+
|
|
145
|
+
```ruby
|
|
146
|
+
Truelist.configure do |config|
|
|
147
|
+
config.max_retries = 3 # retry up to 3 times
|
|
148
|
+
end
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Set to `0` to disable retries.
|
|
152
|
+
|
|
153
|
+
## Result Object
|
|
154
|
+
|
|
155
|
+
The `Truelist::Result` is a frozen data object with the following attributes:
|
|
156
|
+
|
|
157
|
+
| Attribute | Type | Description |
|
|
158
|
+
|-----------|------|-------------|
|
|
159
|
+
| `email` | String | The email that was validated |
|
|
160
|
+
| `state` | String | `"ok"`, `"email_invalid"`, `"accept_all"`, or `"unknown"` |
|
|
161
|
+
| `sub_state` | String | Detailed sub-state (see below) |
|
|
162
|
+
| `suggestion` | String/nil | Suggested correction, if available |
|
|
163
|
+
| `domain` | String/nil | Domain of the email address |
|
|
164
|
+
| `canonical` | String/nil | Canonical part of the email |
|
|
165
|
+
| `mx_record` | String/nil | MX record for the domain |
|
|
166
|
+
| `first_name` | String/nil | First name associated with the email |
|
|
167
|
+
| `last_name` | String/nil | Last name associated with the email |
|
|
168
|
+
| `verified_at` | String/nil | Timestamp of verification |
|
|
169
|
+
| `error` | Boolean | Whether an API error occurred |
|
|
170
|
+
|
|
171
|
+
### Predicates
|
|
172
|
+
|
|
173
|
+
| Method | Returns true when |
|
|
174
|
+
|--------|-------------------|
|
|
175
|
+
| `valid?` | State is `"ok"` |
|
|
176
|
+
| `valid?(allow_risky: true)` | State is `"ok"` or `"accept_all"` |
|
|
177
|
+
| `deliverable?` | State is `"ok"` or `"accept_all"` (alias for `valid?(allow_risky: true)`) |
|
|
178
|
+
| `deliverable?(allow_risky: false)` | State is `"ok"` only |
|
|
179
|
+
| `invalid?` | State is `"email_invalid"` |
|
|
180
|
+
| `accept_all?` | State is `"accept_all"` |
|
|
181
|
+
| `unknown?` | State is `"unknown"` |
|
|
182
|
+
| `disposable?` | Sub-state is `"is_disposable"` |
|
|
183
|
+
| `role?` | Sub-state is `"is_role"` |
|
|
184
|
+
| `error?` | An API error occurred |
|
|
185
|
+
|
|
186
|
+
### Sub-states
|
|
187
|
+
|
|
188
|
+
| Sub-state | Meaning |
|
|
189
|
+
|-----------|---------|
|
|
190
|
+
| `email_ok` | Email is valid and deliverable |
|
|
191
|
+
| `is_disposable` | Disposable/temporary email |
|
|
192
|
+
| `is_role` | Role-based address (info@, admin@) |
|
|
193
|
+
| `failed_smtp_check` | SMTP check failed |
|
|
194
|
+
| `unknown_error` | Could not determine status |
|
|
195
|
+
|
|
196
|
+
## Account Info Object
|
|
197
|
+
|
|
198
|
+
The `Truelist::AccountInfo` is a frozen data object:
|
|
199
|
+
|
|
200
|
+
| Attribute | Type | Description |
|
|
201
|
+
|-----------|------|-------------|
|
|
202
|
+
| `email` | String | Account email |
|
|
203
|
+
| `name` | String | Account holder name |
|
|
204
|
+
| `uuid` | String | Account UUID |
|
|
205
|
+
| `time_zone` | String | Account time zone |
|
|
206
|
+
| `is_admin_role` | Boolean | Whether user is admin |
|
|
207
|
+
| `payment_plan` | String | Current payment plan |
|
|
208
|
+
|
|
209
|
+
### Predicates
|
|
210
|
+
|
|
211
|
+
| Method | Returns true when |
|
|
212
|
+
|--------|-------------------|
|
|
213
|
+
| `admin?` | User has admin role |
|
|
214
|
+
|
|
215
|
+
## For Rails Users
|
|
216
|
+
|
|
217
|
+
If you're using Rails, check out [truelist-rails](https://github.com/Truelist-io-Email-Validation/truelist-rails) which provides ActiveModel validators on top of this gem:
|
|
218
|
+
|
|
219
|
+
```ruby
|
|
220
|
+
# Gemfile
|
|
221
|
+
gem "truelist-rails"
|
|
222
|
+
|
|
223
|
+
# app/models/user.rb
|
|
224
|
+
validates :email, deliverable: true
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
## Testing
|
|
228
|
+
|
|
229
|
+
Stub the API in your tests with WebMock:
|
|
230
|
+
|
|
231
|
+
```ruby
|
|
232
|
+
require "webmock/rspec"
|
|
233
|
+
|
|
234
|
+
RSpec.configure do |config|
|
|
235
|
+
config.before do
|
|
236
|
+
stub_request(:post, %r{https://api\.truelist\.io/api/v1/verify_inline})
|
|
237
|
+
.to_return(
|
|
238
|
+
status: 200,
|
|
239
|
+
body: {
|
|
240
|
+
emails: [{
|
|
241
|
+
address: "user@example.com",
|
|
242
|
+
domain: "example.com",
|
|
243
|
+
canonical: "user",
|
|
244
|
+
mx_record: nil,
|
|
245
|
+
first_name: nil,
|
|
246
|
+
last_name: nil,
|
|
247
|
+
email_state: "ok",
|
|
248
|
+
email_sub_state: "email_ok",
|
|
249
|
+
verified_at: "2026-02-21T10:00:00.000Z",
|
|
250
|
+
did_you_mean: nil
|
|
251
|
+
}]
|
|
252
|
+
}.to_json,
|
|
253
|
+
headers: { "Content-Type" => "application/json" }
|
|
254
|
+
)
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
Or stub at the client level:
|
|
260
|
+
|
|
261
|
+
```ruby
|
|
262
|
+
allow(Truelist::Client).to receive(:new).and_return(
|
|
263
|
+
instance_double(Truelist::Client, validate: Truelist::Result.new(email: "user@example.com", state: "ok"))
|
|
264
|
+
)
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
## Requirements
|
|
268
|
+
|
|
269
|
+
- Ruby >= 3.0
|
|
270
|
+
- No runtime dependencies
|
|
271
|
+
|
|
272
|
+
## Development
|
|
273
|
+
|
|
274
|
+
```bash
|
|
275
|
+
git clone https://github.com/Truelist-io-Email-Validation/truelist-ruby.git
|
|
276
|
+
cd truelist-ruby
|
|
277
|
+
bundle install
|
|
278
|
+
bundle exec rspec
|
|
279
|
+
bundle exec rubocop
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
## Getting Started
|
|
284
|
+
|
|
285
|
+
Sign up for a [free Truelist account](https://app.truelist.io/signup?utm_source=github&utm_medium=readme&utm_campaign=free-plan&utm_content=truelist-ruby) to get your API key. The free plan includes 100 validations and 10 enhanced credits — no credit card required.
|
|
286
|
+
## License
|
|
287
|
+
|
|
288
|
+
Released under the [MIT License](LICENSE).
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Truelist
|
|
4
|
+
class AccountInfo
|
|
5
|
+
attr_reader :email, :name, :uuid, :time_zone, :is_admin_role, :payment_plan
|
|
6
|
+
|
|
7
|
+
def initialize(email:, name:, uuid:, time_zone:, is_admin_role:, payment_plan:)
|
|
8
|
+
@email = email
|
|
9
|
+
@name = name
|
|
10
|
+
@uuid = uuid
|
|
11
|
+
@time_zone = time_zone
|
|
12
|
+
@is_admin_role = is_admin_role
|
|
13
|
+
@payment_plan = payment_plan
|
|
14
|
+
freeze
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def admin?
|
|
18
|
+
@is_admin_role
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'net/http'
|
|
4
|
+
require 'uri'
|
|
5
|
+
require 'json'
|
|
6
|
+
|
|
7
|
+
module Truelist
|
|
8
|
+
class Client
|
|
9
|
+
def initialize(api_key: nil, **options)
|
|
10
|
+
@api_key = api_key
|
|
11
|
+
@base_url = options[:base_url]
|
|
12
|
+
@timeout = options[:timeout]
|
|
13
|
+
@max_retries = options[:max_retries]
|
|
14
|
+
@raise_on_error = options[:raise_on_error]
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def validate(email)
|
|
18
|
+
uri = URI("#{base_url}/api/v1/verify_inline?email=#{URI.encode_www_form_component(email)}")
|
|
19
|
+
|
|
20
|
+
response = request_with_retries(:post, uri)
|
|
21
|
+
parse_validation_response(email, response)
|
|
22
|
+
rescue Truelist::AuthenticationError
|
|
23
|
+
raise
|
|
24
|
+
rescue StandardError => e
|
|
25
|
+
handle_error(email, e)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def account
|
|
29
|
+
uri = URI("#{base_url}/me")
|
|
30
|
+
|
|
31
|
+
response = request_with_retries(:get, uri)
|
|
32
|
+
parse_account_response(response)
|
|
33
|
+
rescue Truelist::AuthenticationError
|
|
34
|
+
raise
|
|
35
|
+
rescue StandardError
|
|
36
|
+
raise if raise_on_error?
|
|
37
|
+
|
|
38
|
+
nil
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
def config = Truelist.configuration
|
|
44
|
+
def api_key = @api_key || config.api_key!
|
|
45
|
+
def base_url = @base_url || config.base_url
|
|
46
|
+
def timeout = @timeout || config.timeout
|
|
47
|
+
def max_retries = @max_retries || config.max_retries
|
|
48
|
+
|
|
49
|
+
def raise_on_error?
|
|
50
|
+
return @raise_on_error unless @raise_on_error.nil?
|
|
51
|
+
|
|
52
|
+
config.raise_on_error
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def request_with_retries(method, uri, auth_key: nil)
|
|
56
|
+
retries = 0
|
|
57
|
+
key = auth_key || api_key
|
|
58
|
+
|
|
59
|
+
loop do
|
|
60
|
+
response = perform_request(method, uri, auth_key: key)
|
|
61
|
+
check_authentication!(response)
|
|
62
|
+
|
|
63
|
+
return response if response.code.to_i < 429 || retries >= max_retries
|
|
64
|
+
|
|
65
|
+
if retryable_status?(response.code.to_i)
|
|
66
|
+
retries += 1
|
|
67
|
+
sleep(backoff_delay(retries))
|
|
68
|
+
next
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
return response
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def check_authentication!(response)
|
|
76
|
+
return unless response.code.to_i == 401
|
|
77
|
+
|
|
78
|
+
raise Truelist::AuthenticationError, 'Invalid API key. Check your Truelist API key configuration.'
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def retryable_status?(status)
|
|
82
|
+
status == 429 || status >= 500
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def backoff_delay(retry_count)
|
|
86
|
+
(2**retry_count) * 0.1
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def perform_request(method, uri, auth_key: nil)
|
|
90
|
+
http = build_http(uri)
|
|
91
|
+
|
|
92
|
+
request = build_request(method, uri, auth_key: auth_key || api_key)
|
|
93
|
+
http.request(request)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def build_http(uri)
|
|
97
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
98
|
+
http.use_ssl = uri.scheme == 'https'
|
|
99
|
+
http.open_timeout = timeout
|
|
100
|
+
http.read_timeout = timeout
|
|
101
|
+
http
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def build_request(method, uri, auth_key: nil)
|
|
105
|
+
request = case method
|
|
106
|
+
when :get then Net::HTTP::Get.new(uri)
|
|
107
|
+
when :post then Net::HTTP::Post.new(uri)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
request['Authorization'] = "Bearer #{auth_key || api_key}"
|
|
111
|
+
request['Content-Type'] = 'application/json'
|
|
112
|
+
request['Accept'] = 'application/json'
|
|
113
|
+
request['User-Agent'] = "truelist-ruby/#{Truelist::VERSION}"
|
|
114
|
+
|
|
115
|
+
request
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def parse_validation_response(email, response)
|
|
119
|
+
status = response.code.to_i
|
|
120
|
+
|
|
121
|
+
case status
|
|
122
|
+
when 200
|
|
123
|
+
parse_success(email, response.body)
|
|
124
|
+
when 429
|
|
125
|
+
handle_error(email, Truelist::RateLimitError.new('Rate limit exceeded'))
|
|
126
|
+
else
|
|
127
|
+
handle_error(email, Truelist::ApiError.new("API returned #{status}: #{response.body}",
|
|
128
|
+
status: status, body: response.body))
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def parse_success(email_input, body)
|
|
133
|
+
data = JSON.parse(body)
|
|
134
|
+
email_data = data['emails']&.first || {}
|
|
135
|
+
|
|
136
|
+
Result.new(
|
|
137
|
+
email: email_data['address'] || email_input,
|
|
138
|
+
state: email_data['email_state'] || 'unknown',
|
|
139
|
+
sub_state: email_data['email_sub_state'],
|
|
140
|
+
suggestion: email_data['did_you_mean'],
|
|
141
|
+
domain: email_data['domain'],
|
|
142
|
+
canonical: email_data['canonical'],
|
|
143
|
+
mx_record: email_data['mx_record'],
|
|
144
|
+
first_name: email_data['first_name'],
|
|
145
|
+
last_name: email_data['last_name'],
|
|
146
|
+
verified_at: email_data['verified_at']
|
|
147
|
+
)
|
|
148
|
+
rescue JSON::ParserError => e
|
|
149
|
+
handle_error(email_input, e)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def parse_account_response(response)
|
|
153
|
+
status = response.code.to_i
|
|
154
|
+
|
|
155
|
+
case status
|
|
156
|
+
when 200
|
|
157
|
+
data = JSON.parse(response.body)
|
|
158
|
+
AccountInfo.new(
|
|
159
|
+
email: data['email'],
|
|
160
|
+
name: data['name'],
|
|
161
|
+
uuid: data['uuid'],
|
|
162
|
+
time_zone: data['time_zone'],
|
|
163
|
+
is_admin_role: data['is_admin_role'],
|
|
164
|
+
payment_plan: data.dig('account', 'payment_plan')
|
|
165
|
+
)
|
|
166
|
+
when 429
|
|
167
|
+
raise Truelist::RateLimitError, 'Rate limit exceeded'
|
|
168
|
+
else
|
|
169
|
+
raise Truelist::ApiError.new("API returned #{status}: #{response.body}",
|
|
170
|
+
status: status, body: response.body)
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def handle_error(email, error)
|
|
175
|
+
raise error if raise_on_error?
|
|
176
|
+
|
|
177
|
+
Result.new(email: email, state: 'unknown', error: true)
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Truelist
|
|
4
|
+
class Configuration
|
|
5
|
+
attr_accessor :api_key, :base_url, :timeout, :max_retries, :raise_on_error
|
|
6
|
+
|
|
7
|
+
def initialize
|
|
8
|
+
@api_key = ENV.fetch('TRUELIST_API_KEY', nil)
|
|
9
|
+
@base_url = 'https://api.truelist.io'
|
|
10
|
+
@timeout = 10
|
|
11
|
+
@max_retries = 2
|
|
12
|
+
@raise_on_error = false
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def api_key!
|
|
16
|
+
api_key || raise(Truelist::AuthenticationError,
|
|
17
|
+
'Truelist API key is not configured. Set TRUELIST_API_KEY or use Truelist.configure.')
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Truelist
|
|
4
|
+
class Error < StandardError; end
|
|
5
|
+
|
|
6
|
+
class AuthenticationError < Error; end
|
|
7
|
+
|
|
8
|
+
class RateLimitError < Error; end
|
|
9
|
+
|
|
10
|
+
class ApiError < Error
|
|
11
|
+
attr_reader :status, :body
|
|
12
|
+
|
|
13
|
+
def initialize(message = nil, status: nil, body: nil)
|
|
14
|
+
@status = status
|
|
15
|
+
@body = body
|
|
16
|
+
super(message)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Truelist
|
|
4
|
+
class Result
|
|
5
|
+
attr_reader :email, :state, :sub_state, :suggestion, :domain, :canonical, :mx_record,
|
|
6
|
+
:first_name, :last_name, :verified_at, :error
|
|
7
|
+
|
|
8
|
+
def initialize(email:, state:, sub_state: nil, suggestion: nil, domain: nil, canonical: nil, mx_record: nil,
|
|
9
|
+
first_name: nil, last_name: nil, verified_at: nil, error: false)
|
|
10
|
+
@email = email
|
|
11
|
+
@state = state.to_s
|
|
12
|
+
@sub_state = sub_state&.to_s
|
|
13
|
+
@suggestion = suggestion
|
|
14
|
+
@domain = domain
|
|
15
|
+
@canonical = canonical
|
|
16
|
+
@mx_record = mx_record
|
|
17
|
+
@first_name = first_name
|
|
18
|
+
@last_name = last_name
|
|
19
|
+
@verified_at = verified_at
|
|
20
|
+
@error = error
|
|
21
|
+
freeze
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def valid?(allow_risky: false)
|
|
25
|
+
return %w[ok accept_all].include?(state) if allow_risky
|
|
26
|
+
|
|
27
|
+
state == 'ok'
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def deliverable?(allow_risky: true)
|
|
31
|
+
valid?(allow_risky: allow_risky)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def invalid?
|
|
35
|
+
state == 'email_invalid'
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def accept_all?
|
|
39
|
+
state == 'accept_all'
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def unknown?
|
|
43
|
+
state == 'unknown'
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def disposable?
|
|
47
|
+
sub_state == 'is_disposable'
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def role?
|
|
51
|
+
sub_state == 'is_role'
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def error?
|
|
55
|
+
@error
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
data/lib/truelist.rb
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'truelist/version'
|
|
4
|
+
require_relative 'truelist/errors'
|
|
5
|
+
require_relative 'truelist/configuration'
|
|
6
|
+
require_relative 'truelist/result'
|
|
7
|
+
require_relative 'truelist/account_info'
|
|
8
|
+
require_relative 'truelist/client'
|
|
9
|
+
|
|
10
|
+
module Truelist
|
|
11
|
+
class << self
|
|
12
|
+
def configuration
|
|
13
|
+
@configuration ||= Configuration.new
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def configure
|
|
17
|
+
yield(configuration)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def reset_configuration!
|
|
21
|
+
@configuration = Configuration.new
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def validate(email)
|
|
25
|
+
Client.new.validate(email)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: truelist
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.2.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Truelist
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-04-08 00:00:00.000000000 Z
|
|
12
|
+
dependencies: []
|
|
13
|
+
description: Pure Ruby client for the Truelist.io email validation API. Verify email
|
|
14
|
+
deliverability with zero dependencies. Works anywhere Ruby runs — no Rails required.
|
|
15
|
+
email:
|
|
16
|
+
- support@truelist.io
|
|
17
|
+
executables: []
|
|
18
|
+
extensions: []
|
|
19
|
+
extra_rdoc_files: []
|
|
20
|
+
files:
|
|
21
|
+
- LICENSE
|
|
22
|
+
- README.md
|
|
23
|
+
- lib/truelist.rb
|
|
24
|
+
- lib/truelist/account_info.rb
|
|
25
|
+
- lib/truelist/client.rb
|
|
26
|
+
- lib/truelist/configuration.rb
|
|
27
|
+
- lib/truelist/errors.rb
|
|
28
|
+
- lib/truelist/result.rb
|
|
29
|
+
- lib/truelist/version.rb
|
|
30
|
+
homepage: https://github.com/Truelist-io-Email-Validation/truelist-ruby
|
|
31
|
+
licenses:
|
|
32
|
+
- MIT
|
|
33
|
+
metadata:
|
|
34
|
+
homepage_uri: https://github.com/Truelist-io-Email-Validation/truelist-ruby
|
|
35
|
+
source_code_uri: https://github.com/Truelist-io-Email-Validation/truelist-ruby
|
|
36
|
+
changelog_uri: https://github.com/Truelist-io-Email-Validation/truelist-ruby/blob/main/CHANGELOG.md
|
|
37
|
+
rubygems_mfa_required: 'true'
|
|
38
|
+
post_install_message:
|
|
39
|
+
rdoc_options: []
|
|
40
|
+
require_paths:
|
|
41
|
+
- lib
|
|
42
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '3.0'
|
|
47
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
48
|
+
requirements:
|
|
49
|
+
- - ">="
|
|
50
|
+
- !ruby/object:Gem::Version
|
|
51
|
+
version: '0'
|
|
52
|
+
requirements: []
|
|
53
|
+
rubygems_version: 3.4.10
|
|
54
|
+
signing_key:
|
|
55
|
+
specification_version: 4
|
|
56
|
+
summary: Ruby SDK for Truelist.io email validation
|
|
57
|
+
test_files: []
|