zerobounce-sdk 1.1.2 → 2.0.13

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.
@@ -9,7 +9,7 @@ module Zerobounce
9
9
  class Request < BaseRequest
10
10
 
11
11
  def self.get(path, params, content_type='application/json')
12
- response = self._get(Zerobounce::API_ROOT_URL, path, params, content_type)
12
+ response = self._get(Zerobounce.configuration.api_root_url, path, params, content_type)
13
13
  if response.headers[:content_type] == 'application/json'
14
14
  response_body = response.body
15
15
  response_body_json = JSON.parse(response_body)
@@ -29,7 +29,7 @@ module Zerobounce
29
29
  end
30
30
 
31
31
  def self.bulk_get(path, params, content_type='application/json')
32
- response = self._get(Zerobounce::BULK_API_ROOT_URL, path, params, content_type)
32
+ response = self._get(Zerobounce.configuration.bulk_api_root_url, path, params, content_type)
33
33
  if response.headers[:content_type] == 'application/json'
34
34
  response_body = response.body
35
35
  response_body_json = JSON.parse(response_body)
@@ -48,7 +48,7 @@ module Zerobounce
48
48
  end
49
49
 
50
50
  def self.bulk_post(path, params, content_type='application/json', filepath=nil)
51
- response = self._post(Zerobounce::BULK_API_ROOT_URL, path, params, \
51
+ response = self._post(Zerobounce.configuration.bulk_api_root_url, path, params, \
52
52
  content_type, filepath)
53
53
  if response.headers[:content_type] == 'application/json'
54
54
  response_body = response.body
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Zerobounce
4
+ # Validation status values returned by the API (validate, validate_batch).
5
+ # Use for comparison: response['status'] == Zerobounce::ValidateStatus::VALID
6
+ # Unknown/future API values are not listed; compare against response['status'] as string.
7
+ module ValidateStatus
8
+ NONE = ''
9
+ VALID = 'valid'
10
+ INVALID = 'invalid'
11
+ CATCH_ALL = 'catch-all'
12
+ UNKNOWN = 'unknown'
13
+ SPAMTRAP = 'spamtrap'
14
+ ABUSE = 'abuse'
15
+ DO_NOT_MAIL = 'do_not_mail'
16
+ end
17
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Zerobounce
4
+ # Validation sub-status values returned by the API (validate, validate_batch).
5
+ # Use for comparison: response['sub_status'] == Zerobounce::ValidateSubStatus::ACCEPT_ALL
6
+ # Unknown/future API values are not listed; compare against response['sub_status'] as string.
7
+ module ValidateSubStatus
8
+ NONE = ''
9
+ ANTISPAM_SYSTEM = 'antispam_system'
10
+ GREYLISTED = 'greylisted'
11
+ MAIL_SERVER_TEMPORARY_ERROR = 'mail_server_temporary_error'
12
+ FORCIBLE_DISCONNECT = 'forcible_disconnect'
13
+ MAIL_SERVER_DID_NOT_RESPOND = 'mail_server_did_not_respond'
14
+ TIMEOUT_EXCEEDED = 'timeout_exceeded'
15
+ FAILED_SMTP_CONNECTION = 'failed_smtp_connection'
16
+ MAILBOX_QUOTA_EXCEEDED = 'mailbox_quota_exceeded'
17
+ EXCEPTION_OCCURRED = 'exception_occurred'
18
+ POSSIBLE_TRAP = 'possible_trap'
19
+ ROLE_BASED = 'role_based'
20
+ GLOBAL_SUPPRESSION = 'global_suppression'
21
+ MAILBOX_NOT_FOUND = 'mailbox_not_found'
22
+ NO_DNS_ENTRIES = 'no_dns_entries'
23
+ FAILED_SYNTAX_CHECK = 'failed_syntax_check'
24
+ POSSIBLE_TYPO = 'possible_typo'
25
+ UNROUTABLE_IP_ADDRESS = 'unroutable_ip_address'
26
+ LEADING_PERIOD_REMOVED = 'leading_period_removed'
27
+ DOES_NOT_ACCEPT_MAIL = 'does_not_accept_mail'
28
+ ALIAS_ADDRESS = 'alias_address'
29
+ ROLE_BASED_CATCH_ALL = 'role_based_catch_all'
30
+ DISPOSABLE = 'disposable'
31
+ TOXIC = 'toxic'
32
+ ALTERNATE = 'alternate'
33
+ MX_FORWARD = 'mx_forward'
34
+ BLOCKED = 'blocked'
35
+ ALLOWED = 'allowed'
36
+ ACCEPT_ALL = 'accept_all'
37
+ ROLE_BASED_ACCEPT_ALL = 'role_based_accept_all'
38
+ GOLD = 'gold'
39
+ end
40
+ end
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Zerobounce
4
4
  # The version of the gem.
5
- VERSION = '1.1.2'
5
+ VERSION = '2.0.13'
6
6
  end
data/lib/zerobounce.rb CHANGED
@@ -2,19 +2,20 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require 'date'
5
+ require 'tempfile'
5
6
 
6
7
  require 'zerobounce/error'
7
8
  require 'zerobounce/version'
9
+ require 'zerobounce/validate_status'
10
+ require 'zerobounce/validate_sub_status'
8
11
  require 'zerobounce/request'
9
12
  require 'zerobounce/mock_request'
10
13
  require 'zerobounce/configuration'
14
+ require 'zerobounce/api_urls'
11
15
 
12
16
  # Validate an email address with Zerobounce.net
13
17
  module Zerobounce
14
18
 
15
- API_ROOT_URL = 'https://api.zerobounce.net/v2'
16
- BULK_API_ROOT_URL = 'https://bulkapi.zerobounce.net/v2'
17
-
18
19
  class << self
19
20
  attr_writer :configuration
20
21
 
@@ -135,10 +136,13 @@ module Zerobounce
135
136
  # "sub_status_mailbox_quota_exceeded": 0,
136
137
  # "sub_status_forcible_disconnect": 0,
137
138
  # "sub_status_failed_smtp_connection": 0,
139
+ # "sub_status_accept_all": 0,
138
140
  # "sub_status_mx_forward": 0,
139
141
  # "sub_status_alternate": 0,
140
- # "sub_status_blocked": 0,
141
142
  # "sub_status_allowed": 0,
143
+ # "sub_status_blocked": 0,
144
+ # "sub_status_gold": 0,
145
+ # "sub_status_role_based_accept_all": 0,
142
146
  # "start_date": "1/1/2018",
143
147
  # "end_date": "12/12/2023"
144
148
  # }
@@ -256,6 +260,27 @@ module Zerobounce
256
260
  @@request.bulk_post('sendfile', params, 'multipart/form-data', filepath)
257
261
  end
258
262
 
263
+ # Validate CSV from a stream (IO or String).
264
+ #
265
+ # @param [IO, String] io Stream or string content to upload
266
+ # @param [String] file_name Filename for the upload (e.g. "emails.csv")
267
+ # @option [Int] :email_address_column (same as validate_file_send)
268
+ # @option [Int] :first_name_column
269
+ # @option [Int] :last_name_column
270
+ # @option [Int] :gender_column
271
+ # @option [Int] :has_header_row
272
+ # @option [Int] :return_url
273
+ # @return [Hash] same as validate_file_send
274
+ def validate_file_send_stream(io, file_name, **opts)
275
+ content = io.respond_to?(:read) ? io.read : io.to_s
276
+ Tempfile.create(['zb', File.extname(file_name)]) do |tmp|
277
+ tmp.binmode
278
+ tmp.write(content)
279
+ tmp.rewind
280
+ validate_file_send(tmp.path, **opts)
281
+ end
282
+ end
283
+
259
284
  # Get validate file status
260
285
  #
261
286
  # @param [String] :file_id Id of the file.
@@ -333,6 +358,24 @@ module Zerobounce
333
358
  @@request.bulk_post('scoring/sendfile', params, 'multipart/form-data', filepath)
334
359
  end
335
360
 
361
+ # Score CSV from a stream (IO or String).
362
+ #
363
+ # @param [IO, String] io Stream or string content to upload
364
+ # @param [String] file_name Filename for the upload (e.g. "emails.csv")
365
+ # @option [Int] :email_address_column (same as scoring_file_send)
366
+ # @option [Int] :has_header_row
367
+ # @option [Int] :return_url
368
+ # @return [Hash] same as scoring_file_send
369
+ def scoring_file_send_stream(io, file_name, **opts)
370
+ content = io.respond_to?(:read) ? io.read : io.to_s
371
+ Tempfile.create(['zb', File.extname(file_name)]) do |tmp|
372
+ tmp.binmode
373
+ tmp.write(content)
374
+ tmp.rewind
375
+ scoring_file_send(tmp.path, **opts)
376
+ end
377
+ end
378
+
336
379
  # Get validate results file
337
380
  #
338
381
  # @param [String] :file_id Id of the file.
@@ -427,19 +470,120 @@ module Zerobounce
427
470
  # ]
428
471
  # }
429
472
  def guessformat(domain, first_name: '', middle_name: '', last_name: '')
430
- params = {
431
- domain: domain
432
- }
433
- unless first_name.empty?
434
- params['first_name'] = first_name
435
- end
436
- unless middle_name.empty?
437
- params['middle_name'] = middle_name
473
+ warn "[DEPRECATION] `guessformat` is deprecated and will be removed in a future version.\n" \
474
+ "Please use `find_email` or `find_domain` instead."
475
+ params = {domain: domain}
476
+ params[:first_name] = first_name unless first_name.nil? || first_name.empty?
477
+ params[:middle_name] = middle_name unless middle_name.nil? || middle_name.empty?
478
+ params[:last_name] = last_name unless last_name.nil? || last_name.empty?
479
+ @@request.get('guessformat', params)
480
+ end
481
+
482
+
483
+ # Find email address format
484
+ #
485
+ # @option [String] domain Domain to search within (e.g. example.com). Required if company_name is not provided.
486
+ # @option [String] company_name Company name to search within (e.g. Example). Required if domain is not provided.
487
+ # @option [String] first_name First name of target.
488
+ # @option [String] middle_name Middle name of target.
489
+ # @option [String] last_name Last name of target.
490
+ #
491
+ # @return [Hash]
492
+ # {
493
+ # "email": "john@zerobounce.net",
494
+ # "email_confidence": "medium",
495
+ # "domain": "zerobounce.net",
496
+ # "company_name": "ZeroBounce",
497
+ # "did_you_mean": "",
498
+ # "failure_reason": ""
499
+ # }
500
+ def find_email(first_name, domain: '', company_name: '', middle_name: '', last_name: '')
501
+ # Validate that exactly one of domain or company_name is provided
502
+ if (domain.nil? || domain.empty?) && (company_name.nil? || company_name.empty?)
503
+ raise ArgumentError, "Either domain or company_name must be provided"
504
+ elsif !(domain.nil? || domain.empty?) && !(company_name.nil? || company_name.empty?)
505
+ raise ArgumentError, "Only one of domain or company_name can be provided"
438
506
  end
439
- unless last_name.empty?
440
- params['last_name'] = last_name
507
+
508
+ params = { first_name: first_name }
509
+ params[:domain] = domain unless domain.nil? || domain.empty?
510
+ params[:company_name] = company_name unless company_name.nil? || company_name.empty?
511
+ params[:middle_name] = middle_name unless middle_name.nil? || middle_name.empty?
512
+ params[:last_name] = last_name unless last_name.nil? || last_name.empty?
513
+
514
+ @@request.get('guessformat', params)
515
+ end
516
+
517
+ # Find domain format
518
+ #
519
+ # @option [String] domain Domain to search within (e.g. example.com). Required if company_name is not provided.
520
+ # @option [String] company_name Company name to search within (e.g. Example). Required if domain is not provided.
521
+ #
522
+ # @return [Hash]
523
+ # {
524
+ # "domain": "zerobounce.net",
525
+ # "company_name": "Hertza, LLC",
526
+ # "format": "first.last",
527
+ # "confidence": "high",
528
+ # "did_you_mean": "",
529
+ # "failure_reason": "",
530
+ # "other_domain_formats": [
531
+ # {"format": "first", "confidence": "high"},
532
+ # {"format": "last.first", "confidence": "high"},
533
+ # {"format": "lastfirst", "confidence": "high"},
534
+ # {"format": "firstl", "confidence": "high"},
535
+ # {"format": "lfirst", "confidence": "high"},
536
+ # {"format": "firstlast", "confidence": "high"},
537
+ # {"format": "last_middle_f", "confidence": "high"},
538
+ # {"format": "last", "confidence": "high"},
539
+ # {"format": "f.last", "confidence": "medium"},
540
+ # {"format": "last-f", "confidence": "medium"},
541
+ # {"format": "l.first", "confidence": "medium"},
542
+ # {"format": "last_f", "confidence": "medium"},
543
+ # {"format": "first.middle.last", "confidence": "medium"},
544
+ # {"format": "first-last", "confidence": "medium"},
545
+ # {"format": "last.f", "confidence": "medium"},
546
+ # {"format": "last_first", "confidence": "medium"},
547
+ # {"format": "f-last", "confidence": "medium"},
548
+ # {"format": "first.l", "confidence": "medium"},
549
+ # {"format": "first-l", "confidence": "medium"},
550
+ # {"format": "first_l", "confidence": "medium"},
551
+ # {"format": "first_last", "confidence": "medium"},
552
+ # {"format": "f_last", "confidence": "medium"},
553
+ # {"format": "last-first", "confidence": "medium"},
554
+ # {"format": "flast", "confidence": "medium"},
555
+ # {"format": "lastf", "confidence": "medium"},
556
+ # {"format": "l_first", "confidence": "medium"},
557
+ # {"format": "l-first", "confidence": "medium"},
558
+ # {"format": "first-middle-last", "confidence": "low"},
559
+ # {"format": "firstmlast", "confidence": "low"},
560
+ # {"format": "last.middle.first", "confidence": "low"},
561
+ # {"format": "last_middle_first", "confidence": "low"},
562
+ # {"format": "first_middle_last", "confidence": "low"},
563
+ # {"format": "last-middle-first", "confidence": "low"},
564
+ # {"format": "first-m-last", "confidence": "low"},
565
+ # {"format": "firstmiddlelast", "confidence": "low"},
566
+ # {"format": "last.m.first", "confidence": "low"},
567
+ # {"format": "lastmfirst", "confidence": "low"},
568
+ # {"format": "lastmiddlefirst", "confidence": "low"},
569
+ # {"format": "last_m_first", "confidence": "low"},
570
+ # {"format": "first.m.last", "confidence": "low"},
571
+ # {"format": "first_m_last", "confidence": "low"},
572
+ # {"format": "last-m-first", "confidence": "low"}
573
+ # ]
574
+ # }
575
+ def find_domain(domain: '', company_name: '')
576
+ # Validate that exactly one of domain or company_name is provided
577
+ if (domain.nil? || domain.empty?) && (company_name.nil? || company_name.empty?)
578
+ raise ArgumentError, "Either domain or company_name must be provided"
579
+ elsif !(domain.nil? || domain.empty?) && !(company_name.nil? || company_name.empty?)
580
+ raise ArgumentError, "Only one of domain or company_name can be provided"
441
581
  end
442
582
 
583
+ params = {}
584
+ params[:domain] = domain unless domain.nil? || domain.empty?
585
+ params[:company_name] = company_name unless company_name.nil? || company_name.empty?
586
+
443
587
  @@request.get('guessformat', params)
444
588
  end
445
589
 
data/zerobounce.gemspec CHANGED
@@ -29,6 +29,8 @@ Gem::Specification.new do |spec| # rubocop:disable Metrics/BlockLength
29
29
  spec.add_dependency 'rest-client', '~>2.1'
30
30
  spec.add_dependency 'dotenv'
31
31
 
32
+ spec.add_development_dependency 'base64' # stdlib gem on Ruby 3.4+
33
+ spec.add_development_dependency 'bigdecimal' # required by crack (webmock) on Ruby 3.4+
32
34
  spec.add_development_dependency 'bundler', '~> 2.4.6'
33
35
  spec.add_development_dependency 'pry', '~> 0.14.1'
34
36
  spec.add_development_dependency 'rake', '~> 13.0'
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: zerobounce-sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.2
4
+ version: 2.0.13
5
5
  platform: ruby
6
6
  authors:
7
7
  - Zero Bounce
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2025-08-28 00:00:00.000000000 Z
10
+ date: 2026-03-03 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: rest-client
@@ -38,6 +37,34 @@ dependencies:
38
37
  - - ">="
39
38
  - !ruby/object:Gem::Version
40
39
  version: '0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: base64
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ type: :development
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ - !ruby/object:Gem::Dependency
55
+ name: bigdecimal
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ type: :development
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
41
68
  - !ruby/object:Gem::Dependency
42
69
  name: bundler
43
70
  requirement: !ruby/object:Gem::Requirement
@@ -233,36 +260,44 @@ executables: []
233
260
  extensions: []
234
261
  extra_rdoc_files: []
235
262
  files:
236
- - ".env.sample"
263
+ - ".env.example"
264
+ - ".github/auto_assign.yml"
265
+ - ".github/workflows/auto_assign_ci.yaml"
266
+ - ".github/workflows/codeql.yml"
267
+ - ".github/workflows/sdk_ci.yml"
237
268
  - ".gitignore"
238
269
  - ".rspec"
239
270
  - ".ruby-version"
240
271
  - CHANGELOG.md
241
272
  - CODE_OF_CONDUCT.md
242
273
  - CONTRIBUTING.md
274
+ - Dockerfile
243
275
  - Gemfile
244
276
  - Gemfile.lock
245
277
  - LICENSE
246
278
  - LICENSE.txt
247
279
  - README.md
248
280
  - Rakefile
281
+ - SECURITY.md
249
282
  - bin/bundle
250
283
  - bin/console
251
284
  - bin/setup
252
285
  - data/zerobounce-ai-scoring.csv
253
286
  - data/zerobounce-batch-validation.csv
254
287
  - documentation.md
255
- - documentation_es.md
256
288
  - files/scoring.csv
257
289
  - files/validation.csv
258
290
  - files/zerobounce-ai-scoring.csv
259
291
  - files/zerobounce-batch-validation.csv
260
292
  - lib/zerobounce.rb
293
+ - lib/zerobounce/api_urls.rb
261
294
  - lib/zerobounce/base_request.rb
262
295
  - lib/zerobounce/configuration.rb
263
296
  - lib/zerobounce/error.rb
264
297
  - lib/zerobounce/mock_request.rb
265
298
  - lib/zerobounce/request.rb
299
+ - lib/zerobounce/validate_status.rb
300
+ - lib/zerobounce/validate_sub_status.rb
266
301
  - lib/zerobounce/version.rb
267
302
  - zerobounce.gemspec
268
303
  homepage: https://zerobounce.net
@@ -270,7 +305,6 @@ licenses:
270
305
  - MIT
271
306
  metadata:
272
307
  yard.run: yri
273
- post_install_message:
274
308
  rdoc_options: []
275
309
  require_paths:
276
310
  - lib
@@ -285,8 +319,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
285
319
  - !ruby/object:Gem::Version
286
320
  version: '0'
287
321
  requirements: []
288
- rubygems_version: 3.4.6
289
- signing_key:
322
+ rubygems_version: 3.6.6
290
323
  specification_version: 4
291
324
  summary: A Ruby client for Zerobounce.net.
292
325
  test_files: []
data/.env.sample DELETED
@@ -1,3 +0,0 @@
1
- ZEROBOUNCE_API_KEY=99e7ef20ceea4480a173b07b1be75371
2
- INCORRECT_API_KEY=thiskeyisinvalidorotherwiseincorrect
3
- TEST=unit