mcmailer 0.0.3 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ # -*- ruby -*-
2
+
3
+ # DO NOT EDIT THIS FILE. Instead, edit Rakefile, and run `rake bundler:gemfile`.
4
+
5
+ source "https://rubygems.org/"
6
+
7
+ gem "uuid", "2.3.5"
8
+ gem "gibbon", "0.3.5"
9
+
10
+ gem "rdoc", "~>4.0", :group => [:development, :test]
11
+ gem "hoe", "~>3.6", :group => [:development, :test]
12
+
13
+ # vim: syntax=ruby
data/Gemfile.lock ADDED
@@ -0,0 +1,34 @@
1
+ GEM
2
+ remote: https://rubygems.org/
3
+ specs:
4
+ gibbon (0.3.5)
5
+ httparty (> 0.6.0)
6
+ httparty (> 0.6.0)
7
+ json (> 1.4.0)
8
+ json (> 1.4.0)
9
+ rdoc
10
+ hoe (3.6.0)
11
+ rake (>= 0.8, < 11.0)
12
+ httparty (0.11.0)
13
+ multi_json (~> 1.0)
14
+ multi_xml (>= 0.5.2)
15
+ json (1.7.7)
16
+ macaddr (1.6.1)
17
+ systemu (~> 2.5.0)
18
+ multi_json (1.7.2)
19
+ multi_xml (0.5.3)
20
+ rake (10.0.4)
21
+ rdoc (4.0.1)
22
+ json (~> 1.4)
23
+ systemu (2.5.2)
24
+ uuid (2.3.5)
25
+ macaddr (~> 1.0)
26
+
27
+ PLATFORMS
28
+ ruby
29
+
30
+ DEPENDENCIES
31
+ gibbon (= 0.3.5)
32
+ hoe (~> 3.6)
33
+ rdoc (~> 4.0)
34
+ uuid (= 2.3.5)
data/Manifest.txt CHANGED
@@ -1,7 +1,13 @@
1
1
  .autotest
2
+ Gemfile
3
+ Gemfile.lock
2
4
  History.txt
3
5
  Manifest.txt
4
6
  README.txt
5
7
  Rakefile
8
+ config/mcmailer.yml
6
9
  lib/mcmailer.rb
10
+ lib/mcmailer/mailchimp_client.rb
11
+ lib/mcmailer/mailchimp_notification_handler.rb
12
+ mailchimp.rake
7
13
  test/test_mcmailer.rb
@@ -0,0 +1,10 @@
1
+ test:
2
+ api_key: cc7a805fbe90a734204b4100ad3cbbcc-us6
3
+ timeout: 300
4
+ list_name: Mcmailer Test List
5
+ required_fields:
6
+ fname: text
7
+ affiliate: bool
8
+ optional_fields:
9
+ lname: text
10
+ freq_flyer: bool
data/lib/mcmailer.rb CHANGED
@@ -2,7 +2,7 @@ require File.expand_path('../mcmailer/mailchimp_client', __FILE__)
2
2
  require File.expand_path('../mcmailer/mailchimp_notification_handler', __FILE__)
3
3
 
4
4
  module Mcmailer
5
- VERSION = "0.0.3"
5
+ VERSION = "0.0.4"
6
6
 
7
7
  @config = {}
8
8
  @valid_config_keys = ['test', 'development', 'production']
@@ -0,0 +1,117 @@
1
+ require 'gibbon'
2
+
3
+ class MailchimpClient
4
+ class RequestTimeout < StandardError
5
+ def initialize(msg="Request exceeded time limit; consider increasing the timeout value in lib/mailchimp_client")
6
+ super
7
+ end
8
+ end
9
+
10
+ class ListNotFound < StandardError
11
+ def initialize(msg="Unable to find MailChimp list by the name specified")
12
+ super
13
+ end
14
+ end
15
+
16
+ class UnexpectedResponse < StandardError
17
+ end
18
+
19
+
20
+
21
+ attr_reader :previous_response
22
+
23
+ def initialize(api_key, timeout)
24
+ @api_key = api_key
25
+ @gibbon = Gibbon.new(@api_key)
26
+ @gibbon.timeout = timeout
27
+ end
28
+
29
+ def use_list(name)
30
+ begin
31
+ @previous_response = @gibbon.lists
32
+ rescue Timeout::Error
33
+ raise RequestTimeout
34
+ end
35
+
36
+ if @previous_response.has_key?('data')
37
+ lists = @previous_response['data']
38
+ matches = lists.select {|list| list.has_key?('name') && list['name'] == name}
39
+
40
+ if matches.empty?
41
+ raise ListNotFound, "provided name => '#{name}'"
42
+ elsif matches.length > 1
43
+ raise UnexpectedResponse, "list names should be unique but found #{matches.length} lists by the name of '#{name}'"
44
+ else
45
+ @list = matches.first
46
+ end
47
+ else
48
+ raise UnexpectedResponse, "response hash did not have a 'data' key"
49
+ end
50
+ end
51
+
52
+ def subscribers(start=0, limit=100)
53
+ @previous_response = @gibbon.list_members(:id => @list['id'], :start => start, :limit => limit)
54
+ if !@previous_response.has_key?('data')
55
+ raise UnexpectedResponse, "response hash did not have a 'data' key"
56
+ end
57
+
58
+ @previous_response['data']
59
+ rescue Timeout::Error
60
+ raise RequestTimeout
61
+ end
62
+
63
+ def subscribe(email, merge_vars={})
64
+ @previous_response = @gibbon.list_subscribe(:id => @list['id'], :email_address => email, :double_optin => false, :update_existing => true, :merge_vars => merge_vars)
65
+ rescue Timeout::Error
66
+ raise RequestTimeout
67
+ end
68
+
69
+ def subscriber_info(email)
70
+ @previous_response = @gibbon.list_member_info(:id => @list['id'], :email_address => [email])
71
+ rescue Timeout::Error
72
+ raise RequestTimeout
73
+ end
74
+
75
+ def batch_subscribe(batch)
76
+ @previous_response = @gibbon.list_batch_subscribe(:id => @list['id'], :batch => batch, :double_optin => false, :update_existing => true)
77
+ rescue Timeout::Error
78
+ raise RequestTimeout
79
+ end
80
+
81
+ def destroy!(email)
82
+ @previous_response = @gibbon.list_unsubscribe(:id => @list['id'], :email_address => email, :delete_member => true, :send_goodbye => false, :send_notify => false)
83
+ rescue Timeout::Error
84
+ raise RequestTimeout
85
+ end
86
+
87
+ def batch_destroy!(emails)
88
+ @previous_response = @gibbon.list_batch_unsubscribe(:id => @list['id'], :emails => emails, :delete_member => true, :send_goodbye => false, :send_notify => false)
89
+ rescue Timeout::Error
90
+ raise RequestTimeout
91
+ end
92
+
93
+ # WARNING: For large lists this is much slower than deleting all
94
+ # subscribers through Mailchimp's website, but it should work. It's
95
+ # useful for scenarios that require automation like Mailchimp unit
96
+ # tests.
97
+ def destroy_all_subscribers!
98
+ responses = []
99
+ batch_size = 1500
100
+
101
+ until subscribers.count.zero? do
102
+ begin
103
+ emails = self.subscribers(0, batch_size).collect {|s| s['email']}
104
+ batch_destroy!(emails)
105
+ rescue UnexpectedResponse
106
+ raise
107
+ end
108
+
109
+ responses << @previous_response
110
+ break if @previous_response['success_count'].zero?
111
+ end
112
+
113
+ @previous_response = responses
114
+ rescue Timeout::Error
115
+ raise RequestTimeout
116
+ end
117
+ end
@@ -0,0 +1,107 @@
1
+ class MailchimpNotificationHandler
2
+ NO_OP_LAMBDA = lambda {|data| nil}
3
+
4
+ def initialize(params, logger, logic, bool_cols=[])
5
+ @params = params
6
+ @logger = logger
7
+ @bool_columns = bool_cols
8
+ @logic = logic
9
+ @logic.default = NO_OP_LAMBDA
10
+
11
+ # These attrs get populated when the @params hash is parsed.
12
+ @notification_type = ''
13
+ @data = {}
14
+ @message = ''
15
+ end
16
+
17
+ def process
18
+ if valid_notification?
19
+ parse_params
20
+ @logger.info("[Mailchimp (#{@params['fired_at']})] #{@message}")
21
+ @logic[@notification_type].call(@data)
22
+ else
23
+ @logger.error("[Mailchimp] Invalid notification (possible reasons: forged notification from somebody besides Mailchimp, incorrect secret code, invalid notification from Mailchimp, or invalid handling of notification.)\nparams => #{@params}")
24
+ end
25
+ end
26
+
27
+
28
+ private
29
+
30
+ def valid_notification?
31
+ # NOTE: False cases below aren't exhaustive. Implement more
32
+ # false cases as necessary.
33
+ return false unless @params.has_key?('type')
34
+ return false unless @params.has_key?('fired_at')
35
+ return false unless @params.has_key?('data')
36
+
37
+ return true
38
+ end
39
+
40
+ def parse_params
41
+ @notification_type = @params['type']
42
+ @data = @params['data']
43
+ @data['fired_at'] = @params['fired_at']
44
+
45
+ case @notification_type
46
+ when 'subscribe' then
47
+ @message << "#{@data['email']} subscribed on Mailchimp! "
48
+ @message << human_readable_merge_vars
49
+ when 'unsubscribe' then
50
+ @message << "#{@data['email']} was unsubscribed from Mailchimp #{unsubscription_reason} "
51
+ @message << human_readable_merge_vars
52
+ when 'profile' then
53
+ @message << "#{@data['email']}'s profile was updated on Mailchimp. "
54
+ @message << human_readable_merge_vars
55
+ when 'upemail' then
56
+ @message << "the owner of #{@data['old_email']} changed their email address to #{@data['new_email']} on Mailchimp."
57
+ when 'cleaned' then
58
+ @message << "#{@params['data']['email']} was cleaned from Mailchimp #{cleaned_reason}"
59
+ when 'campaign' then
60
+ @message << "the campaign with subject '#{@data['subject']}' was #{@data['status']}."
61
+ else
62
+ @message << "WARNING: Unrecognized Mailchimp notification type '#{@notification_type}'!"
63
+ end
64
+ end
65
+
66
+ def unsubscription_reason
67
+ case @params['data']['reason']
68
+ when 'manual' then 'by request.'
69
+ when 'abuse' then 'due to abuse.'
70
+ else 'for some UNKNOWN reason.'
71
+ end
72
+ end
73
+
74
+ def cleaned_reason
75
+ case @params['data']['reason']
76
+ when 'hard' then "because of a hard bounce on the recipient's server."
77
+ when 'abuse' then "due to abuse."
78
+ else 'for some UNKNOWN reason.'
79
+ end
80
+ end
81
+
82
+ def human_readable_merge_vars
83
+ result = 'Merge variables are:'
84
+ merge_vars.each_pair do |key, value|
85
+ result << "\n\t#{key} => #{value}"
86
+ end
87
+
88
+ return result
89
+ end
90
+
91
+ def merge_vars
92
+ if @params['data'].has_key?('merges')
93
+ result = {}
94
+ @params['data']['merges'].each_pair do |key, value|
95
+ value = num_to_bool(value) if @bool_columns.include?(key.to_s)
96
+ result[key] = value
97
+ end
98
+ return result
99
+ end
100
+ return nil
101
+ end
102
+
103
+ def num_to_bool(string)
104
+ (string == '0' || sttring.nil? || string.empty?) ? false : true
105
+ end
106
+
107
+ end
data/mailchimp.rake ADDED
@@ -0,0 +1,47 @@
1
+ require 'lib/mailchimp_client'
2
+
3
+ namespace :mailchimp do
4
+ def mailchimp_client
5
+ return @mailchimp_client if @mailchimp_client
6
+ @mailchimp_client = MailchimpClient.new(MAILCHIMP[:api_key])
7
+ @mailchimp_client.use_list(MAILCHIMP[:list_name])
8
+ @mailchimp_client
9
+ end
10
+
11
+ desc "Add existing mailing list subscribers to Mailchimp"
12
+ task :batch_subscribe => [:environment] do
13
+ batch = User.find(:all, :conditions => ["opt_in_products = 1 OR opt_in_promotions = 1"]).collect {|user| user.to_mailchimp}
14
+ puts "Adding #{batch.count} emails to the '#{MAILCHIMP[:list_name]}' list..."
15
+
16
+ mailchimp_client.batch_subscribe(batch)
17
+
18
+ puts "Received this response from Mailchimp: #{mailchimp_client.previous_response}"
19
+ puts "Done!"
20
+ end
21
+
22
+ desc "Remove subscriber data on the Mailchimp server"
23
+ task :destroy_subscribers => [:environment] do
24
+ puts "WARNING: This will destroy subscriber data on the Mailchimp server"
25
+
26
+ after_confirmation do
27
+ mailchimp_client.destroy_all_subscribers!
28
+ puts "List now has #{mailchimp_client.subscribers.count} subscribers."
29
+ puts "Done!"
30
+ end
31
+ end
32
+
33
+ def after_confirmation(&block)
34
+ while true
35
+ print " Are you sure you want to proceed? [Y/N] "
36
+ response = STDIN.gets
37
+ if response =~ /^[yY]/
38
+ puts
39
+ block.call()
40
+ return
41
+ elsif response =~ /^[nN]/
42
+ puts " Terminating early as requested."
43
+ return
44
+ end
45
+ end
46
+ end
47
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mcmailer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -86,11 +86,17 @@ extra_rdoc_files:
86
86
  - README.txt
87
87
  files:
88
88
  - .autotest
89
+ - Gemfile
90
+ - Gemfile.lock
89
91
  - History.txt
90
92
  - Manifest.txt
91
93
  - README.txt
92
94
  - Rakefile
95
+ - config/mcmailer.yml
93
96
  - lib/mcmailer.rb
97
+ - lib/mcmailer/mailchimp_client.rb
98
+ - lib/mcmailer/mailchimp_notification_handler.rb
99
+ - mailchimp.rake
94
100
  - test/test_mcmailer.rb
95
101
  - .gemtest
96
102
  homepage: https://github.com/jmitchell/mcmailer
@@ -110,7 +116,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
110
116
  version: '0'
111
117
  segments:
112
118
  - 0
113
- hash: -111663555823437317
119
+ hash: 2007240709961413144
114
120
  required_rubygems_version: !ruby/object:Gem::Requirement
115
121
  none: false
116
122
  requirements: