Skip to content

Latest commit

 

History

History
849 lines (735 loc) · 33.8 KB

README.md

File metadata and controls

849 lines (735 loc) · 33.8 KB

Zerobounce

Ruby client for Zerobounce.net API. Developed based on this version by Aaron Frase.

Security

  • Keep API keys on a trusted server. Do not embed them in mobile apps or browser JavaScript that untrusted users can inspect.
  • Custom API base URLs (when supported) must use https://. Do not pass end-user-controlled hosts into those settings.
  • Request URLs include api_key as a query parameter (ZeroBounce API contract). Do not log full request URLs or enable payload debug logging in production.

Usage

Add this line to your application's Gemfile:

gem 'zerobounce-sdk'

And then execute:

$bundle

Or install it yourself as:

$gem install zerobounce-sdk

Import

require 'zerobounce'

Configuration

Setting API Key

Method 1: Using configure block (recommended)

Zerobounce.configure do |config|
  config.apikey = '<zerobounce-api-key>'
  # Optional: Set custom API URLs
  config.api_root_url = 'https://api.zerobounce.net/v2'
  config.bulk_api_root_url = 'https://bulkapi.zerobounce.net/v2'
end

Method 1a: Using ApiUrls constants (convenience)

# Global API (default)
Zerobounce.configure do |config|
  config.apikey = '<zerobounce-api-key>'
  config.api_root_url = Zerobounce::ApiUrls::DEFAULT_URL
  config.bulk_api_root_url = Zerobounce::ApiUrls::BULK_DEFAULT_URL
end

# European API (convenience)
Zerobounce.configure do |config|
  config.apikey = '<zerobounce-api-key>'
  config.api_root_url = Zerobounce::ApiUrls::EU_URL
  config.bulk_api_root_url = Zerobounce::ApiUrls::BULK_DEFAULT_URL
end

# US API (convenience)
Zerobounce.configure do |config|
  config.apikey = '<zerobounce-api-key>'
  config.api_root_url = Zerobounce::ApiUrls::US_URL
  config.bulk_api_root_url = Zerobounce::ApiUrls::BULK_DEFAULT_URL
end

Method 2: Direct configuration

Zerobounce.config.apikey = '<zerobounce-api-key>'
# Optional: Set custom API URLs
Zerobounce.config.api_root_url = 'https://api.zerobounce.net/v2'
Zerobounce.config.bulk_api_root_url = 'https://bulkapi.zerobounce.net/v2'

Method 3: Using environment variables

Create a .env file in your project root:

ZEROBOUNCE_API_KEY=your_api_key_here
ZEROBOUNCE_API_URL=https://api.zerobounce.net/v2
ZEROBOUNCE_BULK_API_URL=https://bulkapi.zerobounce.net/v2

The gem will automatically load these environment variables when initialized. No additional configuration needed in your code.

Method 4: System environment variables

Set environment variables in your system:

export ZEROBOUNCE_API_KEY=your_api_key_here
export ZEROBOUNCE_API_URL=https://api.zerobounce.net/v2
export ZEROBOUNCE_BULK_API_URL=https://bulkapi.zerobounce.net/v2

API URL Configuration Details

Default Behavior

If you don't specify api_root_url or bulk_api_root_url, the gem will use the following defaults:

  • Main API: https://api.zerobounce.net/v2
  • Bulk API: https://bulkapi.zerobounce.net/v2

These defaults are defined in the Zerobounce::ApiUrls class constants:

  • Zerobounce::ApiUrls::DEFAULT_URL - Main API URL (Global)
  • Zerobounce::ApiUrls::EU_URL - European API URL
  • Zerobounce::ApiUrls::US_URL - US API URL
  • Zerobounce::ApiUrls::BULK_DEFAULT_URL - Bulk API URL

Using ApiUrls Constants

You can use the predefined constants in your configuration. The EU and US URLs are provided as convenience options:

# Global API (default)
Zerobounce.configure do |config|
  config.apikey = 'your-api-key'
  config.api_root_url = Zerobounce::ApiUrls::DEFAULT_URL
  config.bulk_api_root_url = Zerobounce::ApiUrls::BULK_DEFAULT_URL
end

# European API
Zerobounce.configure do |config|
  config.apikey = 'your-api-key'
  config.api_root_url = Zerobounce::ApiUrls::EU_URL
  config.bulk_api_root_url = Zerobounce::ApiUrls::BULK_DEFAULT_URL
end

# US API
Zerobounce.configure do |config|
  config.apikey = 'your-api-key'
  config.api_root_url = Zerobounce::ApiUrls::US_URL
  config.bulk_api_root_url = Zerobounce::ApiUrls::BULK_DEFAULT_URL
end

Custom API URLs

You can override the default URLs for custom deployments or testing:

Zerobounce.configure do |config|
  config.apikey = 'your-api-key'
  # Use custom URLs (e.g., for testing or custom deployments)
  config.api_root_url = 'https://custom-api.example.com/v2'
  config.bulk_api_root_url = 'https://custom-bulk-api.example.com/v2'
end

Environment Variable Priority

The configuration follows this priority order (highest to lowest):

  1. Explicitly set in code (config.api_root_url = '...')
  2. Environment variables (ZEROBOUNCE_API_URL, ZEROBOUNCE_BULK_API_URL)
  3. Default constants (ApiUrls::DEFAULT_URL, ApiUrls::BULK_DEFAULT_URL)

Credits

Zerobounce.credits
=> 79   # returns -1 for invalid key

Activity

Zerobounce.activity('ss@gmail.com')
=> {"found"=>true, "active_in_days"=>"180"}

Usage statistics

Zerobounce.api_usage(Date.today, Date.today)
=>
{"total"=>0,
 "status_valid"=>0,
 "status_invalid"=>0,
 "status_catch_all"=>0,
 "status_do_not_mail"=>0,
 "status_spamtrap"=>0,
 "status_unknown"=>0,
 "sub_status_toxic"=>0,
 "sub_status_disposable"=>0,
 "sub_status_role_based"=>0,
 "sub_status_possible_trap"=>0,
 "sub_status_global_suppression"=>0,
 "sub_status_timeout_exceeded"=>0,
 "sub_status_mail_server_temporary_error"=>0,
 "sub_status_mail_server_did_not_respond"=>0,
 "sub_status_greylisted"=>0,
 "sub_status_antispam_system"=>0,
 "sub_status_does_not_accept_mail"=>0,
 "sub_status_exception_occurred"=>0,
 "sub_status_failed_syntax_check"=>0,
 "sub_status_mailbox_not_found"=>0,
 "sub_status_unroutable_ip_address"=>0,
 "sub_status_possible_typo"=>0,
 "sub_status_no_dns_entries"=>0,
 "sub_status_role_based_catch_all"=>0,
 "sub_status_mailbox_quota_exceeded"=>0,
 "sub_status_forcible_disconnect"=>0,
 "sub_status_failed_smtp_connection"=>0,
 "sub_status_accept_all"=>0,
 "sub_status_mx_forward"=>0,
 "sub_status_alternate"=>0,
 "sub_status_allowed"=>0,
 "sub_status_blocked"=>0,
 "sub_status_gold"=>0,
 "sub_status_role_based_accept_all"=>0,
 "start_date"=>"4/28/2023",
 "end_date"=>"4/28/2023"}

Email Validation

Validate single eMail

Zerobounce.validate('valid@example.com')
# Zerobounce.validate('valid@example.com', '192.168.0.1') # optional IP address
=>
{"address"=>"valid@example.com",
 "status"=>"valid",
 "sub_status"=>"",
 "free_email"=>false,
 "did_you_mean"=>nil,
 "account"=>nil,
 "domain"=>nil,
 "domain_age_days"=>"9692",
 "smtp_provider"=>"example",
 "mx_found"=>"true",
 "mx_record"=>"mx.example.com",
 "firstname"=>"zero",
 "lastname"=>"bounce",
 "gender"=>"male",
 "country"=>nil,
 "region"=>nil,
 "city"=>nil,
 "zipcode"=>nil,
 "processed_at"=>"2023-04-28 15:18:45.631"}

Validate batch

emails
=>
["disposable@example.com",
 "invalid@example.com",
 "valid@example.com",
 "toxic@example.com",
 "donotmail@example.com",
 "spamtrap@example.com"]

# ip_addresses
=> ["192.168.0.1", "192.168.0.1", "192.168.0.1", "192.168.0.1", "192.168.0.1", "192.168.0.1"]
# Zerobounce.validate_batch(emails, ip_addresses) # optional ip_addresses parameter

Zerobounce.validate_batch(emails)
=>
[{"address"=>"disposable@example.com",
  "status"=>"do_not_mail",
  "sub_status"=>"disposable",
  "free_email"=>false,
  "did_you_mean"=>nil,
  "account"=>nil,
  "domain"=>nil,
  "domain_age_days"=>"9692",
  "smtp_provider"=>"example",
  "mx_found"=>"true",
  "mx_record"=>"mx.example.com",
  "firstname"=>"zero",
  "lastname"=>"bounce",
  "gender"=>"male",
  "country"=>nil,
  "region"=>nil,
  "city"=>nil,
  "zipcode"=>nil,
  "processed_at"=>"2023-04-28 15:21:44.340"},
 {"address"=>"invalid@example.com",
  "status"=>"invalid",
  "sub_status"=>"mailbox_not_found",
  "free_email"=>false,
  "did_you_mean"=>nil,
  "account"=>nil,
  "domain"=>nil,
  "domain_age_days"=>"9692",
  "smtp_provider"=>"example",
  "mx_found"=>"true",
  "mx_record"=>"mx.example.com",
  "firstname"=>"zero",
  "lastname"=>"bounce",
  "gender"=>"male",
  "country"=>nil,
  "region"=>nil,
  "city"=>nil,
  "zipcode"=>nil,
  "processed_at"=>"2023-04-28 15:21:44.340"},
 {"address"=>"valid@example.com",
  "status"=>"valid",
  "sub_status"=>"",
  "free_email"=>false,
  "did_you_mean"=>nil,
  "account"=>nil,
  "domain"=>nil,
  "domain_age_days"=>"9692",
  "smtp_provider"=>"example",
  "mx_found"=>"true",
  "mx_record"=>"mx.example.com",
  "firstname"=>"zero",
  "lastname"=>"bounce",
  "gender"=>"male",
  "country"=>nil,
  "region"=>nil,
  "city"=>nil,
  "zipcode"=>nil,
  "processed_at"=>"2023-04-28 15:21:44.340"},
 {"address"=>"toxic@example.com",
  "status"=>"do_not_mail",
  "sub_status"=>"toxic",
  "free_email"=>false,
  "did_you_mean"=>nil,
  "account"=>nil,
  "domain"=>nil,
  "domain_age_days"=>"9692",
  "smtp_provider"=>"example",
  "mx_found"=>"true",
  "mx_record"=>"mx.example.com",
  "firstname"=>"zero",
  "lastname"=>"bounce",
  "gender"=>"male",
  "country"=>nil,
  "region"=>nil,
  "city"=>nil,
  "zipcode"=>nil,
  "processed_at"=>"2023-04-28 15:21:44.340"},
 {"address"=>"donotmail@example.com",
  "status"=>"do_not_mail",
  "sub_status"=>"role_based",
  "free_email"=>false,
  "did_you_mean"=>nil,
  "account"=>nil,
  "domain"=>nil,
  "domain_age_days"=>"9692",
  "smtp_provider"=>"example",
  "mx_found"=>"true",
  "mx_record"=>"mx.example.com",
  "firstname"=>"zero",
  "lastname"=>"bounce",
  "gender"=>"male",
  "country"=>nil,
  "region"=>nil,
  "city"=>nil,
  "zipcode"=>nil,
  "processed_at"=>"2023-04-28 15:21:44.340"},
 {"address"=>"spamtrap@example.com",
  "status"=>"spamtrap",
  "sub_status"=>"",
  "free_email"=>false,
  "did_you_mean"=>nil,
  "account"=>nil,
  "domain"=>nil,
  "domain_age_days"=>"9692",
  "smtp_provider"=>"example",
  "mx_found"=>"true",
  "mx_record"=>"mx.example.com",
  "firstname"=>"zero",
  "lastname"=>"bounce",
  "gender"=>"male",
  "country"=>nil,
  "region"=>nil,
  "city"=>nil,
  "zipcode"=>nil,
  "processed_at"=>"2023-04-28 15:21:44.340"}]

Batch File Validation

Send file

# sample file can be found in the files folder of this repository
 validate_file_path = File.join(Dir.pwd, 'files', 'validation.csv')
=> "/home/zb/wrappers/ruby-test/files/validation.csv"
 Zerobounce.validate_file_send(validate_file_path)
=>
{"success"=>true,
 "message"=>"File Accepted",
 "file_name"=>"validation.csv",
 "file_id"=>"75d854a6-565c-49f9-b4c8-b3344480ec4c"}
 # file_id will be required for next steps
 # optional named parameters:
    email_address_column: 1,
    first_name_column: 2,
    last_name_column: 3,
    gender_column: 4,
    has_header_row: true,
    return_url: nil,        # results callback url
    allow_phase_2: true      # optional; validation bulk sendfile only (omit or nil to skip)
 # Zerobounce.validate_file_send(validate_file_path, email_address_column: 1, gender_column: 4)

Bulk validation uses https://bulkapi.zerobounce.net/v2. See v2 send file, v2 file status, and v2 get file.

Check file

file_id = "75d854a6-565c-49f9-b4c8-b3344480ec4c"
=> "75d854a6-565c-49f9-b4c8-b3344480ec4c"
Zerobounce.validate_file_check(file_id)
=>
{"success"=>true,
 "file_id"=>"75d854a6-565c-49f9-b4c8-b3344480ec4c",
 "file_name"=>"validation.csv",
 "upload_date"=>"2023-04-28T15:25:41Z",
 "file_status"=>"Greylisted",
 "complete_percentage"=>"0%",
 "file_phase_2_status"=>"N/A",  # when present (validation bulk v2)
 "error_reason"=>nil,
 "return_url"=>nil}

Get file

file_id = "75d854a6-565c-49f9-b4c8-b3344480ec4c"
=> "75d854a6-565c-49f9-b4c8-b3344480ec4c"
Zerobounce.validate_file_get(file_id)
=> "\"email\",\"first\",\"last\",\"gender\",\"ip\",\"ZB Status\",\"ZB Sub Status\",\"ZB Account\",\"ZB Domain\",\"ZB First Name\",\"ZB Last Name\",\"ZB Gender\",\"ZB Free Email\",\"ZB MX Found\",\"ZB MX Record\",\"ZB SMTP Provider\",\"ZB Did You Mean\"\n\"disposable@example.com\",\"First\",\"Last\",\"male\",\"127.0.0.1\",\"do_not_mail\",\"disposable\",\"\",\"\",\"zero\",\"bounce\",\"male\",\"False\",\"true\",\"mx.example.com\",\"example\",\"\"\n\"invalid@example.com\",\"First\",\"Last\",\"male\",\"127.0.0.1\",\"invalid\",\"mailbox_not_found\",\"\",\"\",\"zero\",\"bounce\",\"male\",\"False\",\"true\",\"mx.example.com\",\"example\",\"\"\n\"valid@example.com\",\"First\",\"Last\",\"male\",\"127.0.0.1\",\"valid\",\"\",\"\",\"\",\"zero\",\"bounce\",\"male\",\"False\",\"true\",\"mx.example.com\",\"example\",\"\"\n\"toxic@example.com\",\"First\",\"Last\",\"male\",\"127.0.0.1\",\"do_not_mail\",\"toxic\",\"\",\"\",\"zero\",\"bounce\",\"male\",\"False\",\"true\",\"mx.example.com\",\"example\",\"\"\n\"donotmail@example.com\",\"First\",\"Last\",\"male\",\"127.0.0.1\",\"do_not_mail\",\"role_based\",\"\",\"\",\"zero\",\"bounce\",\"male\",\"False\",\"true\",\"mx.example.com\",\"example\",\"\"\n\"spamtrap@example.com\",\"First\",\"Last\",\"male\",\"127.0.0.1\",\"spamtrap\",\"\",\"\",\"\",\"zero\",\"bounce\",\"male\",\"False\",\"true\",\"mx.example.com\",\"example\",\"\"\n\"abuse@example.com\",\"First\",\"Last\",\"male\",\"127.0.0.1\",\"abuse\",\"\",\"\",\"\",\"zero\",\"bounce\",\"male\",\"False\",\"true\",\"mx.example.com\",\"example\",\"\"\n\"unknown@example.com\",\"First\",\"Last\",\"male\",\"127.0.0.1\",\"unknown\",\"mail_server_temporary_error\",\"\",\"\",\"zero\",\"bounce\",\"male\",\"False\",\"true\",\"mx.example.com\",\"example\",\"\"\n\"catch_all@example.com\",\"First\",\"Last\",\"male\",\"127.0.0.1\",\"catch-all\",\"\",\"\",\"\",\"zero\",\"bounce\",\"male\",\"False\",\"true\",\"mx.example.com\",\"example\",\"\"\n\"antispam_system@example.com\",\"First\",\"Last\",\"male\",\"127.0.0.1\",\"unknown\",\"antispam_system\",\"\",\"\",\"zero\",\"bounce\",\"male\",\"False\",\"true\",\"mx.example.com\",\"example\",\"\"\n\"does_not_accept_mail@example.com\",\"First\",\"Last\",\"male\",\"127.0.0.1\",\"invalid\",\"does_not_accept_mail\",\"\",\"\",\"zero\",\"bounce\",\"male\",\"False\",\"true\",\"mx.example.com\",\"example\",\"\"\n\"exception_occurred@example.com\",\"First\",\"Last\",\"male\",\"127.0.0.1\",\"unknown\",\"exception_occurred\",\"\",\"\",\"zero\",\"bounce\",\"male\",\"False\",\"true\",\"mx.example.com\",\"example\",\"\"\n\"failed_smtp_connection@example.com\",\"First\",\"Last\",\"male\",\"127.0.0.1\",\"unknown\",\"failed_smtp_connection\",\"\",\"\",\"zero\",\"bounce\",\"male\",\"False\",\"true\",\"mx.example.com\",\"example\",\"\"\n\"failed_syntax_check@example.com\",\"First\",\"Last\",\"male\",\"127.0.0.1\",\"invalid\",\"failed_syntax_check\",\"\",\"\",\"zero\",\"bounce\",\"male\",\"False\",\"true\",\"mx.example.com\",\"example\",\"\"\n\"forcible_disconnect@example.com\",\"First\",\"Last\",\"male\",\"127.0.0.1\",\"unknown\",\"forcible_disconnect\",\"\",\"\",\"zero\",\"bounce\",\"male\",\"False\",\"true\",\"mx.example.com\",\"example\",\"\"\n\"global_suppression@example.com\",\"First\",\"Last\",\"male\",\"127.0.0.1\",\"do_not_mail\",\"global_suppression\",\"\",\"\",\"zero\",\"bounce\",\"male\",\"False\",\"true\",\"mx.example.com\",\"example\",\"\"\n\"greylisted@example.com\",\"First\",\"Last\",\"male\",\"127.0.0.1\",\"unknown\",\"greylisted\",\"\",\"\",\"zero\",\"bounce\",\"male\",\"False\",\"true\",\"mx.example.com\",\"example\",\"\"\n\"leading_period_removed@example.com\",\"First\",\"Last\",\"male\",\"127.0.0.1\",\"valid\",\"leading_period_removed\",\"\",\"\",\"zero\",\"bounce\",\"male\",\"False\",\"true\",\"mx.example.com\",\"example\",\"\"\n\"mail_server_did_not_respond@example.com\",\"First\",\"Last\",\"male\",\"127.0.0.1\",\"unknown\",\"mail_server_did_not_respond\",\"\",\"\",\"zero\",\"bounce\",\"male\",\"False\",\"true\",\"mx.example.com\",\"example\",\"\"\n\"mail_server_temporary_error@example.com\",\"First\",\"Last\",\"male\",\"127.0.0.1\",\"unknown\",\"mail_server_temporary_error\",\"\",\"\",\"zero\",\"bounce\",\"male\",\"False\",\"true\",\"mx.example.com\",\"example\",\"\"\n\"mailbox_quota_exceeded@example.com\",\"First\",\"Last\",\"male\",\"127.0.0.1\",\"invalid\",\"mailbox_quota_exceeded\",\"\",\"\",\"zero\",\"bounce\",\"male\",\"False\",\"true\",\"mx.example.com\",\"example\",\"\"\n\"mailbox_not_found@example.com\",\"First\",\"Last\",\"male\",\"127.0.0.1\",\"invalid\",\"mailbox_not_found\",\"\",\"\",\"zero\",\"bounce\",\"male\",\"False\",\"true\",\"mx.example.com\",\"example\",\"\"\n\"no_dns_entries@example.com\",\"First\",\"Last\",\"male\",\"127.0.0.1\",\"invalid\",\"no_dns_entries\",\"\",\"\",\"zero\",\"bounce\",\"male\",\"False\",\"true\",\"mx.example.com\",\"example\",\"\"\n\"possible_trap@example.com\",\"First\",\"Last\",\"male\",\"127.0.0.1\",\"do_not_mail\",\"possible_trap\",\"\",\"\",\"zero\",\"bounce\",\"male\",\"False\",\"true\",\"mx.example.com\",\"example\",\"\"\n\"possible_typo@example.com\",\"First\",\"Last\",\"male\",\"127.0.0.1\",\"invalid\",\"possible_typo\",\"\",\"\",\"zero\",\"bounce\",\"male\",\"False\",\"true\",\"mx.example.com\",\"example\",\"\"\n\"role_based@example.com\",\"First\",\"Last\",\"male\",\"127.0.0.1\",\"do_not_mail\",\"role_based\",\"\",\"\",\"zero\",\"bounce\",\"male\",\"False\",\"true\",\"mx.example.com\",\"example\",\"\"\n\"timeout_exceeded@example.com\",\"First\",\"Last\",\"male\",\"127.0.0.1\",\"unknown\",\"timeout_exceeded\",\"\",\"\",\"zero\",\"bounce\",\"male\",\"False\",\"true\",\"mx.example.com\",\"example\",\"\"\n\"unroutable_ip_address@example.com\",\"First\",\"Last\",\"male\",\"127.0.0.1\",\"invalid\",\"unroutable_ip_address\",\"\",\"\",\"zero\",\"bounce\",\"male\",\"False\",\"true\",\"mx.example.com\",\"example\",\"\"\n\"free_email@example.com\",\"First\",\"Last\",\"male\",\"127.0.0.1\",\"valid\",\"\",\"\",\"\",\"zero\",\"bounce\",\"male\",\"True\",\"true\",\"mx.example.com\",\"example\",\"\"\n\"role_based_catch_all@example.com\",\"First\",\"Last\",\"male\",\"127.0.0.1\",\"do_not_mail\",\"role_based_catch_all\",\"\",\"\",\"zero\",\"bounce\",\"male\",\"False\",\"true\",\"mx.example.com\",\"example\",\"\"\n"

Optional v2 get file query parameters use Zerobounce::GetFileOptions and Zerobounce::DownloadType (PHASE_1, PHASE_2, COMBINED). Set activity_data for validation validate_file_get only; it is not sent for scoring_file_get.

opts = Zerobounce::GetFileOptions.new(
  download_type: Zerobounce::DownloadType::COMBINED,
  activity_data: true
)
csv = Zerobounce.validate_file_get(file_id, opts)

Non-success HTTP responses or JSON error bodies (including some HTTP 200 responses with "success": false) raise RuntimeError with the API message. To inspect a raw body string, use Zerobounce.get_file_json_indicates_error?(body).

Delete file

file_id = "75d854a6-565c-49f9-b4c8-b3344480ec4c"
=> "75d854a6-565c-49f9-b4c8-b3344480ec4c"
Zerobounce.validate_file_delete(file_id)
=>
{"success"=>true,
 "message"=>"File Deleted",
 "file_name"=>"validation.csv",
 "file_id"=>"75d854a6-565c-49f9-b4c8-b3344480ec4c"}

AI Scoring

Send file

scoring_file_path = File.join(Dir.pwd, 'files', 'scoring.csv')
=> "/home/odin/zb/wrappers/ruby-test/files/scoring.csv"
Zerobounce.scoring_file_send(scoring_file_path)
=>
{"success"=>true,
 "message"=>"File Accepted",
 "file_name"=>"scoring.csv",
 "file_id"=>"89fb7262-b845-4fa1-aa25-e486347ec64e"}
# file_id will be required for the next steps
# optional named parameters
    email_address_column: 1,
    has_header_row: true,
    return_url: nil         # results callback url
# Zerobounce.scoring_file_send(scoring_file_path, email_address_column: 1)

File check

file_id = "89fb7262-b845-4fa1-aa25-e486347ec64e"
=> "89fb7262-b845-4fa1-aa25-e486347ec64e"
Zerobounce.scoring_file_check(file_id)
=>
{"success"=>true,
 "file_id"=>"89fb7262-b845-4fa1-aa25-e486347ec64e",
 "file_name"=>"scoring.csv",
 "upload_date"=>"2023-04-28T16:57:18Z",
 "file_status"=>"Complete",
 "complete_percentage"=>"100% Complete.",
 "return_url"=>nil}

Get file

file_id = "89fb7262-b845-4fa1-aa25-e486347ec64e"
=> "89fb7262-b845-4fa1-aa25-e486347ec64e"
Zerobounce.scoring_file_get(file_id)
=> "\"email\",\"ZeroBounceQualityScore\"\r\n\"disposable@example.com\",\"0\"\r\n\"invalid@example.com\",\"10\"\r\n\"valid@example.com\",\"10\"\r\n\"toxic@example.com\",\"2\"\r\n\"donotmail@example.com\",\"0\"\r\n\"spamtrap@example.com\",\"0\"\r\n\"abuse@example.com\",\"10\"\r\n\"unknown@example.com\",\"10\"\r\n\"catch_all@example.com\",\"10\"\r\n\"antispam_system@example.com\",\"0\"\r\n\"does_not_accept_mail@example.com\",\"0\"\r\n\"exception_occurred@example.com\",\"0\"\r\n\"failed_smtp_connection@example.com\",\"0\"\r\n\"failed_syntax_check@example.com\",\"0\"\r\n\"forcible_disconnect@example.com\",\"0\"\r\n\"global_suppression@example.com\",\"0\"\r\n\"greylisted@example.com\",\"0\"\r\n\"leading_period_removed@example.com\",\"0\"\r\n\"mail_server_did_not_respond@example.com\",\"0\"\r\n\"mail_server_temporary_error@example.com\",\"0\"\r\n\"mailbox_quota_exceeded@example.com\",\"0\"\r\n\"mailbox_not_found@example.com\",\"0\"\r\n\"no_dns_entries@example.com\",\"0\"\r\n\"possible_trap@example.com\",\"0\"\r\n\"possible_typo@example.com\",\"0\"\r\n\"role_based@example.com\",\"0\"\r\n\"timeout_exceeded@example.com\",\"0\"\r\n\"unroutable_ip_address@example.com\",\"0\"\r\n\"free_email@example.com\",\"0\"\r\n\"role_based_catch_all@example.com\",\"0\""
# Optional second argument: GetFileOptions with download_type only (activity_data is not sent for scoring getfile)
# Zerobounce.scoring_file_get(file_id, Zerobounce::GetFileOptions.new(download_type: Zerobounce::DownloadType::PHASE_2))

Delete file

file_id = "89fb7262-b845-4fa1-aa25-e486347ec64e"
=> "89fb7262-b845-4fa1-aa25-e486347ec64e"
Zerobounce.scoring_file_delete(file_id)
=> {"success"=>false, "message"=>"File cannot be found."}

More details on CSV file submission and CSV result retrieval

This applies for both 'Email validation' and 'AI Scoring' functionalities.

The data of the submitted CSV file will be included in the result CSV file. Therefore, the first columns of the result CSV file will consist of the submitted data and the following columns will provide information specific to each functionality.

'Email validation'

The additional columns that would be included in result CSV file:

  • ZB Status
  • ZB Sub Status
  • ZB Account
  • ZB Domain
  • ZB First Name
  • ZB Last Name
  • ZB Gender
  • ZB Free Email
  • ZB MX Found
  • ZB MX Record
  • ZB SMTP Provider
  • ZB Did You Mean
  • ZB City
  • ZB Region/State
  • ZB Zip Code
  • ZB Country

When has_header_row: true is provided to validate_file_send() method, original columns of the submitted file will be retained.

When has_header_row: false, the columns that were explicitly specified in validate_file_send() will be given the following column title:

email_address_column    => "Email Address"
first_name_column       => "First Name"
last_name_column        => "Last Name"
gender_column           => "Gender"

Any additional that were not (or can not be) specified by parameters, will be given column title "Custom".

'AI Scoring':

The additional column that would be included in result CSV file:

  • ZeroBounceQualityScore

When has_header_row: true is provided to scoring_file_send() method, original columns of the submitted file will be retained.

When has_header_row: false is provided to scoring_file_send() method, column title for the email column (as well as the other columns) will be Email Address.

Email Finder

Guess Format by Domain

# New keyword argument syntax (recommended)
Zerobounce.guessformat(domain: "zerobounce.net")
=>
{"email"=>"",
 "domain"=>"zerobounce.net",
 "format"=>"first.last",
 "status"=>"",
 "sub_status"=>"",
 "confidence"=>"high",
 "did_you_mean"=>"",
 "failure_reason"=>"",
 "other_domain_formats"=>
  [{"format"=>"first", "confidence"=>"high"},
   {"format"=>"last.first", "confidence"=>"high"},
   {"format"=>"lfirst", "confidence"=>"high"},
   {"format"=>"lastfirst", "confidence"=>"high"},
   {"format"=>"firstl", "confidence"=>"high"},
   {"format"=>"last", "confidence"=>"medium"},
   {"format"=>"first.middle.last", "confidence"=>"medium"},
   {"format"=>"first-last", "confidence"=>"medium"},
   {"format"=>"l.first", "confidence"=>"medium"},
   {"format"=>"f.last", "confidence"=>"medium"},
   {"format"=>"f-last", "confidence"=>"medium"},
   {"format"=>"first.l", "confidence"=>"medium"},
   {"format"=>"first-l", "confidence"=>"medium"},
   {"format"=>"firstlast", "confidence"=>"medium"},
   {"format"=>"first_l", "confidence"=>"medium"},
   {"format"=>"f_last", "confidence"=>"medium"},
   {"format"=>"last.f", "confidence"=>"medium"},
   {"format"=>"last-f", "confidence"=>"medium"},
   {"format"=>"last-first", "confidence"=>"medium"},
   {"format"=>"first_last", "confidence"=>"medium"},
   {"format"=>"last_f", "confidence"=>"medium"},
   {"format"=>"last_first", "confidence"=>"medium"},
   {"format"=>"flast", "confidence"=>"medium"},
   {"format"=>"lastf", "confidence"=>"medium"},
   {"format"=>"l-first", "confidence"=>"low"},
   {"format"=>"l_first", "confidence"=>"low"}]}

# With names for better accuracy (new syntax)
Zerobounce.guessformat(domain: "zerobounce.net", first_name: "John", middle_name: 'Deere', last_name: "Doe")

# Backwards compatible syntax (still supported)
Zerobounce.guessformat("zerobounce.net")
Zerobounce.guessformat("zerobounce.net", first_name: "John", middle_name: 'Deere', last_name: "Doe")

Guess Format by Company Name

# New keyword argument syntax (recommended)
Zerobounce.guessformat(company_name: "Zero Bounce")
=>
{"email"=>"",
 "company_name"=>"Zero Bounce",
 "format"=>"first.last",
 "status"=>"",
 "sub_status"=>"",
 "confidence"=>"high",
 "did_you_mean"=>"",
 "failure_reason"=>"",
 "other_domain_formats"=>[...]}

# With names for better accuracy (new syntax)
Zerobounce.guessformat(first_name: "John", last_name: "Doe", company_name: "Zero Bounce")

Find Email Address

# Find email by domain
Zerobounce.find_email("John", domain: "zerobounce.net")
=>
{
  "email": "john@zerobounce.net",
  "email_confidence": "medium",
  "domain": "zerobounce.net",
  "company_name": "ZeroBounce",
  "did_you_mean": "",
  "failure_reason": ""
}

# Find email by company name
Zerobounce.find_email("John", company_name: "Zero Bounce")
=>
{
  "email": "john@zerobounce.net",
  "email_confidence": "medium",
  "domain": "zerobounce.net",
  "company_name": "ZeroBounce",
  "did_you_mean": "",
  "failure_reason": ""
}

# With additional name information for better accuracy
Zerobounce.find_email("John", domain: "zerobounce.net", middle_name: "Deere", last_name: "Doe")

Find Domain Information

# Find domain format by domain
Zerobounce.find_domain(domain: "zerobounce.net")
=>
{
  "domain": "zerobounce.net",
  "company_name": "Hertza, LLC",
  "format": "first.last",
  "confidence": "high",
  "did_you_mean": "",
  "failure_reason": "",
  "other_domain_formats": [
    {"format": "first", "confidence": "high"},
    {"format": "last.first", "confidence": "high"},
    {"format": "lastfirst", "confidence": "high"},
    {"format": "firstl", "confidence": "high"},
    {"format": "lfirst", "confidence": "high"},
    {"format": "firstlast", "confidence": "high"},
    {"format": "last_middle_f", "confidence": "high"},
    {"format": "last", "confidence": "high"},
    {"format": "f.last", "confidence": "medium"},
    {"format": "last-f", "confidence": "medium"},
    {"format": "l.first", "confidence": "medium"},
    {"format": "last_f", "confidence": "medium"},
    {"format": "first.middle.last", "confidence": "medium"},
    {"format": "first-last", "confidence": "medium"},
    {"format": "last.f", "confidence": "medium"},
    {"format": "last_first", "confidence": "medium"},
    {"format": "f-last", "confidence": "medium"},
    {"format": "first.l", "confidence": "medium"},
    {"format": "first-l", "confidence": "medium"},
    {"format": "first_l", "confidence": "medium"},
    {"format": "first_last", "confidence": "medium"},
    {"format": "f_last", "confidence": "medium"},
    {"format": "last-first", "confidence": "medium"},
    {"format": "flast", "confidence": "medium"},
    {"format": "lastf", "confidence": "medium"},
    {"format": "l_first", "confidence": "medium"},
    {"format": "l-first", "confidence": "medium"},
    {"format": "first-middle-last", "confidence": "low"},
    {"format": "firstmlast", "confidence": "low"},
    {"format": "last.middle.first", "confidence": "low"},
    {"format": "last_middle_first", "confidence": "low"},
    {"format": "first_middle_last", "confidence": "low"},
    {"format": "last-middle-first", "confidence": "low"},
    {"format": "first-m-last", "confidence": "low"},
    {"format": "firstmiddlelast", "confidence": "low"},
    {"format": "last.m.first", "confidence": "low"},
    {"format": "lastmfirst", "confidence": "low"},
    {"format": "lastmiddlefirst", "confidence": "low"},
    {"format": "last_m_first", "confidence": "low"},
    {"format": "first.m.last", "confidence": "low"},
    {"format": "first_m_last", "confidence": "low"},
    {"format": "last-m-first", "confidence": "low"}
  ]
}

# Find domain format by company name
Zerobounce.find_domain(company_name: "Zero Bounce")
=>
{
  "domain": "zerobounce.net",
  "company_name": "Zero Bounce",
  "format": "first.last",
  "confidence": "high",
  "did_you_mean": "",
  "failure_reason": "",
  "other_domain_formats": [...]
}

Warmup API

The Warmup API is a separate product. It has its own host, its own team-scoped key, and its own client; your validation key will not work with it.

warmup = Zerobounce::Warmup.new('<YOUR_WARMUP_API_KEY>')

warmup.list_sending_accounts
warmup.progress_today
warmup.download_seeds          # CSV text, columns seed,provider

Add a from-address you run yourself, with one of the five standard engagement rules (0 Default, 1 Slow Pace, 2 Repair, 3 Boost, 4 Protect):

warmup.add_sending_account('sender@example.com', 'self_managed', engagement_rule_id: 0)

Or have ZeroBounce provision the mailbox. A 200 means the request was accepted; provisioning happens afterwards.

warmup.add_sending_account(
  'sender@example.com', 'zb_managed',
  email_content: { types_of_emails: %w[newsletter marketing] },
  managed_sending: { timezone_offset: -5, warmup_start_date: Date.new(2026, 3, 4) }
)

A custom engagement rule can be changed only once every seven days; inside that window the API answers 429.

warmup.create_custom_engagement('sender@example.com', 20, 20, 35)  # reply, important, click %

check_domain_dmarc takes an email address, not a domain — the live API rejects a bare domain even though the docs describe one:

warmup.check_domain_dmarc('sender@example.com')

Every documented empty success (204, or an empty 200) comes back as nil, and a payload that is not a JSON object — /seeds is an array, /seed-count a bare number — comes back as the parsed value itself. Failures raise Zerobounce::ApiResponseError, whose #status_code gives the HTTP status. Nothing is stored by test_smtp_configuration, and the credentials you pass it are masked out of any error it raises.

Development

Local setup

sudo apt install -y rbenv
git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build
rbenv install 3.2.1
rbenv global 3.2.1
rbenv rehash
ruby -v
gem install bundler -v "~>2.4.6"
bundle install

Run tests with Docker

From the sdk-docs/ folder in the SDKs monorepo:

cd sdk-docs
docker compose build ruby
docker compose run --rm ruby

Or from this directory:

docker build -t zb-ruby-sdk .
docker run --rm zb-ruby-sdk

Run tests (local)

bundle exec rspec

You should see an output like this

running tests
.....................................................

Finished in 6.81 seconds (files took 0.40587 seconds to load)
53 examples, 0 failures

Test parameters

The tests use the following environment parameter:

  • ZEROBOUNCE_API_KEY – Your API key; used in mock tests as the valid key sample (any value will work for mock tests).

An invalid API key for error-handling tests is hardcoded in the spec; no env var is required.

To set your key:

export ZEROBOUNCE_API_KEY=your_api_key_here

A .env.example file is provided.

Tests use webmock and vcr for mocking HTTP requests. This means that actual requests were made and recorded in the spec/cassettes with an (at the time) valid API key used for testing purposes. This key has been invalidated in the meantime, however it is provided in the .env.example file for the mock tests to work. If you do not wish to use this key for mocks, you can replace it with any value in the .yml files under spec/cassettes or delete all of them and rerun the tests so that vcr records them with a new key.

Live API suite

rspec never touches the network: the live suite lives outside spec/, so CI does not even load it. It needs real keys, which come from the environment and are never written anywhere:

export ZEROBOUNCE_API_KEY=...          # validation API
export ZEROBOUNCE_WARMUP_API_KEY=...   # Warmup API
ruby -Ilib live/live_api.rb

By default it only makes free calls: sandbox addresses, reads, and requests that are expected to fail but still prove the API accepts the payload. It checks that the credit balance did not move. Two flags opt in to more:

Flag What it adds
ZEROBOUNCE_LIVE_SPEND=1 billable calls (scoring, activity, email finder) and a bulk round trip that deletes its own job
ZEROBOUNCE_LIVE_CHANGES=1 reversible account changes — a test filter and a self-managed Warmup sending account — each undone in cleanup even when a check fails

It never creates an Inbox Placement test, sets the Inbox Placement webhook, or starts ZB-managed Warmup onboarding, because the API cannot undo any of those.

Run it by hand before every release: the offline suite proves the SDK sends what we think it sends, and only the live suite proves the API accepts it.

Publish

  1. Bump VERSION in lib/zerobounce/version.rb, commit, tag (vX.Y.Z), push tag.
  2. Actions → Publish → Run workflow with that tag.

Registry: zerobounce-sdk on RubyGems