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 +13 -0
- data/Gemfile.lock +34 -0
- data/Manifest.txt +6 -0
- data/config/mcmailer.yml +10 -0
- data/lib/mcmailer.rb +1 -1
- data/lib/mcmailer/mailchimp_client.rb +117 -0
- data/lib/mcmailer/mailchimp_notification_handler.rb +107 -0
- data/mailchimp.rake +47 -0
- metadata +8 -2
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
|
data/config/mcmailer.yml
ADDED
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.
|
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.
|
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:
|
119
|
+
hash: 2007240709961413144
|
114
120
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
115
121
|
none: false
|
116
122
|
requirements:
|