# Copyright 2012 Mail Bypass, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. require 'net/http/post/multipart' module MessagebusApi DEFAULT_API_ENDPOINT_STRING = 'https://api.messagebus.com' class Messagebus TEMPLATE = 'template' EMAIL= 'email' attr_writer :send_common_info def initialize(api_key) @api_key = api_key init_http_connection(domain) @msg_buffer_size = 20 @msg_buffer = [] @msg_type = nil @msg_buffer_flushed = false @send_common_info = {} @results = base_response_params @rest_endpoints = define_rest_endpoints @rest_http_errors = define_rest_http_errors end def results @results end def message_buffer_size=(size) @msg_buffer_size = size if (size >= 1 && size <= 100) end def message_buffer_size @msg_buffer_size end def flushed? @msg_buffer_flushed end def api_version make_api_get_call(@rest_endpoints[:version]) end def add_message(params, flush_buffer = false) if params.key?(:templateKey) add_template_message(params) else add_email_message(params) end @msg_buffer_flushed = false if flush_buffer || @msg_buffer.size >= @msg_buffer_size flush end return end def flush if (@msg_buffer.size==0) @results=@empty_send_results return end if @msg_type == TEMPLATE endpoint = @rest_endpoints[:templates_send] else endpoint = @rest_endpoints[:emails_send] end json = json_message_from_list(@msg_buffer) if (@last_init_time < Time.now.utc - 60) init_http_connection(domain) end @results=make_api_post_call(endpoint, json) @msg_buffer.clear @msg_buffer_flushed = true return end def mailing_lists make_api_get_call(@rest_endpoints[:mailing_lists]) end def upload_mailing_list(name, file_path) path = @rest_endpoints[:mailing_lists_upload] File.open(file_path) do |f| headers = common_http_headers.merge(rest_upload_headers) req = Net::HTTP::Post::Multipart.new path, {:jsonData => "{\"name\":\"#{name}\"}", "fileData" => UploadIO.new(f, "text/plain", file_path)}, headers @results = @http.request(req) end check_response(@results) end def overwrite_mailing_list(file_name, file_path, mailing_list_key) path = @rest_endpoints[:mailing_list_overwrite].gsub("%KEY%", mailing_list_key) File.open(file_path) do |f| headers = common_http_headers.merge(rest_upload_headers) req = Net::HTTP::Post::Multipart.new path, {:jsonData => "{\"name\":\"#{file_name}\"}", "fileData" => UploadIO.new(f, "text/plain", file_path)}, headers @results = @http.request(req) end check_response(@results) end def mailing_list_delete(mailing_list_key) path = @rest_endpoints[:mailing_list_delete].gsub("%KEY%", mailing_list_key) @results = make_api_delete_call(path) @results end def campaigns path = @rest_endpoints[:campaigns] @results = make_api_get_call(path) @results end def campaigns_send(params) path = @rest_endpoints[:campaigns_send] campaign_params = base_campaign_params.merge!(params) json = campaign_params.to_json @results = make_api_post_call(path, json) @results end def campaign_status(campaign_key) path = @rest_endpoints[:campaign_status].gsub("%KEY%", campaign_key) @results = make_api_get_call(path) @results end def delete_mailing_list_entry(mailing_list_key, email) path = @rest_endpoints[:mailing_lists_entry_email].gsub("%KEY%", mailing_list_key).gsub("%EMAIL%", email) @results = make_api_delete_call(path) @results end def add_mailing_list_entry(mailing_list_key, merge_fields) path = @rest_endpoints[:mailing_lists_entries].gsub("%KEY%", mailing_list_key) json = {:mergeFields => merge_fields}.to_json @results = make_api_post_call(path, json) @results end def unsubscribes(start_date = '', end_date = '') path = "#{@rest_endpoints[:unsubscribes]}?#{date_range(start_date, end_date)}" @results = make_api_get_call(path) @results end def feedbackloops(start_date = '', end_date = '') path = "#{@rest_endpoints[:feedbackloops]}?#{date_range(start_date, end_date)}" @results = make_api_get_call(path) @results end def delivery_errors(start_date = '', end_date = '', tag='') path = "#{@rest_endpoints[:delivery_errors]}?#{date_range(start_date, end_date)}&tag=#{URI.escape(tag)}" @results = make_api_get_call(path) @results end def stats(start_date = '', end_date = '', tag = '') path = "#{@rest_endpoints[:stats]}?#{date_range(start_date, end_date)}&tag=#{URI.escape(tag)}" @results = make_api_get_call(path) @results end def cacert_info(cert_file) @http.verify_mode = OpenSSL::SSL::VERIFY_PEER if !File.exists?(cert_file) raise MessagebusApi::MissingFileError.new("Unable to read file #{cert_file}") end @http.ca_file = File.join(cert_file) end def format_iso_time(time) time.strftime("%Y-%m-%dT%H:%M:%SZ") end def deliver(message) deliver!(message) end def deliver!(message) if message.to.first.nil? ||message.subject.nil? || message.from.first.nil? then raise "Messagebus API error=Missing required header: :toEmail => #{message.to.first} :subject => #{message.subject} :fromEmail => #{message.from.first}" end message_from_email = from_email_with_name(message.header[:from].to_s) from_name = "" from_email = message.from.first if !message_from_email.nil? from_name = message_from_email[1] end if !well_formed_address?(from_email) raise "Messagebus API error=From Address is invalid :toEmail => #{message.to.first} :subject => #{message.subject} :fromEmail => #{message.from.first}" end custom_headers = {} custom_headers["envelope-sender"] = message.return_path if !message.return_path.nil? custom_headers["bcc"] = message.bcc[0] if !message.bcc.nil? message.header.fields.each do |f| custom_headers[f.name] = f.value if f.name =~ /x-.*/i end msg = { :toEmail => message.to.first, :subject => message.subject, :fromEmail => from_email, :fromName => from_name, :customHeaders => custom_headers } msg[:plaintextBody] = ( message.body ) ? "#{message.body}" : "No plaintext version was supplied." if message.multipart? msg[:plaintextBody] = (message.text_part) ? message.text_part.body.to_s : "No plaintext version was supplied." msg[:htmlBody] = message.html_part.body.to_s if message.html_part end begin add_message(msg, true) rescue => message_bus_api_error raise "Messagebus API error=#{message_bus_api_error}, message=#{msg.inspect}" end end private def init_http_connection(target_server) if (@http and @http.started?) @http.finish end @last_init_time = Time.now.utc endpoint_url = URI.parse(target_server) @http = Net::HTTP.new(endpoint_url.host, endpoint_url.port) @http.use_ssl = true @http end def domain DEFAULT_API_ENDPOINT_STRING end def common_http_headers {'User-Agent' => MessagebusApi::Info.get_user_agent, 'X-MessageBus-Key' => @api_key} end def rest_post_headers {"Content-Type" => "application/json; charset=utf-8"} end def rest_upload_headers {"Content-Type" => "multipart/form-data; charset=utf-8"} end def add_email_message(params) @msg_type = EMAIL if @msg_type == nil @msg_buffer << base_message_params.merge!(params) end def add_template_message(params) @msg_type = TEMPLATE if @msg_type == nil @msg_buffer << base_template_params.merge!(params) end def date_range(start_date, end_date) date_range_str="" if (start_date!="") date_range_str+="startDate=#{start_date}" end if (end_date!="") if (date_range_str!="") date_range_str+="&" end date_range_str+="endDate=#{end_date}" end date_range_str end def set_date(date_string, days_ago) if date_string.length == 0 return date_str_for_time_range(days_ago) end date_string end def date_str_for_time_range(days_ago) format_iso_time(Time.now.utc - (days_ago*86400)) end def from_email_with_name(address) address.match(/^(.*)\s<(.*?)>/) end def well_formed_address?(address) !address.match(/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i).nil? end def json_message_from_list(messages) {:messages => messages}.to_json end def make_api_post_call(path, data) headers = common_http_headers.merge(rest_post_headers) response = @http.request_post(path, data, headers) check_response(response) end def make_api_get_call(path) headers = common_http_headers response = @http.request_get(path, headers) check_response(response) end def make_api_delete_call(path) headers = common_http_headers response = @http.delete(path, headers) check_response(response) end def check_response(response, symbolize_names=true) case response when Net::HTTPSuccess begin return JSON.parse(response.body, :symbolize_names => symbolize_names) rescue JSON::ParserError => e raise MessagebusApi::RemoteServerError.new("JSON parsing error. Response started with #{response.body.slice(0..9)}") end when Net::HTTPClientError, Net::HTTPServerError if (response.body && response.body.size > 0) result = begin JSON.parse(response.body, :symbolize_names => symbolize_names) rescue JSON::ParserError nil end raise MessagebusApi::RemoteServerError.new("#{response.code.to_s}:#{rest_http_error_message(response)}") else raise MessagebusApi::RemoteServerError.new("#{response.code.to_s}:#{rest_http_error_message(response)}") end else raise "Unexpected HTTP Response: #{response.class.name}" end raise "Could not determine response" end def rest_http_error?(status_code) @rest_http_errors.key?(status_code) end def rest_http_error_message(response) message = "Unknown Error Code" message = @rest_http_errors[response.code.to_s] if rest_http_error?(response.code.to_s) if (response.body.size > 0) values = JSON.parse(response.body) if (values['statusMessage']) message += " - " + values['statusMessage'] end end message end def define_rest_endpoints { :emails_send => "/api/v3/emails/send", :templates_send => "/api/v3/templates/send", :stats => "/api/v3/stats", :delivery_errors => "/api/v3/delivery_errors", :unsubscribes => "/api/v3/unsubscribes", :feedbackloops => "/api/v3/feedbackloops", :mailing_lists => "/api/v3/mailing_lists", :mailing_lists_upload => "/api/v3/mailing_lists/upload", :mailing_list_overwrite => "/api/v3/mailing_list/%KEY%/upload", :mailing_list_delete => "/api/v3/mailing_list/%KEY%", :mailing_lists_entries => "/api/v3/mailing_list/%KEY%/entries", :mailing_lists_entry_email => "/api/v3/mailing_list/%KEY%/entry/%EMAIL%", :campaigns_send => "/api/v3/campaigns/send", :campaign_status => "/api/v3/campaign/%KEY%/status", :campaigns => "/api/v3/campaigns", :version => "/api/version" } end def define_rest_http_errors { "400" => "Invalid Request", "401" => "Unauthorized-Missing API Key", "403" => "Unauthorized-Invalid API Key", "404" => "Incorrect URL", "405" => "Method not allowed", "406" => "Format not acceptable", "408" => "Request Timeout", "409" => "Conflict", "410" => "Object missing or deleted", "413" => "Too many messages in request", "415" => "POST JSON data invalid", "422" => "Unprocessable Entity", "500" => "Internal Server Error", "501" => "Not Implemented", "503" => "Service Unavailable", "507" => "Insufficient Storage" } end def base_response_params {:statusCode => 0, :statusMessage => "", :statusTime => "1970-01-01T00:00:00.000Z"} end def base_message_params {:toEmail => '', :fromEmail => '', :subject => '', :toName => '', :fromName => '', :plaintextBody => '', :htmlBody => '', :customHeaders => {}, :tags => []} end def base_template_params {:toEmail => '', :toName => '', :templateKey => '', :mergeFields => {}, :customHeaders => {}} end def base_campaign_params {:campaignName => '', :fromName => '', :fromEmail => '', :subject => '', :mailingListKey => '', :htmlBody => '', :plaintextBody => '', :tags => [], :customHeaders => {}} end end end