├── lib ├── kickbox │ ├── error.rb │ ├── error │ │ └── client_error.rb │ ├── client.rb │ ├── http_client │ │ ├── response.rb │ │ ├── response_handler.rb │ │ ├── request_handler.rb │ │ ├── error_handler.rb │ │ └── auth_handler.rb │ ├── api │ │ └── kickbox.rb │ └── http_client.rb └── kickbox.rb ├── .gitignore ├── kickbox.gemspec └── README.md /lib/kickbox/error.rb: -------------------------------------------------------------------------------- 1 | require "kickbox/error/client_error" 2 | 3 | module Kickbox 4 | 5 | module Error 6 | 7 | end 8 | 9 | end 10 | -------------------------------------------------------------------------------- /lib/kickbox.rb: -------------------------------------------------------------------------------- 1 | require "rubygems" 2 | 3 | require "kickbox/client" 4 | require "kickbox/error" 5 | require "kickbox/http_client" 6 | 7 | module Kickbox 8 | end 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | .yardoc 6 | Gemfile.lock 7 | InstalledFiles 8 | _yardoc 9 | coverage 10 | doc/ 11 | lib/bundler/man 12 | pkg 13 | rdoc 14 | spec/reports 15 | test/tmp 16 | test/version_tmp 17 | tmp 18 | -------------------------------------------------------------------------------- /lib/kickbox/error/client_error.rb: -------------------------------------------------------------------------------- 1 | module Kickbox 2 | 3 | module Error 4 | 5 | class ClientError < ::StandardError 6 | 7 | attr_reader :code 8 | 9 | def initialize(message, code) 10 | @code = code 11 | super message 12 | end 13 | 14 | end 15 | 16 | end 17 | 18 | end 19 | -------------------------------------------------------------------------------- /lib/kickbox/client.rb: -------------------------------------------------------------------------------- 1 | require "faraday" 2 | require "json" 3 | 4 | require "kickbox/api/kickbox" 5 | 6 | module Kickbox 7 | 8 | class Client 9 | 10 | def initialize(auth = {}, options = {}) 11 | @http_client = Kickbox::HttpClient::HttpClient.new(auth, options) 12 | end 13 | 14 | # 15 | def kickbox() 16 | Kickbox::Api::Kickbox.new(@http_client) 17 | end 18 | 19 | end 20 | 21 | end 22 | -------------------------------------------------------------------------------- /lib/kickbox/http_client/response.rb: -------------------------------------------------------------------------------- 1 | module Kickbox 2 | 3 | module HttpClient 4 | 5 | # Response object contains the response returned by the client 6 | class Response 7 | 8 | attr_accessor :body, :code, :headers 9 | 10 | def initialize(body, code, headers) 11 | @body = body 12 | @code = code 13 | @headers = headers 14 | end 15 | 16 | end 17 | 18 | end 19 | 20 | end 21 | -------------------------------------------------------------------------------- /lib/kickbox/http_client/response_handler.rb: -------------------------------------------------------------------------------- 1 | module Kickbox 2 | 3 | module HttpClient 4 | 5 | # ResponseHandler takes care of decoding the response body into suitable type 6 | class ResponseHandler 7 | 8 | def self.get_body(env) 9 | type = env.response_headers["content-type"] || '' 10 | body = env.body 11 | 12 | # Response body is in JSON 13 | if type.include?("json") 14 | body = JSON.parse body 15 | end 16 | 17 | return body 18 | end 19 | 20 | end 21 | 22 | end 23 | 24 | end 25 | -------------------------------------------------------------------------------- /lib/kickbox/http_client/request_handler.rb: -------------------------------------------------------------------------------- 1 | module Kickbox 2 | 3 | module HttpClient 4 | 5 | # RequestHandler takes care of encoding the request body into format given by options 6 | class RequestHandler 7 | 8 | def self.set_body(options) 9 | type = options.fetch(:request_type, "raw") 10 | 11 | # Raw body 12 | if type == "raw" 13 | options[:body] = options[:body].is_a?(Hash) ? "" : options[:body] 14 | options[:headers].delete("content-type") 15 | end 16 | 17 | return options 18 | end 19 | 20 | end 21 | 22 | end 23 | 24 | end 25 | -------------------------------------------------------------------------------- /kickbox.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | Gem::Specification.new do |gem| 4 | gem.name = "kickbox" 5 | gem.version = "2.0.5" 6 | gem.description = "Official kickbox API library client for ruby" 7 | gem.summary = "Official kickbox API library client for ruby" 8 | 9 | gem.author = "Chaitanya Surapaneni" 10 | gem.email = "chaitanya.surapaneni@kickbox.com" 11 | gem.homepage = "https://kickbox.com" 12 | gem.license = "MIT" 13 | 14 | gem.require_paths = ['lib'] 15 | 16 | gem.files = Dir["lib/**/*"] 17 | 18 | gem.add_dependency "faraday", "~> 1.0" 19 | gem.add_dependency "json", ">= 1.8" 20 | end 21 | -------------------------------------------------------------------------------- /lib/kickbox/api/kickbox.rb: -------------------------------------------------------------------------------- 1 | module Kickbox 2 | 3 | module Api 4 | 5 | # 6 | class Kickbox 7 | 8 | def initialize(client) 9 | @client = client 10 | end 11 | 12 | # Email Verification 13 | # 14 | # '/verify?email=:email&timeout=:timeout' GET 15 | # 16 | # email - Email address to verify 17 | def verify(email, options = {}) 18 | body = options.fetch("query", {}) 19 | timeout = options.fetch("timeout", 6000) 20 | 21 | email = CGI::escape(email) 22 | 23 | @client.get("/verify?email=#{email}&timeout=#{timeout}", body, options) 24 | end 25 | 26 | # Email Batch Verification 27 | # 28 | # '/verify-batch' PUT 29 | # 30 | # emails - Email addresses to verify 31 | def verify_batch(emails, options = {}) 32 | if options['headers'].blank? 33 | options['headers'] = {} 34 | end 35 | 36 | date = Time.current.strftime('%m-%d-%y %H-%M') 37 | options['headers'].merge!( 38 | 'Content-Type' => 'text/csv', 39 | 'X-Kickbox-Filename' => "Batch Email Verification #{date}" 40 | ) 41 | 42 | emails = emails.join("\n") 43 | @client.put("/verify-batch", emails, options) 44 | end 45 | 46 | # Email Batch Verification Status 47 | # 48 | # '/verify-batch/:id' GET 49 | # 50 | # id - Job ID of the batch verification job 51 | def batch_status(id) 52 | @client.get("/verify-batch/#{id}") 53 | end 54 | 55 | end 56 | 57 | end 58 | 59 | end 60 | -------------------------------------------------------------------------------- /lib/kickbox/http_client/error_handler.rb: -------------------------------------------------------------------------------- 1 | module Kickbox 2 | 3 | module HttpClient 4 | 5 | # ErrorHanlder takes care of selecting the error message from response body 6 | class ErrorHandler < Faraday::Middleware 7 | 8 | def initialize(app) 9 | super(app) 10 | end 11 | 12 | def call(env) 13 | @app.call(env).on_complete do |env| 14 | code = env.status 15 | type = env.response_headers["content-type"] || '' 16 | 17 | case code 18 | when 500...599 19 | raise Kickbox::Error::ClientError.new("Error #{code}", code) 20 | when 400...499 21 | body = Kickbox::HttpClient::ResponseHandler.get_body(env) 22 | message = "" 23 | 24 | # If HTML, whole body is taken 25 | if body.is_a?(String) 26 | message = body 27 | end 28 | 29 | # If JSON, a particular field is taken and used 30 | if type.include?("json") and body.is_a?(Hash) 31 | if body.has_key?("message") 32 | message = body["message"] 33 | else 34 | message = "Unable to select error message from json returned by request responsible for error" 35 | end 36 | end 37 | 38 | if message == "" 39 | message = "Unable to understand the content type of response returned by request responsible for error" 40 | end 41 | 42 | raise Kickbox::Error::ClientError.new message, code 43 | end 44 | end 45 | end 46 | 47 | end 48 | 49 | end 50 | 51 | end 52 | -------------------------------------------------------------------------------- /lib/kickbox/http_client/auth_handler.rb: -------------------------------------------------------------------------------- 1 | require "base64" 2 | 3 | module Kickbox 4 | 5 | module HttpClient 6 | 7 | # AuthHandler takes care of devising the auth type and using it 8 | class AuthHandler < Faraday::Middleware 9 | 10 | HTTP_HEADER = 1 11 | 12 | def initialize(app, auth = {}, options = {}) 13 | @auth = auth 14 | super(app) 15 | end 16 | 17 | def call(env) 18 | if !@auth.empty? 19 | auth = get_auth_type 20 | flag = false 21 | 22 | if auth == HTTP_HEADER 23 | env = http_header(env) 24 | flag = true 25 | end 26 | 27 | if !flag 28 | raise StandardError.new "Unable to calculate authorization method. Please check" 29 | end 30 | else 31 | raise StandardError.new "Server requires authentication to proceed further. Please check" 32 | end 33 | 34 | @app.call(env) 35 | end 36 | 37 | # Calculating the Authentication Type 38 | def get_auth_type() 39 | 40 | if @auth.has_key?(:http_header) 41 | return HTTP_HEADER 42 | end 43 | 44 | return -1 45 | end 46 | 47 | # Authorization with HTTP header 48 | def http_header(env) 49 | env[:request_headers]["Authorization"] = "token #{@auth[:http_header]}" 50 | 51 | return env 52 | end 53 | 54 | def query_params(url) 55 | if url.query.nil? or url.query.empty? 56 | {} 57 | else 58 | Faraday::Utils.parse_query(url.query) 59 | end 60 | end 61 | 62 | def merge_query(env, query) 63 | query = query.update query_params(env[:url]) 64 | 65 | env[:url].query = Faraday::Utils.build_query(query) 66 | 67 | return env 68 | end 69 | end 70 | 71 | end 72 | 73 | end 74 | -------------------------------------------------------------------------------- /lib/kickbox/http_client.rb: -------------------------------------------------------------------------------- 1 | require "kickbox/http_client/auth_handler" 2 | require "kickbox/http_client/error_handler" 3 | require "kickbox/http_client/request_handler" 4 | require "kickbox/http_client/response" 5 | require "kickbox/http_client/response_handler" 6 | 7 | module Kickbox 8 | 9 | module HttpClient 10 | 11 | # Main HttpClient which is used by Api classes 12 | class HttpClient 13 | 14 | attr_accessor :options, :headers 15 | 16 | def initialize(auth = {}, options = {}) 17 | 18 | if auth.is_a?(String) 19 | auth = { :http_header => auth } 20 | end 21 | 22 | @options = { 23 | :base => "https://api.kickbox.com", 24 | :api_version => "v2", 25 | :user_agent => "kickbox-ruby/2.0.3 (https://github.com/kickboxio/kickbox-ruby)" 26 | } 27 | 28 | @options.update(options) 29 | 30 | @headers = { 31 | "user-agent" => @options[:user_agent] 32 | } 33 | 34 | if @options.has_key?(:headers) 35 | @headers.update(Hash[@options[:headers].map { |k, v| [k.downcase, v] }]) 36 | @options.delete(:headers) 37 | end 38 | 39 | @client = Faraday.new(@options[:base]) do |conn| 40 | conn.use(Kickbox::HttpClient::AuthHandler, auth) 41 | conn.use(Kickbox::HttpClient::ErrorHandler) 42 | 43 | conn.adapter(Faraday.default_adapter) 44 | end 45 | end 46 | 47 | def get(path, params = {}, options = {}) 48 | request(path, nil, "get", options.merge({ :query => params })) 49 | end 50 | 51 | def post(path, body = {}, options = {}) 52 | request(path, body, "post", options) 53 | end 54 | 55 | def patch(path, body = {}, options = {}) 56 | request(path, body, "patch", options) 57 | end 58 | 59 | def delete(path, body = {}, options = {}) 60 | request(path, body, "delete", options) 61 | end 62 | 63 | def put(path, body = {}, options = {}) 64 | request(path, body, "put", options) 65 | end 66 | 67 | # Intermediate function which does three main things 68 | # 69 | # - Transforms the body of request into correct format 70 | # - Creates the requests with give parameters 71 | # - Returns response body after parsing it into correct format 72 | def request(path, body, method, options) 73 | options = @options.merge(options) 74 | 75 | options[:headers] = options[:headers] || {} 76 | options[:headers] = @headers.merge(Hash[options[:headers].map { |k, v| [k.downcase, v] }]) 77 | 78 | options[:body] = body 79 | 80 | if method != "get" 81 | options[:body] = options[:body] || {} 82 | options = set_body(options) 83 | end 84 | 85 | response = create_request(method, path, options) 86 | 87 | env = response.env 88 | body = get_body(env) 89 | 90 | Kickbox::HttpClient::Response.new(body, env.status, env.response_headers) 91 | end 92 | 93 | # Creating a request with the given arguments 94 | # 95 | # If api_version is set, appends it immediately after host 96 | def create_request(method, path, options) 97 | version = options.has_key?(:api_version) ? "/#{options[:api_version]}" : "" 98 | 99 | path = "#{version}#{path}" 100 | 101 | instance_eval <<-RUBY, __FILE__, __LINE__ + 1 102 | @client.#{method}(path) do |req| 103 | req.body = options[:body] 104 | req.headers.update(options[:headers]) 105 | req.params.update(options[:query]) if options[:query] 106 | end 107 | RUBY 108 | end 109 | 110 | # Get response body in correct format 111 | def get_body(env) 112 | Kickbox::HttpClient::ResponseHandler.get_body(env) 113 | end 114 | 115 | # Set request body in correct format 116 | def set_body(options) 117 | Kickbox::HttpClient::RequestHandler.set_body(options) 118 | end 119 | 120 | end 121 | 122 | end 123 | 124 | end 125 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Kickbox Email Verification Service 3 |
4 |

5 | 6 | # Email Verification Library for Ruby 7 | 8 | Kickbox determines if an email address is not only valid, but associated with a actual user. Uses include: 9 | 10 | * Preventing users from creating accounts on your applications using fake, misspelled, or throw-away email addresses. 11 | * Reducing bounces by removing old, invalid, and low quality email addresses from your mailing lists. 12 | * Saving money and projecting your reputation by only sending to real email users. 13 | 14 | ## Getting Started 15 | 16 | To begin, hop over to [kickbox.com](https://kickbox.com) and create a free account. Once you've signed up and logged in, click on **API Settings** and then click **Add API Key**. Take note of the generated API Key - you'll need it to setup the client as explained below. 17 | 18 | ## Installation 19 | 20 | Make sure you have [rubygems](https://rubygems.org) installed 21 | 22 | ```bash 23 | $ gem install kickbox 24 | ``` 25 | 26 | #### Versions 27 | 28 | Works with [ 1.9.* / 2.* ] 29 | 30 | ## Usage 31 | 32 | ```ruby 33 | require "kickbox" 34 | 35 | client = Kickbox::Client.new('Your_API_Key_Here') 36 | kickbox = client.kickbox() 37 | response = kickbox.verify("test@example.com") 38 | ``` 39 | `verify` returns a Kickbox::HttpClient::Response which has a `body` attribute which contains the deserialized JSON. 40 | 41 | You can use it like this: 42 | 43 | ```ruby 44 | response.body['result'] #=> "deliverable" 45 | response.body['reason'] #=> "accepted_email" 46 | 47 | ``` 48 | 49 | Full response information is provided below 50 | 51 | 52 | #### Options 53 | 54 | **timeout** `integer` (optional) - Maximum time, in milliseconds, for the API to complete a verification request. Default: 6000. 55 | 56 | ```ruby 57 | # Example with options 58 | response = kickbox.verify("test@example.com", { "timeout" => 60000 }) 59 | ``` 60 | 61 | ### Response information 62 | 63 | A successful API call responds with the following values: 64 | 65 | * **result** `string` - The verification result: `deliverable`, `undeliverable`, `risky`, `unknown` 66 | * **reason** `string` - The reason for the result. Possible reasons are: 67 | * `invalid_email` - Specified email is not a valid email address syntax 68 | * `invalid_domain` - Domain for email does not exist 69 | * `rejected_email` - Email address was rejected by the SMTP server, email address does not exist 70 | * `accepted_email` - Email address was accepted by the SMTP server 71 | * `low_quality ` - Email address has quality issues that may make it a risky or low-value address 72 | * `low_deliverability ` - Email address appears to be deliverable, but deliverability cannot be guaranteed 73 | * `no_connect` - Could not connect to SMTP server 74 | * `timeout` - SMTP session timed out 75 | * `invalid_smtp` - SMTP server returned an unexpected/invalid response 76 | * `unavailable_smtp` - SMTP server was unavailable to process our request 77 | * `unexpected_error` - An unexpected error has occurred 78 | * **role** `true | false` - *true* if the email address is a role address (`postmaster@example.com`, `support@example.com`, etc) 79 | * **free** `true | false` - *true* if the email address uses a free email service like gmail.com or yahoo.com. 80 | * **disposable** `true | false` - *true* if the email address uses a *disposable* domain like trashmail.com or mailinator.com. 81 | * **accept_all** `true | false` - *true* if the email was accepted, but the domain appears to accept all emails addressed to that domain. 82 | * **did_you_mean** `null | string` - Returns a suggested email if a possible spelling error was detected. (`bill.lumbergh@gamil.com` -> `bill.lumbergh@gmail.com`) 83 | * **sendex** `float` - A quality score of the provided email address ranging between 0 (no quality) and 1 (perfect quality). More information on the Sendex Score can be found [here](https://docs.kickbox.com/v2.0/docs/the-sendex). 84 | * **email** `string` - Returns a normalized version of the provided email address. (`BoB@example.com` -> `bob@example.com`) 85 | * **user** `string` - The user (a.k.a local part) of the provided email address. (`bob@example.com` -> `bob`) 86 | * **domain** `string` - The domain of the provided email address. (`bob@example.com` -> `example.com`) 87 | * **success** `true | false` - *true* if the API request was successful (i.e., no authentication or unexpected errors occurred) 88 | 89 | ### Response headers 90 | 91 | Along with each response, the following HTTP headers are included: 92 | 93 | * `X-Kickbox-Balance` - Your remaining verification credit balance (Daily + On Demand). 94 | * `X-Kickbox-Response-Time` - The elapsed time (in milliseconds) it took Kickbox to process the request. 95 | 96 | ## License 97 | MIT 98 | 99 | ## Bug Reports 100 | Report [here](https://github.com/kickboxio/kickbox-ruby/issues). 101 | 102 | ## Need Help? 103 | help@kickbox.com 104 | --------------------------------------------------------------------------------