mcmailer 0.0.3 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|