diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a9c888f --- /dev/null +++ b/.gitignore @@ -0,0 +1,49 @@ +# rcov generated +coverage +coverage.data + +# rdoc generated +rdoc + +# yard generated +doc +.yardoc + +# bundler +.bundle + +# jeweler generated +pkg + +# Have editor/IDE/OS specific files you need to ignore? Consider using a global gitignore: +# +# * Create a file at ~/.gitignore +# * Include files you want ignored +# * Run: git config --global core.excludesfile ~/.gitignore +# +# After doing this, these files will be ignored in all your git projects, +# saving you from having to 'pollute' every project you touch with them +# +# Not sure what to needs to be ignored for particular editors/OSes? Here's some ideas to get you started. (Remember, remove the leading # of the line) +# +# For MacOS: +# +#.DS_Store + +# For TextMate +#*.tmproj +#tmtags + +# For emacs: +#*~ +#\#* +#.\#* + +# For vim: +#*.swp + +# For redcar: +#.redcar + +# For rubinius: +#*.rbc diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..261da03 --- /dev/null +++ b/Gemfile @@ -0,0 +1,12 @@ +# frozen_string_literal: true +source "https://rubygems.org" + +# gem "rails" + +group :development do + gem "shoulda", ">= 0" + gem "rdoc", "~> 3.12" + gem "bundler", "~> 1.0" + gem "jeweler", "~> 2.3.0" + gem "simplecov", ">= 0" +end diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..f5c2439 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,87 @@ +GEM + remote: https://rubygems.org/ + specs: + activesupport (5.1.1) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (~> 0.7) + minitest (~> 5.1) + tzinfo (~> 1.1) + addressable (2.4.0) + builder (3.2.3) + concurrent-ruby (1.0.5) + descendants_tracker (0.0.4) + thread_safe (~> 0.3, >= 0.3.1) + docile (1.1.5) + faraday (0.9.2) + multipart-post (>= 1.2, < 3) + git (1.3.0) + github_api (0.16.0) + addressable (~> 2.4.0) + descendants_tracker (~> 0.0.4) + faraday (~> 0.8, < 0.10) + hashie (>= 3.4) + mime-types (>= 1.16, < 3.0) + oauth2 (~> 1.0) + hashie (3.5.5) + highline (1.7.8) + i18n (0.8.4) + jeweler (2.3.6) + builder + bundler (>= 1) + git (>= 1.2.5) + github_api (~> 0.16.0) + highline (>= 1.6.15) + nokogiri (>= 1.5.10) + psych (~> 2.2) + rake + rdoc + semver2 + json (1.8.6) + jwt (1.5.6) + mime-types (2.99.3) + mini_portile2 (2.2.0) + minitest (5.10.2) + multi_json (1.12.1) + multi_xml (0.6.0) + multipart-post (2.0.0) + nokogiri (1.8.0) + mini_portile2 (~> 2.2.0) + oauth2 (1.3.1) + faraday (>= 0.8, < 0.12) + jwt (~> 1.0) + multi_json (~> 1.3) + multi_xml (~> 0.5) + rack (>= 1.2, < 3) + psych (2.2.4) + rack (2.0.3) + rake (12.0.0) + rdoc (3.12.2) + json (~> 1.4) + semver2 (3.4.2) + shoulda (3.5.0) + shoulda-context (~> 1.0, >= 1.0.1) + shoulda-matchers (>= 1.4.1, < 3.0) + shoulda-context (1.2.2) + shoulda-matchers (2.8.0) + activesupport (>= 3.0.0) + simplecov (0.14.1) + docile (~> 1.1.0) + json (>= 1.8, < 3) + simplecov-html (~> 0.10.0) + simplecov-html (0.10.1) + thread_safe (0.3.6) + tzinfo (1.2.3) + thread_safe (~> 0.1) + +PLATFORMS + ruby + +DEPENDENCIES + bundler (~> 1.0) + jeweler (~> 2.3.0) + rdoc (~> 3.12) + shoulda + simplecov + +BUNDLED WITH + 1.15.1 diff --git a/README.md b/README.md index 30a57ae..53a2d9b 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,130 @@ # sendpulse-rest-api-ruby + A simple SendPulse REST client library and example for Ruby. + +Includes a REST API and a Rails ActionMailer Delivery Method using the REST API. + +## REST API Pre-requisites + +Please activate REST API and obtain your API Client ID and Secret values by following these instructions: +- Login to login.sendpulse.com +- Go to Account Settings -> API tab +- Activate REST API (if not already activated). +- Copy and paste ID and Secret from there + +## Setup Instructions + +### Ruby without Bundler + +Run the following command in your terminal: +```bash +gem install sendpulse-rest-api-ruby +``` + +Add the following require line to your ruby code: +```ruby +require 'sendpulse-rest-api-ruby' +``` + +### Rails or Ruby with Bundler + +Add the following to `Gemfile`: +```ruby +gem 'sendpulse-rest-api-ruby' +``` + +Run the following command in your terminal: + +```bash +bundle +``` + +Outside of Rails, you'd have to add a require line like the one in "Ruby without Bundler". + +### Rails REST API Configuration + +In Rails, you'd also have to configure the `API_CLIENT_ID` and `API_CLIENT_SECRET` variables at minimum. + +It is recommended to do so by adding a Rails initializer as follows: +- Under `config/initializers`, create a file named `sendpulse_initializer.rb` +- Add the following code to it (entering the correct values): +```ruby +Sendpulse.configure do |config| + config.api_client_id = 'apiclientidvalue' + config.api_client_secret = 'apiclientsecretvalue' +end +``` + +For use with Heroku, it is recommended you configure Rails initializer via environment variables as follows: + +```ruby +Sendpulse.configure do |config| + config.api_client_id = ENV['SENDPULSE_API_CLIENT_ID'] + config.api_client_secret = ENV['SENDPULSE_API_CLIENT_SECRET'] +end +``` + +Then run the following command to configure these environment variables in Heroku: +```bash +heroku config:add SENDPULSE_API_CLIENT_ID=apiclientidvalue SENDPULSE_API_CLIENT_SECRET=apiclientsecretvalue +``` + +### Rails ActionMailer Delivery Method Configuration + +This depends on Rails REST API Configuration being done. + +Simply set your `config.action_mailer.delivery_method` to `:sendpulse` in your environment config file. + +For example, you can add the following to `config/environments/production.rb` for production's deployment: +```ruby +config.action_mailer.delivery_method = :sendpulse +``` + +## API Example + +```ruby +require 'sendpulse-rest-api-ruby' +require 'yaml' + +API_CLIENT_ID = 'apiclientidvalue' +API_CLIENT_SECRET = 'apiclientsecretvalue' +API_PROTOCOL = 'https' +API_TOKEN = '' + +sendpulse_api = SendpulseApi.new(API_CLIENT_ID, API_CLIENT_SECRET, API_PROTOCOL, API_TOKEN) + +result = sendpulse_api.get_token +YAML::dump(result) + +result = sendpulse_api.create_campaign('Name', 'example@gmail.com', 'Example subject', 'Example', 'book_id') #+ +YAML::dump(result) + +result = sendpulse_api.add_sender('Some name', 'example@gmail.com') +YAML::dump(result) + +result = sendpulse_api.get_balance +YAML::dump(result) + +email = { + html: '

TEXT

', + text: 'TEST', + subject: 'Test, test test test', + from: { name: 'some', email: 'example@gmail.com' }, + to: [ + { name: 'some1', email: 'example1@gmail.com' }, + { name: 'some3', email: 'example3@divermail.com' }, + ], + bcc: [{ name: 'some2', email: 'example2@gmail.com' }] +} + +result = sendpulse_api.smtp_send_mail(email) +YAML::dump(result) +``` + +## License + +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ +Copyright 2015 SendPulse +See `LICENSE` for more details. diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..5e2ac60 --- /dev/null +++ b/Rakefile @@ -0,0 +1,44 @@ +# encoding: utf-8 + +require 'rubygems' +require 'bundler' +begin + Bundler.setup(:default, :development) +rescue Bundler::BundlerError => e + $stderr.puts e.message + $stderr.puts "Run `bundle install` to install missing gems" + exit e.status_code +end +require 'rake' + +require 'jeweler' +Jeweler::Tasks.new do |gem| + # gem is a Gem::Specification... see http://guides.rubygems.org/specification-reference/ for more options + gem.name = "sendpulse-rest-api-ruby" + gem.homepage = "http://github.com/sendpulse/sendpulse-rest-api-ruby" + gem.license = "MIT" + gem.summary = %Q{A simple SendPulse REST client library and example for Ruby.} + gem.description = %Q{A simple SendPulse REST client library and example for Ruby.} + gem.email = "tech@sendpulse.com" + gem.authors = ["SendPulse Tech Team"] + # dependencies defined in Gemfile +end +Jeweler::RubygemsDotOrgTasks.new + +desc "Code coverage detail" +task :simplecov do + ENV['COVERAGE'] = "true" + Rake::Task['test'].execute +end + +task :default => :version #TODO change to test/rspec once added + +require 'rdoc/task' +Rake::RDocTask.new do |rdoc| + version = File.exist?('VERSION') ? File.read('VERSION') : "" + + rdoc.rdoc_dir = 'rdoc' + rdoc.title = "sendpulse-rest-api-ruby #{version}" + rdoc.rdoc_files.include('README*') + rdoc.rdoc_files.include('lib/**/*.rb') +end diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..3eefcb9 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +1.0.0 diff --git a/example.rb b/example.rb index 69b1aed..6db26e9 100644 --- a/example.rb +++ b/example.rb @@ -1,4 +1,4 @@ -require './api/sendpulse_api' +require_relative 'lib/sendpulse-rest-api-ruby' require 'yaml' API_CLIENT_ID = '' @@ -33,4 +33,4 @@ } result = sendpulse_api.smtp_send_mail(email) -YAML::dump(result) \ No newline at end of file +YAML::dump(result) diff --git a/lib/action_mailer/sendpulse.rb b/lib/action_mailer/sendpulse.rb new file mode 100644 index 0000000..a1b74f8 --- /dev/null +++ b/lib/action_mailer/sendpulse.rb @@ -0,0 +1,15 @@ +module Sendpulse + + def self.configuration + @configration ||= Configuration.new + end + + def self.configure + yield(configuration) + end +end + +require_relative 'sendpulse/configuration' +require_relative 'sendpulse/delivery_method' +require_relative 'sendpulse/message' +require_relative 'sendpulse/railtie' if defined?(Rails::Railtie) diff --git a/lib/action_mailer/sendpulse/configuration.rb b/lib/action_mailer/sendpulse/configuration.rb new file mode 100644 index 0000000..1f78127 --- /dev/null +++ b/lib/action_mailer/sendpulse/configuration.rb @@ -0,0 +1,10 @@ +module Sendpulse + class Configuration + attr_accessor :api_client_id, :api_client_secret, :api_protocol, :options, :logger + + def initialize + @api_protocol = 'https' + @logger = Rails.logger if defined?(Rails) + end + end +end diff --git a/lib/action_mailer/sendpulse/delivery_method.rb b/lib/action_mailer/sendpulse/delivery_method.rb new file mode 100644 index 0000000..c6b9a2c --- /dev/null +++ b/lib/action_mailer/sendpulse/delivery_method.rb @@ -0,0 +1,43 @@ +require "digest/sha1" +begin + require "mail" + require "mail/check_delivery_params" + require 'sendpulse-rest-api-ruby' +rescue LoadError +end + +module Sendpulse + class DeliveryMethod + include Mail::CheckDeliveryParams if defined?(Mail::CheckDeliveryParams) + + class InvalidOption < StandardError; end + + attr_accessor :settings + + def initialize(options={}) + Sendpulse.configuration.options = options + self.settings = Sendpulse.configuration + end + + def deliver!(mail) + check_delivery_params(mail) if respond_to?(:check_delivery_params) + api_client_id = settings.api_client_id + api_client_secret = settings.api_client_secret + api_protocol = settings.api_protocol + + sendpulse_api = SendpulseApi.new(api_client_id, api_client_secret, api_protocol) + + email = { + html: mail.html_part.decoded, + text: mail.html_part.encoded, + subject: mail.subject, + from: { name: mail.from.first, email: mail.from_addrs.first }, + to: mail.to_addrs.map {|addrs| {name: addrs, email: addrs}}, + bcc: mail.bcc_addrs.map {|addrs| {name: addrs, email: addrs}} + } + + result = sendpulse_api.smtp_send_mail(email) + settings.logger.info("[sendpulse_api.smtp_send_mail] result:\n#{YAML::dump(result)}") if settings.logger + end + end +end diff --git a/lib/action_mailer/sendpulse/message.rb b/lib/action_mailer/sendpulse/message.rb new file mode 100644 index 0000000..9bd6a50 --- /dev/null +++ b/lib/action_mailer/sendpulse/message.rb @@ -0,0 +1,125 @@ +require "cgi" +require "erb" +require "fileutils" +require "uri" + +module Sendpulse + class Message + attr_reader :mail + + def self.rendered_messages(mail, options = {}) + messages = [] + messages << new(mail, options.merge(part: mail.html_part)) if mail.html_part + messages << new(mail, options.merge(part: mail.text_part)) if mail.text_part + messages << new(mail, options) if messages.empty? + messages.each(&:render) + messages.sort + end + + def initialize(mail, options = {}) + @mail = mail + @location = options[:location] + @part = options[:part] + @template = options[:message_template] + @attachments = [] + end + + def render + FileUtils.mkdir_p(@location) + + if mail.attachments.any? + attachments_dir = File.join(@location, 'attachments') + FileUtils.mkdir_p(attachments_dir) + mail.attachments.each do |attachment| + filename = attachment_filename(attachment) + path = File.join(attachments_dir, filename) + + unless File.exist?(path) # true if other parts have already been rendered + File.open(path, 'wb') { |f| f.write(attachment.body.raw_source) } + end + + @attachments << [attachment.filename, "attachments/#{CGI.escape(filename)}"] + end + end + + File.open(filepath, 'w') do |f| + f.write ERB.new(template).result(binding) + end + end + + def template + File.read(File.expand_path("../templates/#{@template}.html.erb", __FILE__)) + end + + def filepath + File.join(@location, "#{type}.html") + end + + def content_type + @part && @part.content_type || @mail.content_type + end + + def body + @body ||= begin + body = (@part || @mail).decoded + + mail.attachments.each do |attachment| + body.gsub!(attachment.url, "attachments/#{attachment_filename(attachment)}") + end + + body + end + end + + def from + @from ||= Array(@mail['from']).join(", ") + end + + def sender + @sender ||= Array(@mail['sender']).join(", ") + end + + def to + @to ||= Array(@mail['to']).join(", ") + end + + def cc + @cc ||= Array(@mail['cc']).join(", ") + end + + def bcc + @bcc ||= Array(@mail['bcc']).join(", ") + end + + def reply_to + @reply_to ||= Array(@mail['reply-to']).join(", ") + end + + def type + content_type =~ /html/ ? "rich" : "plain" + end + + def encoding + body.respond_to?(:encoding) ? body.encoding : "utf-8" + end + + def auto_link(text) + text.gsub(URI::Parser.new.make_regexp(%W[https http])) do |link| + "#{ link }" + end + end + + def h(content) + CGI.escapeHTML(content) + end + + def attachment_filename(attachment) + attachment.filename.gsub(/[^\w\-_.]/, '_') + end + + def <=>(other) + order = %w[rich plain] + order.index(type) <=> order.index(other.type) + end + end +end diff --git a/lib/action_mailer/sendpulse/railtie.rb b/lib/action_mailer/sendpulse/railtie.rb new file mode 100644 index 0000000..b503349 --- /dev/null +++ b/lib/action_mailer/sendpulse/railtie.rb @@ -0,0 +1,12 @@ +module Sendpulse + class Railtie < Rails::Railtie + initializer "sendpulse.add_delivery_method" do + ActiveSupport.on_load :action_mailer do + ActionMailer::Base.add_delivery_method( + :sendpulse, + Sendpulse::DeliveryMethod + ) + end + end + end +end diff --git a/api/sendpulse_api.rb b/lib/api/sendpulse_api.rb similarity index 99% rename from api/sendpulse_api.rb rename to lib/api/sendpulse_api.rb index f1405aa..c9abd48 100644 --- a/api/sendpulse_api.rb +++ b/lib/api/sendpulse_api.rb @@ -114,7 +114,7 @@ def send_request(path, method = 'GET', data = {}, use_token = true) http.use_ssl = true if @protocol == 'https' token = {} - token.merge!( 'authorization' => @token ) if use_token + token.merge!( 'Authorization' => "Bearer #{@token}" ) if use_token case method when 'POST' @@ -637,4 +637,4 @@ def smtp_send_mail(email) send_request('smtp/emails', 'POST', data) end -end \ No newline at end of file +end diff --git a/lib/sendpulse-rest-api-ruby.rb b/lib/sendpulse-rest-api-ruby.rb new file mode 100644 index 0000000..953369f --- /dev/null +++ b/lib/sendpulse-rest-api-ruby.rb @@ -0,0 +1,2 @@ +require_relative 'api/sendpulse_api.rb' +require_relative 'action_mailer/sendpulse.rb' diff --git a/sendpulse-rest-api-ruby.gemspec b/sendpulse-rest-api-ruby.gemspec new file mode 100644 index 0000000..247da51 --- /dev/null +++ b/sendpulse-rest-api-ruby.gemspec @@ -0,0 +1,61 @@ +# Generated by jeweler +# DO NOT EDIT THIS FILE DIRECTLY +# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec' +# -*- encoding: utf-8 -*- +# stub: sendpulse-rest-api-ruby 1.0.0 ruby lib + +Gem::Specification.new do |s| + s.name = "sendpulse-rest-api-ruby".freeze + s.version = "1.0.0" + + s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version= + s.require_paths = ["lib".freeze] + s.authors = ["SendPulse Tech Team".freeze] + s.date = "2017-06-09" + s.description = "A simple SendPulse REST client library and example for Ruby.".freeze + s.email = "tech@sendpulse.com".freeze + s.extra_rdoc_files = [ + "LICENSE", + "README.md" + ] + s.files = [ + "Gemfile", + "Gemfile.lock", + "LICENSE", + "README.md", + "Rakefile", + "VERSION", + "example.rb", + "lib/api/sendpulse_api.rb", + "lib/sendpulse-rest-api-ruby.rb" + ] + s.homepage = "http://github.com/sendpulse/sendpulse-rest-api-ruby".freeze + s.licenses = ["MIT".freeze] + s.rubygems_version = "2.6.12".freeze + s.summary = "A simple SendPulse REST client library and example for Ruby.".freeze + + if s.respond_to? :specification_version then + s.specification_version = 4 + + if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then + s.add_development_dependency(%q.freeze, [">= 0"]) + s.add_development_dependency(%q.freeze, ["~> 3.12"]) + s.add_development_dependency(%q.freeze, ["~> 1.0"]) + s.add_development_dependency(%q.freeze, ["~> 2.3.0"]) + s.add_development_dependency(%q.freeze, [">= 0"]) + else + s.add_dependency(%q.freeze, [">= 0"]) + s.add_dependency(%q.freeze, ["~> 3.12"]) + s.add_dependency(%q.freeze, ["~> 1.0"]) + s.add_dependency(%q.freeze, ["~> 2.3.0"]) + s.add_dependency(%q.freeze, [">= 0"]) + end + else + s.add_dependency(%q.freeze, [">= 0"]) + s.add_dependency(%q.freeze, ["~> 3.12"]) + s.add_dependency(%q.freeze, ["~> 1.0"]) + s.add_dependency(%q.freeze, ["~> 2.3.0"]) + s.add_dependency(%q.freeze, [">= 0"]) + end +end +