├── .ebert.yml ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── cindy.gemspec ├── lib ├── cindy.rb ├── cindy │ ├── client.rb │ ├── error.rb │ └── version.rb └── faraday │ └── response │ └── raise_cindy_error.rb └── spec ├── cindy_spec.rb └── spec_helper.rb /.ebert.yml: -------------------------------------------------------------------------------- 1 | # This configuration was used Ebert to review the polydice/cindy repository 2 | # on bff7628dbc71fee9a5e3313733576ad1aa817992. 3 | # You can make this the default configuration for future reviews by moving this 4 | # file to your repository as `.ebert.yml` and pushing it to GitHub, and tweak 5 | # it as you wish - To know more on how to change this file to better review your 6 | # repository you can go to https://ebertapp.io/docs/config and see the configuration 7 | # details. 8 | --- 9 | styleguide: plataformatec/linters 10 | engines: 11 | reek: 12 | enabled: true 13 | fixme: 14 | enabled: true 15 | rubocop: 16 | enabled: true 17 | duplication: 18 | config: 19 | languages: 20 | - ruby 21 | enabled: true 22 | remark-lint: 23 | enabled: true 24 | exclude_paths: 25 | - spec 26 | 27 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | sudo: false 3 | cache: bundler 4 | rvm: 5 | - 2.3.3 6 | - 2.4.1 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.2.1 2 | 3 | * Update dependencies [Idris Y](https://github.com/sld) [#4](https://github.com/polydice/cindy/pull/4) 4 | 5 | ## 0.2.0 6 | 7 | * Add create campaigns API [jonah honeyman](https://github.com/jonuts) [#3](https://github.com/polydice/cindy/pull/3) 8 | 9 | ## 0.1.1 10 | 11 | * Fix URL path issues when Sendy installed in a folder 12 | 13 | ## 0.1.0 14 | 15 | * First release 16 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in cindy.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014-2015 Polydice, Inc. http://polydice.com 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cindy 2 | 3 | A lightweight and flexible Ruby SDK for [Sendy](http://sendy.co), a self-hosted email newsletter app. 4 | 5 | ## Usage 6 | 7 | The API of Cindy was basically implemented after [Sendy's API documentation](http://sendy.co/api). 8 | 9 | ### Client 10 | 11 | To use Cindy, first create a client instance: 12 | 13 | ```ruby 14 | c = Cindy.new "http://sendy.co/demo/", "QywLZqDddP2P//d6ntekf+GY82nLrHke" 15 | ``` 16 | 17 | There're two parameters for initialize method: 18 | 19 | 1. API Endpoint - The URL for Sendy installation. 20 | 2. API Key - Optional, only for subscription status methods. 21 | 22 | ### Subscribe / Unsubscribe 23 | 24 | Then you can subscribe or unsubscribe from a list: 25 | 26 | ```ruby 27 | > c.subscribe list_id, "foo@bar.com", "My Name" 28 | => true 29 | > c.unsubscribe list_id, "foo@bar.com" 30 | => false 31 | ``` 32 | 33 | ### Subscription Status 34 | 35 | To check subscription status for Email address: 36 | 37 | ```ruby 38 | > c.subscription_status list_id, "foo@bar.com" 39 | => "Unsubscribed" 40 | ``` 41 | 42 | ### Active Subscriber Count 43 | 44 | To get active subscriber count of a list: 45 | 46 | ```ruby 47 | > c.active_subscriber_count list_id 48 | => 1660 49 | ``` 50 | 51 | ### Create Campaign 52 | 53 | To create new campaign: 54 | 55 | ```ruby 56 | > c.create_campaign from_name: "foo", from_email: "foo@bar.com", reply_to: "foo@bar.com", subject: "Hello, world", html_text: "

Hello, world

" 57 | => Campaign created 58 | ``` 59 | 60 | ### More Reference 61 | 62 | Check [Sendy's API documentation](http://sendy.co/api) to learn more about parameters and possible responses. 63 | 64 | ## License 65 | 66 | MIT License. Copyright 2013-2015 Polydice, Inc. http://polydice.com/ -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | require 'rspec/core/rake_task' 3 | 4 | RSpec::Core::RakeTask.new(:spec) 5 | 6 | task :default => :spec 7 | -------------------------------------------------------------------------------- /cindy.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'cindy/version' 5 | 6 | Gem::Specification.new do |gem| 7 | gem.name = "cindy" 8 | gem.version = Cindy::VERSION 9 | gem.authors = ["Richard Lee"] 10 | gem.email = ["rl@polydice.com"] 11 | gem.description = %q{A lightweight and flexible Ruby SDK for Sendy, a self-hosted email newsletter app.} 12 | gem.summary = %q{Simple Ruby wrapper for Sendy API.} 13 | gem.homepage = "https://github.com/polydice/cindy" 14 | 15 | gem.files = `git ls-files`.split($/) 16 | gem.test_files = gem.files.grep(%r{^spec/}) 17 | gem.require_paths = ["lib"] 18 | 19 | gem.add_dependency("faraday", "~> 0.9") 20 | gem.add_dependency('faraday_middleware', '~> 0.10') 21 | gem.add_dependency('hashie', '~> 3.4') 22 | 23 | gem.add_development_dependency("rake", "~> 10.4.2") 24 | gem.add_development_dependency("rspec", "~> 2.12.0") 25 | end 26 | -------------------------------------------------------------------------------- /lib/cindy.rb: -------------------------------------------------------------------------------- 1 | require "cindy/version" 2 | require "cindy/client" 3 | require "cindy/error" 4 | 5 | module Cindy 6 | 7 | class << self 8 | 9 | def new(sendy_url, api_key = nil) 10 | # Alias method for Cindy::Client 11 | Cindy::Client.new(sendy_url, api_key) 12 | end 13 | 14 | end 15 | 16 | end 17 | -------------------------------------------------------------------------------- /lib/cindy/client.rb: -------------------------------------------------------------------------------- 1 | require 'faraday' 2 | require 'faraday_middleware' 3 | require 'faraday/response/raise_cindy_error' 4 | 5 | module Cindy 6 | class Client 7 | 8 | def initialize(sendy_url, api_key = nil) 9 | @url = sendy_url 10 | @key = api_key || ENV['SENDY_API_KEY'] 11 | end 12 | 13 | def create_campaign(opts={}) 14 | post_opts = {} 15 | req_opts = %i(from_name from_email reply_to subject html_text) 16 | optional_opts = %i(plain_text list_ids brand_id send_campaign) 17 | 18 | req_opts.each do |opt| 19 | post_opts[opt] = opts.delete(opt) || raise(ArgumentError, "opt :#{opt} required") 20 | end 21 | post_opts.merge!(Hash[optional_opts.zip(opts.values_at(*optional_opts))]) 22 | post_opts[:api_key] = @key 23 | 24 | response = connection.post "api/campaigns/create.php" do |req| 25 | req.body = post_opts 26 | end 27 | 28 | response.body 29 | end 30 | 31 | def subscribe(list_id, email, name = nil) 32 | response = connection.post "subscribe" do |req| 33 | params = {list: list_id, email: email, boolean: true} 34 | params[:name] = name if name 35 | req.body = params 36 | end 37 | 38 | !!(response.body =~ /^1$/) 39 | end 40 | 41 | def unsubscribe(list_id, email) 42 | response = connection.post "unsubscribe", {list: list_id, email: email, boolean: true} 43 | 44 | !!(response.body =~ /^1$/) 45 | end 46 | 47 | def subscription_status(list_id, email) 48 | response = connection.post "api/subscribers/subscription-status.php" do |req| 49 | req.body = {list_id: list_id, email: email, api_key: @key} 50 | end 51 | 52 | response.body 53 | end 54 | 55 | def active_subscriber_count(list_id) 56 | response = connection.post "api/subscribers/active-subscriber-count.php" do |req| 57 | req.body = {list_id: list_id, api_key: @key} 58 | end 59 | 60 | response.body.to_i 61 | end 62 | 63 | protected 64 | 65 | def connection 66 | @connection ||= Faraday.new(:url => @url) do |faraday| 67 | faraday.request :url_encoded 68 | faraday.adapter Faraday.default_adapter 69 | 70 | faraday.use ::Faraday::Response::RaiseCindyError 71 | faraday.use ::FaradayMiddleware::FollowRedirects 72 | faraday.use ::FaradayMiddleware::Mashify 73 | end 74 | end 75 | 76 | end 77 | end 78 | -------------------------------------------------------------------------------- /lib/cindy/error.rb: -------------------------------------------------------------------------------- 1 | module Cindy 2 | # Generic error 3 | class Error < StandardError; end 4 | 5 | class NoDataPasssed < Error; end 6 | 7 | class APIKeyNotPassed < Error; end 8 | 9 | class InvalidAPIKey < Error; end 10 | 11 | class EmailNotPassed < Error; end 12 | 13 | class ListIDNotPassed < Error; end 14 | 15 | class ListDoesNotExist < Error; end 16 | 17 | class EmailDoesNotExistInList < Error; end 18 | 19 | class SomeFieldsAreMissing < Error; end 20 | 21 | class InvalidEmailAddress < Error; end 22 | 23 | class AlreadySubscribed < Error; end 24 | 25 | # Raised when Sendy returns a 400 HTTP status code 26 | class BadRequest < Error; end 27 | 28 | # Raised when Sendy returns a 401 HTTP status code 29 | class Unauthorized < Error; end 30 | 31 | # Raised when Sendy returns a 403 HTTP status code 32 | class Forbidden < Error; end 33 | 34 | # Raised when Sendy returns a 404 HTTP status code 35 | class NotFound < Error; end 36 | 37 | # Raised when Sendy returns a 406 HTTP status code 38 | class NotAcceptable < Error; end 39 | 40 | # Raised when Sendy returns a 422 HTTP status code 41 | class UnprocessableEntity < Error; end 42 | 43 | # Raised when Sendy returns a 500 HTTP status code 44 | class InternalServerError < Error; end 45 | 46 | # Raised when Sendy returns a 501 HTTP status code 47 | class NotImplemented < Error; end 48 | 49 | # Raised when Sendy returns a 502 HTTP status code 50 | class BadGateway < Error; end 51 | 52 | # Raised when Sendy returns a 503 HTTP status code 53 | class ServiceUnavailable < Error; end 54 | end 55 | -------------------------------------------------------------------------------- /lib/cindy/version.rb: -------------------------------------------------------------------------------- 1 | module Cindy 2 | VERSION = "0.2.1" 3 | end 4 | -------------------------------------------------------------------------------- /lib/faraday/response/raise_cindy_error.rb: -------------------------------------------------------------------------------- 1 | require 'faraday' 2 | require 'cindy/error' 3 | 4 | module Faraday 5 | class Response::RaiseCindyError < Response::Middleware 6 | 7 | def on_complete(response) 8 | case response[:body] 9 | when /No data passed/i 10 | when /Already subscribed./i 11 | raise ::Cindy::AlreadySubscribed 12 | when /Invalid email address./i 13 | raise ::Cindy::InvalidEmailAddress 14 | end 15 | 16 | # key = response[:status].to_i 17 | # raise ERROR_MAP[key].new(response) if ERROR_MAP.has_key? key 18 | end 19 | 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /spec/cindy_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Cindy do 4 | 5 | describe ".new" do 6 | 7 | it "is a Cindy::Client" do 8 | expect(Cindy.new("http://foo.com/")).to be_a Cindy::Client 9 | end 10 | 11 | end 12 | 13 | end 14 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'rspec' 2 | require 'cindy' 3 | 4 | RSpec.configure do |config| 5 | config.color_enabled = true 6 | config.formatter = 'documentation' 7 | end 8 | --------------------------------------------------------------------------------