madmimi 1.0.16 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.rspec +2 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +93 -0
- data/README.rdoc +88 -37
- data/Rakefile +2 -30
- data/VERSION +1 -1
- data/config/paths.yml +23 -0
- data/lib/madmimi.rb +210 -123
- data/madmimi.gemspec +130 -22
- data/spec/cassettes/add_to_list/success.yml +54 -0
- data/spec/cassettes/add_to_list/user_does_not_exist.yml +165 -0
- data/spec/cassettes/add_to_list/user_existed.yml +54 -0
- data/spec/cassettes/add_to_list/user_exists.yml +171 -0
- data/spec/cassettes/add_to_list/user_updated.yml +174 -0
- data/spec/cassettes/add_user/user_did_not_exist.yml +54 -0
- data/spec/cassettes/add_user/user_exist.yml +303 -0
- data/spec/cassettes/add_user/user_existed.yml +54 -0
- data/spec/cassettes/add_user/user_missing.yml +138 -0
- data/spec/cassettes/add_user/user_updated.yml +153 -0
- data/spec/cassettes/add_users/users_did_not_exist.yml +54 -0
- data/spec/cassettes/add_users/users_exist.yml +326 -0
- data/spec/cassettes/add_users/users_existed.yml +54 -0
- data/spec/cassettes/add_users/users_missing.yml +305 -0
- data/spec/cassettes/add_users/users_updated.yml +327 -0
- data/spec/cassettes/add_users_to_list/gained_membership.yml +107 -0
- data/spec/cassettes/add_users_to_list/missing_membership.yml +174 -0
- data/spec/cassettes/add_users_to_list/users_did_not_exist.yml +54 -0
- data/spec/cassettes/add_users_to_list/users_exist.yml +365 -0
- data/spec/cassettes/add_users_to_list/users_existed.yml +54 -0
- data/spec/cassettes/add_users_to_list/users_missing.yml +341 -0
- data/spec/cassettes/add_users_to_list/users_updated.yml +365 -0
- data/spec/cassettes/audience_search/does_not_include_suppressed_users.yml +76 -0
- data/spec/cassettes/audience_search/includes_suppressed_users.yml +86 -0
- data/spec/cassettes/csv_import/success.yml +54 -0
- data/spec/cassettes/csv_import/users_exist.yml +273 -0
- data/spec/cassettes/csv_import/users_missing.yml +381 -0
- data/spec/cassettes/delete_list/exists.yml +57 -0
- data/spec/cassettes/delete_list/fail.yml +54 -0
- data/spec/cassettes/delete_list/not_exists.yml +56 -0
- data/spec/cassettes/delete_list/success.yml +54 -0
- data/spec/cassettes/delete_list/users_exist.yml +129 -0
- data/spec/cassettes/list_members/multiple.yml +68 -0
- data/spec/cassettes/list_members/single.yml +68 -0
- data/spec/cassettes/lists/multiple.yml +56 -0
- data/spec/cassettes/lists/single.yml +55 -0
- data/spec/cassettes/mailing_stats/mailing_does_not_exist.yml +50 -0
- data/spec/cassettes/mailing_stats/promotion_and_mailing_exist.yml +57 -0
- data/spec/cassettes/mailing_stats/promotion_does_not_exist.yml +50 -0
- data/spec/cassettes/members/multiple.yml +174 -0
- data/spec/cassettes/members/single.yml +61 -0
- data/spec/cassettes/memberships/multiple.yml +56 -0
- data/spec/cassettes/memberships/single.yml +108 -0
- data/spec/cassettes/new_list/exists.yml +57 -0
- data/spec/cassettes/new_list/fail.yml +52 -0
- data/spec/cassettes/new_list/not_exists.yml +56 -0
- data/spec/cassettes/new_list/success.yml +52 -0
- data/spec/cassettes/promotions/multiple.yml +63 -0
- data/spec/cassettes/promotions/single.yml +61 -0
- data/spec/cassettes/remove_from_all_list/user_does_not_have_memberships.yml +60 -0
- data/spec/cassettes/remove_from_all_lists/user_has_memberships.yml +113 -0
- data/spec/cassettes/remove_from_all_lists/user_with_memberships.yml +49 -0
- data/spec/cassettes/remove_from_list/user_does_not_exist.yml +49 -0
- data/spec/cassettes/remove_from_list/user_in_list_does_not_exist.yml +62 -0
- data/spec/cassettes/remove_from_list/user_in_list_exists.yml +68 -0
- data/spec/cassettes/remove_from_list/user_in_the_list.yml +49 -0
- data/spec/cassettes/remove_from_list/user_not_in_the_list.yml +49 -0
- data/spec/cassettes/save_promotion/only_plain_text.yml +56 -0
- data/spec/cassettes/save_promotion/only_raw_html.yml +56 -0
- data/spec/cassettes/save_promotion/promotion_exists.yml +65 -0
- data/spec/cassettes/save_promotion/promotion_missing.yml +63 -0
- data/spec/cassettes/save_promotion/raw_html_and_plain_text.yml +56 -0
- data/spec/cassettes/send_html/promotion_does_not_exist.yml +56 -0
- data/spec/cassettes/send_html/send_single.yml +53 -0
- data/spec/cassettes/send_html/send_to_all.yml +56 -0
- data/spec/cassettes/send_html/send_to_list.yml +56 -0
- data/spec/cassettes/send_mail/promotion_does_not_exist.yml +54 -0
- data/spec/cassettes/send_mail/send_single.yml +53 -0
- data/spec/cassettes/send_mail/send_to_all.yml +56 -0
- data/spec/cassettes/send_mail/send_to_list.yml +56 -0
- data/spec/cassettes/send_plaintext/promotion_does_not_exist.yml +56 -0
- data/spec/cassettes/send_plaintext/send_single.yml +53 -0
- data/spec/cassettes/send_plaintext/send_to_all.yml +56 -0
- data/spec/cassettes/send_plaintext/send_to_list.yml +56 -0
- data/spec/cassettes/send_plaintext/tracking_beacon_missing.yml +56 -0
- data/spec/cassettes/status/transactional_mail_does_not_exist.yml +51 -0
- data/spec/cassettes/status/transactional_mail_exists.yml +53 -0
- data/spec/cassettes/suppress_email/already_suppressed.yml +51 -0
- data/spec/cassettes/suppress_email/not_suppressed.yml +102 -0
- data/spec/cassettes/suppress_email/user_does_not_exist.yml +100 -0
- data/spec/cassettes/suppress_email/user_is_now_suppressed.yml +51 -0
- data/spec/cassettes/suppress_email/user_not_suppressed.yml +51 -0
- data/spec/cassettes/suppressed/member_does_not_exist.yml +49 -0
- data/spec/cassettes/suppressed/member_is_not_suppressed.yml +51 -0
- data/spec/cassettes/suppressed/member_is_suppressed.yml +51 -0
- data/spec/cassettes/suppressed_since/members_exist.yml +56 -0
- data/spec/cassettes/suppressed_since/no_members.yml +56 -0
- data/spec/cassettes/unsuppress_email/already_suppressed.yml +100 -0
- data/spec/cassettes/unsuppress_email/not_suppressed.yml +99 -0
- data/spec/cassettes/unsuppress_email/user_does_not_exist.yml +99 -0
- data/spec/cassettes/unsuppress_email/user_is_now_unsuppressed.yml +49 -0
- data/spec/cassettes/unsuppress_email/user_suppressed.yml +51 -0
- data/spec/cassettes/update_email/user_does_not_have_permission.yml +52 -0
- data/spec/cassettes/update_email/user_existed.yml +54 -0
- data/spec/cassettes/update_email/user_missing.yml +52 -0
- data/spec/cassettes/update_email/user_with_new_email.yml +345 -0
- data/spec/cassettes/update_email/user_with_old_email.yml +345 -0
- data/spec/madmimi_spec.rb +1166 -0
- data/spec/spec_helper.rb +15 -0
- metadata +210 -96
- data/test/fixtures/lists.xml +0 -7
- data/test/fixtures/promotions.xml +0 -10
- data/test/fixtures/search.xml +0 -38
- data/test/helper.rb +0 -43
- data/test/test_madmimi.rb +0 -63
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: be4672c1319bb8ab018016377765b23bf7dd99a5
|
4
|
+
data.tar.gz: edcb37c771c1c11a7ebe34342ebd8971d7d26285
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e577ff522f35537b25bea05a7d8aa1a4bf9064b8678f510058413cef9fd94a671a4fa179b29b8f216cf5c160ec1400d065de66e5470c5cbc9067480c102f22bb
|
7
|
+
data.tar.gz: 26eac26ebec69e51f232b1913f4238d3a3f041bdb1559e2aacc9dc27e1768606961933026d0d8bf9e664e7b52c862bd264e55dc14536648efc0e4397e55668ae
|
data/.rspec
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
GEM
|
2
|
+
remote: https://rubygems.org/
|
3
|
+
specs:
|
4
|
+
activesupport (4.1.5)
|
5
|
+
i18n (~> 0.6, >= 0.6.9)
|
6
|
+
json (~> 1.7, >= 1.7.7)
|
7
|
+
minitest (~> 5.1)
|
8
|
+
thread_safe (~> 0.1)
|
9
|
+
tzinfo (~> 1.1)
|
10
|
+
addressable (2.3.5)
|
11
|
+
builder (3.2.2)
|
12
|
+
crack (0.4.2)
|
13
|
+
safe_yaml (~> 1.0.0)
|
14
|
+
descendants_tracker (0.0.3)
|
15
|
+
diff-lcs (1.2.5)
|
16
|
+
faraday (0.9.0)
|
17
|
+
multipart-post (>= 1.2, < 3)
|
18
|
+
git (1.2.6)
|
19
|
+
github_api (0.11.2)
|
20
|
+
addressable (~> 2.3)
|
21
|
+
descendants_tracker (~> 0.0.1)
|
22
|
+
faraday (~> 0.8, < 0.10)
|
23
|
+
hashie (>= 1.2)
|
24
|
+
multi_json (>= 1.7.5, < 2.0)
|
25
|
+
nokogiri (~> 1.6.0)
|
26
|
+
oauth2
|
27
|
+
hashie (2.0.5)
|
28
|
+
highline (1.6.20)
|
29
|
+
httparty (0.13.1)
|
30
|
+
json (~> 1.8)
|
31
|
+
multi_xml (>= 0.5.2)
|
32
|
+
i18n (0.6.11)
|
33
|
+
jeweler (2.0.1)
|
34
|
+
builder
|
35
|
+
bundler (>= 1.0)
|
36
|
+
git (>= 1.2.5)
|
37
|
+
github_api
|
38
|
+
highline (>= 1.6.15)
|
39
|
+
nokogiri (>= 1.5.10)
|
40
|
+
rake
|
41
|
+
rdoc
|
42
|
+
json (1.8.1)
|
43
|
+
jwt (0.1.11)
|
44
|
+
multi_json (>= 1.5)
|
45
|
+
mini_portile (0.5.2)
|
46
|
+
minitest (5.4.0)
|
47
|
+
multi_json (1.8.4)
|
48
|
+
multi_xml (0.5.5)
|
49
|
+
multipart-post (2.0.0)
|
50
|
+
nokogiri (1.6.1)
|
51
|
+
mini_portile (~> 0.5.0)
|
52
|
+
oauth2 (0.9.3)
|
53
|
+
faraday (>= 0.8, < 0.10)
|
54
|
+
jwt (~> 0.1.8)
|
55
|
+
multi_json (~> 1.3)
|
56
|
+
multi_xml (~> 0.5)
|
57
|
+
rack (~> 1.2)
|
58
|
+
rack (1.5.2)
|
59
|
+
rake (10.1.1)
|
60
|
+
rdoc (4.1.1)
|
61
|
+
json (~> 1.4)
|
62
|
+
rspec (3.0.0)
|
63
|
+
rspec-core (~> 3.0.0)
|
64
|
+
rspec-expectations (~> 3.0.0)
|
65
|
+
rspec-mocks (~> 3.0.0)
|
66
|
+
rspec-core (3.0.3)
|
67
|
+
rspec-support (~> 3.0.0)
|
68
|
+
rspec-expectations (3.0.3)
|
69
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
70
|
+
rspec-support (~> 3.0.0)
|
71
|
+
rspec-mocks (3.0.3)
|
72
|
+
rspec-support (~> 3.0.0)
|
73
|
+
rspec-support (3.0.3)
|
74
|
+
safe_yaml (1.0.1)
|
75
|
+
thread_safe (0.3.4)
|
76
|
+
tzinfo (1.2.2)
|
77
|
+
thread_safe (~> 0.1)
|
78
|
+
vcr (2.9.2)
|
79
|
+
webmock (1.11.0)
|
80
|
+
addressable (>= 2.2.7)
|
81
|
+
crack (>= 0.3.2)
|
82
|
+
|
83
|
+
PLATFORMS
|
84
|
+
ruby
|
85
|
+
|
86
|
+
DEPENDENCIES
|
87
|
+
activesupport (> 3.0.0)
|
88
|
+
crack (> 0.1.7)
|
89
|
+
httparty (>= 0.13.1)
|
90
|
+
jeweler (> 1.4)
|
91
|
+
rspec
|
92
|
+
vcr
|
93
|
+
webmock
|
data/README.rdoc
CHANGED
@@ -4,70 +4,124 @@ The power of Mad Mimi in your Ruby application. Deliver emails, track statistics
|
|
4
4
|
|
5
5
|
== Installation
|
6
6
|
|
7
|
-
gem install madmimi
|
7
|
+
$ gem install madmimi
|
8
|
+
|
9
|
+
or if you prefer to live on the edge, just clone this repository and build it from scratch.
|
10
|
+
|
11
|
+
== Dependencies
|
12
|
+
|
13
|
+
* active_support (I intend to remove this in the not too distant future, and build my own implementation.)
|
8
14
|
|
9
15
|
== Basic Usage
|
10
16
|
|
11
|
-
|
12
|
-
|
17
|
+
mimi = MadMimi.new('emailaddress', 'api_key')
|
18
|
+
mimi.lists # get all of your Mad Mimi lists returned as a hash
|
13
19
|
|
14
|
-
|
20
|
+
You can pass +raise_exceptions+ to the initializer, if you wish to receive exceptions for failing requests:
|
15
21
|
|
16
|
-
|
22
|
+
MadMimi.new('emailaddress', 'api_key', { :raise_exceptions => true })
|
23
|
+
mimi.lists # will raise an Exception if request fails
|
24
|
+
|
25
|
+
You can set +verify_ssl+ to either true or false in initializer, if you wish verify SSL or not in requests:
|
26
|
+
|
27
|
+
MadMimi.new('emailaddress', 'api_key', { :verify_ssl => true })
|
17
28
|
|
18
29
|
=== Audience Members and Lists
|
19
30
|
|
20
|
-
mimi.memberships('email')
|
31
|
+
mimi.memberships('email') # returns a hash of the lists that specific email address is subscribed to
|
32
|
+
|
33
|
+
mimi.new_list('New list name') # make a new list
|
34
|
+
|
35
|
+
mimi.delete_list('New list name') # delete the list I just created
|
36
|
+
|
37
|
+
mimi.csv_import("name,email\ndave,dave@example.com\n") # import from a csv string
|
38
|
+
|
39
|
+
mimi.add_user({ :email => 'dave@example.com', :first_name => 'Dave' }) # add new audience member
|
40
|
+
|
41
|
+
mimi.add_users([
|
42
|
+
{ :email => 'dave@example.com', :first_name => 'Dave' },
|
43
|
+
{ :email => 'custom@example.com', :custom_field_1 => 'Dummy value' },
|
44
|
+
{ :email => 'smith@example.com', :last_name => 'Smith' }
|
45
|
+
]) # add audience members in bulk
|
46
|
+
|
47
|
+
mimi.add_to_list('dave@example.com', 'Test List') # add this email address to a specific list
|
48
|
+
mimi.add_to_list('dave@example.com', 'Test List', {
|
49
|
+
:first_name => 'Dave',
|
50
|
+
:last_name => 'Example',
|
51
|
+
:custom_field => 'Custom value'
|
52
|
+
}) # add additional data with this email
|
21
53
|
|
22
|
-
mimi.
|
54
|
+
mimi.remove_from_list('dave@example.com', 'Test List') # remove this email address from a specific list
|
23
55
|
|
24
|
-
mimi.
|
56
|
+
mimi.remove_from_all_lists('dave@example.com') # remove this email address from all lists
|
25
57
|
|
26
|
-
|
58
|
+
# this API call needs advanced permissions (manually requested)
|
59
|
+
mimi.update_email('dave@example.com', 'john@example.com') # changes email address for user 'dave@example.com' to 'john@example.com'
|
27
60
|
|
28
|
-
mimi.
|
61
|
+
mimi.members # get all audience members
|
29
62
|
|
30
|
-
mimi.
|
63
|
+
mimi.list_members('Test List') # get audience members in specific list
|
31
64
|
|
32
|
-
mimi.
|
65
|
+
mimi.suppressed?('dave@example.com') # check if audience member is suppressed
|
66
|
+
|
67
|
+
mimi.suppress_email('dave@example.com') # move email to suppressed list
|
68
|
+
|
69
|
+
mimi.unsuppress_email('dave@example.com') # move email from suppressed list
|
70
|
+
|
71
|
+
mimi.suppressed_since('unix timestamp') # get a TXT of all addresses that were suppressed since this timestamp
|
33
72
|
|
34
73
|
=== Promotions
|
35
74
|
|
36
|
-
mimi.promotions
|
75
|
+
mimi.promotions # returns a hash of your promotions
|
37
76
|
|
38
|
-
mimi.save_promotion('promotion_name', 'raw_html', 'plain_text')
|
77
|
+
mimi.save_promotion('promotion_name', 'raw_html', 'plain_text') # saves a promotion (creates the promotion if it does not exist)
|
39
78
|
|
40
|
-
mimi.mailing_stats('promotion_id', 'mailing_id')
|
79
|
+
mimi.mailing_stats('promotion_id', 'mailing_id') # get stats on a specific mailing
|
41
80
|
|
42
81
|
== Sending E-Mail (using the Mailer API)
|
43
82
|
|
44
83
|
=== Replacing keys in your email body text:
|
45
84
|
|
46
|
-
options = {
|
85
|
+
options = {
|
86
|
+
'promotion_name' => 'Test Promotion',
|
87
|
+
'recipients' => 'Nicholas Young <nicholas@madmimi.com>',
|
88
|
+
'from' => 'MadMimi Ruby <rubygem@madmimi.com>',
|
89
|
+
'subject' => 'Test Subject'
|
90
|
+
}
|
91
|
+
yaml_body = {
|
92
|
+
'greeting' => 'Hello',
|
93
|
+
'name' => 'Nicholas'
|
94
|
+
}
|
47
95
|
|
48
|
-
|
49
|
-
|
50
|
-
mimi.send_mail(options, yaml_body)
|
96
|
+
mimi.send_mail(options, yaml_body)
|
51
97
|
|
52
98
|
=== Sending Raw HTML (presumably generated by your app)
|
53
99
|
|
54
|
-
options = {
|
55
|
-
|
56
|
-
|
100
|
+
options = {
|
101
|
+
'promotion_name' => 'Test Promotion',
|
102
|
+
'recipients' => 'Nicholas Young <nicholas@madmimi.com>',
|
103
|
+
'from' => 'MadMimi Ruby <rubygem@madmimi.com>',
|
104
|
+
'subject' => 'Test Subject'
|
105
|
+
}
|
106
|
+
raw_html = "<html><head><title>My great promotion!</title></head><body>Body stuff[[tracking_beacon]]</body></html>"
|
57
107
|
|
58
|
-
mimi.send_html(options, raw_html)
|
108
|
+
mimi.send_html(options, raw_html)
|
59
109
|
|
60
110
|
=== Sending Plain Text
|
61
111
|
|
62
|
-
options = {
|
112
|
+
options = {
|
113
|
+
'promotion_name' => 'Test Promotion',
|
114
|
+
'recipients' => 'Nicholas Young <nicholas@madmimi.com>',
|
115
|
+
'from' => 'MadMimi Ruby <rubygem@madmimi.com>',
|
116
|
+
'subject' => 'Test Subject'
|
117
|
+
}
|
118
|
+
plain_text = "Plain text email contents [[unsubscribe]]"
|
63
119
|
|
64
|
-
|
65
|
-
|
66
|
-
mimi.send_plaintext(options, plain_text)
|
120
|
+
mimi.send_plaintext(options, plain_text)
|
67
121
|
|
68
122
|
=== Getting the status of a transactional mailing
|
69
123
|
|
70
|
-
mimi.status('transaction_id')
|
124
|
+
mimi.status('transaction_id') # get the status on a specific transactional mailing
|
71
125
|
|
72
126
|
=== Return values
|
73
127
|
|
@@ -82,18 +136,14 @@ there is probably an error or uncompleted operation.
|
|
82
136
|
|
83
137
|
=== Specific options keys
|
84
138
|
|
85
|
-
'raw_html': Must include at least one of the [[tracking_beacon]] or [[peek_image]] tags.
|
86
|
-
|
87
|
-
'
|
88
|
-
If it exists and you specify raw_html, the promotion body will be replaced.
|
89
|
-
|
90
|
-
'list_name': For all of the #send methods, if 'list_name' is provided, the recipients
|
139
|
+
* 'raw_html': Must include at least one of the [[tracking_beacon]] or [[peek_image]] tags.
|
140
|
+
* 'promotion_name': If a promotion doesn't exist under the given name, it will be created. If it exists and you specify raw_html, the promotion body will be replaced.
|
141
|
+
* 'list_name': For all of the #send methods, if 'list_name' is provided, the recipients
|
91
142
|
will be those for an already-existing "audience."
|
92
|
-
|
93
|
-
'to_all': Set to true to send a promotion, plain_text or raw_html to all your audience members
|
143
|
+
* 'to_all': Set to true to send a promotion, plain_text or raw_html to all your audience members
|
94
144
|
|
95
145
|
== Note on Patches/Pull Requests
|
96
|
-
|
146
|
+
|
97
147
|
* Fork the project.
|
98
148
|
* Make your feature addition or bug fix.
|
99
149
|
* Add tests for it. This is important so I don't break it in a
|
@@ -105,6 +155,7 @@ will be those for an already-existing "audience."
|
|
105
155
|
== Contributors
|
106
156
|
tuker
|
107
157
|
marcheiligers
|
158
|
+
maximgladkov
|
108
159
|
|
109
160
|
== Copyright
|
110
161
|
|
data/Rakefile
CHANGED
@@ -9,11 +9,7 @@ begin
|
|
9
9
|
gem.description = %Q{Send emails, track statistics, and manage your subscriber base with ease.}
|
10
10
|
gem.email = "nicholas@madmimi.com"
|
11
11
|
gem.homepage = "http://github.com/madmimi/madmimi-gem"
|
12
|
-
gem.authors = ["Nicholas Young", "Marc Heiligers"]
|
13
|
-
gem.add_dependency "crack", ">0.1.7"
|
14
|
-
gem.add_development_dependency "jeweler", ">1.4"
|
15
|
-
gem.add_development_dependency "fakeweb", ">1.2"
|
16
|
-
gem.add_development_dependency "shoulda", ">2.10"
|
12
|
+
gem.authors = ["Nicholas Young", "Marc Heiligers", "Maxim Gladkov"]
|
17
13
|
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
18
14
|
end
|
19
15
|
Jeweler::GemcutterTasks.new
|
@@ -21,31 +17,7 @@ rescue LoadError
|
|
21
17
|
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
22
18
|
end
|
23
19
|
|
24
|
-
require '
|
25
|
-
Rake::TestTask.new(:test) do |test|
|
26
|
-
test.libs << 'lib' << 'test'
|
27
|
-
test.pattern = 'test/**/test_*.rb'
|
28
|
-
test.verbose = true
|
29
|
-
end
|
30
|
-
|
31
|
-
begin
|
32
|
-
require 'rcov/rcovtask'
|
33
|
-
Rcov::RcovTask.new do |test|
|
34
|
-
test.libs << 'test'
|
35
|
-
test.pattern = 'test/**/test_*.rb'
|
36
|
-
test.verbose = true
|
37
|
-
end
|
38
|
-
rescue LoadError
|
39
|
-
task :rcov do
|
40
|
-
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
task :test => :check_dependencies
|
45
|
-
|
46
|
-
task :default => :test
|
47
|
-
|
48
|
-
require 'rake/rdoctask'
|
20
|
+
require 'rdoc/task'
|
49
21
|
Rake::RDocTask.new do |rdoc|
|
50
22
|
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
51
23
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.0
|
1
|
+
1.1.0
|
data/config/paths.yml
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
:create_list: /audience_lists.json
|
2
|
+
:remove_from_all_lists: /audience_lists/remove_all
|
3
|
+
:destroy_list: /audience_lists/%{list}
|
4
|
+
:add_to_list: /audience_lists/%{list}/add
|
5
|
+
:remove_from_list: /audience_lists/%{list}/remove
|
6
|
+
:audience_lists: /audience_lists/lists.xml
|
7
|
+
:audience_list_members: /audience_lists/%{list}/members.xml
|
8
|
+
:audience_members: /audience_members
|
9
|
+
:get_audience_members: /audience_members.xml
|
10
|
+
:search: /audience_members/search.xml
|
11
|
+
:memberships: /audience_members/%{email}/lists.xml
|
12
|
+
:is_suppressed: /audience_members/%{email}/is_suppressed
|
13
|
+
:update_user_email: /audience_members/%{email}/update_email
|
14
|
+
:suppressed_since: /audience_members/suppressed_since/%{timestamp}.txt
|
15
|
+
:suppress_user: /suppressed_audience_members
|
16
|
+
:unsuppress_user: /suppressed_audience_members/%{email}
|
17
|
+
:promotions: /promotions.xml
|
18
|
+
:promotion_save: /promotions/save
|
19
|
+
:mailing_stats: /promotions/%{promotion_id}/mailings/%{mailing_id}.xml
|
20
|
+
:mailer: /mailer
|
21
|
+
:mailer_to_list: /mailer/to_list
|
22
|
+
:mailer_to_all: /mailer/to_all
|
23
|
+
:mailer_status: /mailers/status/%{transaction_id}
|
data/lib/madmimi.rb
CHANGED
@@ -14,7 +14,7 @@
|
|
14
14
|
# The above copyright notice and this permission notice shall be included in
|
15
15
|
# all copies or substantial portions of the Software.
|
16
16
|
|
17
|
-
# Except as contained in this notice, the name(s) of the above copyright holder(s)
|
17
|
+
# Except as contained in this notice, the name(s) of the above copyright holder(s)
|
18
18
|
# shall not be used in advertising or otherwise to promote the sale, use or other
|
19
19
|
# dealings in this Software without prior written authorization.
|
20
20
|
|
@@ -26,38 +26,47 @@
|
|
26
26
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
27
27
|
# THE SOFTWARE.
|
28
28
|
|
29
|
+
require 'active_support/core_ext/string'
|
30
|
+
require 'active_support/core_ext/hash'
|
29
31
|
require 'uri'
|
30
|
-
require '
|
31
|
-
require '
|
32
|
-
require 'crack'
|
32
|
+
require 'rubygems'
|
33
|
+
require 'httparty'
|
33
34
|
require 'csv'
|
35
|
+
require 'yaml'
|
36
|
+
require 'crack'
|
34
37
|
|
35
38
|
class MadMimi
|
36
39
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
40
|
+
MadMimiError = Class.new(StandardError)
|
41
|
+
|
42
|
+
include HTTParty
|
43
|
+
|
44
|
+
base_uri 'api.madmimi.com'
|
45
|
+
|
46
|
+
parser(
|
47
|
+
Proc.new do |body, format|
|
48
|
+
begin
|
49
|
+
case format
|
50
|
+
when :json
|
51
|
+
Crack::JSON.parse(body)
|
52
|
+
when :xml
|
53
|
+
Crack::XML.parse(body)
|
54
|
+
else
|
55
|
+
body
|
56
|
+
end
|
57
|
+
rescue Crack::ParseError, REXML::ParseException
|
58
|
+
body
|
59
|
+
end
|
60
|
+
end
|
61
|
+
)
|
62
|
+
|
63
|
+
def initialize(username, api_key, options = {})
|
64
|
+
@api_settings = options.reverse_merge({
|
65
|
+
:verify_ssl => true
|
66
|
+
}).merge({
|
67
|
+
:username => username,
|
68
|
+
:api_key => api_key
|
69
|
+
})
|
61
70
|
end
|
62
71
|
|
63
72
|
def username
|
@@ -68,95 +77,143 @@ class MadMimi
|
|
68
77
|
@api_settings[:api_key]
|
69
78
|
end
|
70
79
|
|
71
|
-
def
|
72
|
-
|
80
|
+
def raise_exceptions?
|
81
|
+
@api_settings[:raise_exceptions]
|
82
|
+
end
|
83
|
+
|
84
|
+
def raise_exceptions=(raise_exceptions)
|
85
|
+
@api_settings[:raise_exceptions] = raise_exceptions
|
86
|
+
end
|
87
|
+
|
88
|
+
def verify_ssl?
|
89
|
+
@api_settings[:verify_ssl]
|
90
|
+
end
|
91
|
+
|
92
|
+
def verify_ssl=(verify_ssl)
|
93
|
+
@api_settings[:verify_ssl] = verify_ssl
|
73
94
|
end
|
74
95
|
|
75
96
|
# Audience and lists
|
76
97
|
def lists
|
77
|
-
|
78
|
-
|
98
|
+
wrap_with_array('lists', 'list') do
|
99
|
+
do_request(path(:audience_lists), :get, :format => :xml)
|
100
|
+
end
|
79
101
|
end
|
80
102
|
|
81
103
|
def memberships(email)
|
82
|
-
|
83
|
-
|
104
|
+
wrap_with_array('lists', 'list') do
|
105
|
+
do_request(path(:memberships, :email => email), :get)
|
106
|
+
end
|
84
107
|
end
|
85
108
|
|
86
109
|
def new_list(list_name)
|
87
|
-
do_request(
|
110
|
+
do_request(path(:create_list), :post, :name => list_name)
|
88
111
|
end
|
89
112
|
|
90
113
|
def delete_list(list_name)
|
91
|
-
do_request(
|
114
|
+
do_request(path(:destroy_list, :list => list_name), :delete)
|
92
115
|
end
|
93
116
|
|
94
117
|
def csv_import(csv_string)
|
95
|
-
do_request(
|
118
|
+
do_request(path(:audience_members), :post, :csv_file => csv_string)
|
96
119
|
end
|
97
120
|
|
98
|
-
def add_user(
|
99
|
-
|
100
|
-
do_request(AUDIENCE_MEMBERS_PATH, :post, :csv_file => csv_data)
|
121
|
+
def add_user(hash_or_array)
|
122
|
+
csv_import(build_csv(hash_or_array))
|
101
123
|
end
|
102
124
|
|
103
|
-
|
104
|
-
|
125
|
+
alias :add_users :add_user
|
126
|
+
|
127
|
+
def add_to_list(email, list_name, options={})
|
128
|
+
do_request(path(:add_to_list, :list => list_name), :post, options.merge(:email => email))
|
105
129
|
end
|
106
130
|
|
107
131
|
def remove_from_list(email, list_name)
|
108
|
-
do_request(
|
132
|
+
do_request(path(:remove_from_list, :list => list_name), :post, :email => email)
|
109
133
|
end
|
110
134
|
|
111
|
-
def
|
112
|
-
do_request(
|
135
|
+
def remove_from_all_lists(email)
|
136
|
+
do_request(path(:remove_from_all_lists), :post, :email => email)
|
113
137
|
end
|
114
|
-
|
138
|
+
|
139
|
+
def update_email(existing_email, new_email)
|
140
|
+
do_request(path(:update_user_email, :email => existing_email), :post, :email => existing_email, :new_email => new_email)
|
141
|
+
end
|
142
|
+
|
143
|
+
def members
|
144
|
+
wrap_with_array('audience', 'member') do
|
145
|
+
do_request(path(:get_audience_members), :get)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def list_members(list_name)
|
150
|
+
wrap_with_array('audience', 'member') do
|
151
|
+
do_request(path(:audience_list_members, :list => list_name), :get)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def suppressed_since(timestamp, show_suppression_reason = false)
|
156
|
+
do_request(path(:suppressed_since, :timestamp => timestamp), :get, {
|
157
|
+
:show_suppression_reason => show_suppression_reason
|
158
|
+
})
|
159
|
+
end
|
160
|
+
|
115
161
|
def suppress_email(email)
|
116
|
-
|
162
|
+
return '' if suppressed?(email)
|
163
|
+
|
164
|
+
process_json_response do
|
165
|
+
do_request(path(:suppress_user), :post, :audience_member_id => email, :format => :json)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
def unsuppress_email(email)
|
170
|
+
return '' unless suppressed?(email)
|
171
|
+
|
172
|
+
process_json_response do
|
173
|
+
do_request(path(:unsuppress_user, :email => email), :delete, :format => :json)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
def suppressed?(email)
|
178
|
+
response = do_request(path(:is_suppressed, :email => email), :get)
|
179
|
+
response == 'true'
|
117
180
|
end
|
118
181
|
|
119
182
|
def audience_search(query_string, raw = false)
|
120
|
-
|
121
|
-
Crack::XML.parse(request)
|
183
|
+
do_request(path(:search), :get, :raw => raw, :query => query_string)
|
122
184
|
end
|
123
185
|
|
124
|
-
# Not the most elegant, but it works for now. :)
|
125
186
|
def add_users_to_list(list_name, arr)
|
126
|
-
arr.
|
127
|
-
a[:add_list] = list_name
|
128
|
-
add_user(a)
|
129
|
-
end
|
187
|
+
add_users(arr.map{ |a| a[:add_list] = list_name; a })
|
130
188
|
end
|
131
|
-
|
189
|
+
|
132
190
|
# Promotions
|
133
191
|
def promotions
|
134
|
-
|
135
|
-
|
192
|
+
wrap_with_array('promotions', 'promotion') do
|
193
|
+
do_request(path(:promotions), :get)
|
194
|
+
end
|
136
195
|
end
|
137
|
-
|
196
|
+
|
138
197
|
def save_promotion(promotion_name, raw_html, plain_text = nil)
|
139
198
|
options = { :promotion_name => promotion_name }
|
140
|
-
|
199
|
+
|
141
200
|
unless raw_html.nil?
|
142
201
|
check_for_tracking_beacon raw_html
|
143
202
|
check_for_opt_out raw_html
|
144
203
|
options[:raw_html] = raw_html
|
145
204
|
end
|
146
|
-
|
205
|
+
|
147
206
|
unless plain_text.nil?
|
148
207
|
check_for_opt_out plain_text
|
149
208
|
options[:raw_plain_text] = plain_text
|
150
209
|
end
|
151
|
-
|
152
|
-
do_request
|
210
|
+
|
211
|
+
do_request(path(:promotion_save), :post, options)
|
153
212
|
end
|
154
|
-
|
213
|
+
|
155
214
|
# Stats
|
156
215
|
def mailing_stats(promotion_id, mailing_id)
|
157
|
-
path
|
158
|
-
request = do_request(path, :get)
|
159
|
-
Crack::XML.parse(request)
|
216
|
+
do_request(path(:mailing_stats, :promotion_id => promotion_id, :mailing_id => mailing_id), :get)
|
160
217
|
end
|
161
218
|
|
162
219
|
# Mailer API
|
@@ -164,12 +221,12 @@ class MadMimi
|
|
164
221
|
options = opt.dup
|
165
222
|
options[:body] = yaml_body.to_yaml
|
166
223
|
if !options[:list_name].nil? || options[:to_all]
|
167
|
-
do_request(options[:to_all] ?
|
224
|
+
do_request(path(options[:to_all] ? :mailer_to_all : :mailer_to_list), :post, options, true)
|
168
225
|
else
|
169
|
-
do_request(
|
226
|
+
do_request(path(:mailer), :post, options, true)
|
170
227
|
end
|
171
228
|
end
|
172
|
-
|
229
|
+
|
173
230
|
def send_html(opt, html)
|
174
231
|
options = opt.dup
|
175
232
|
if html.include?('[[tracking_beacon]]') || html.include?('[[peek_image]]')
|
@@ -178,9 +235,9 @@ class MadMimi
|
|
178
235
|
unless html.include?('[[unsubscribe]]') || html.include?('[[opt_out]]')
|
179
236
|
raise MadMimiError, "When specifying list_name, include the [[unsubscribe]] or [[opt_out]] macro in your HTML before sending."
|
180
237
|
end
|
181
|
-
do_request(options[:to_all] ?
|
238
|
+
do_request(path(options[:to_all] ? :mailer_to_all : :mailer_to_list), :post, options, true)
|
182
239
|
else
|
183
|
-
do_request(
|
240
|
+
do_request(path(:mailer), :post, options, true)
|
184
241
|
end
|
185
242
|
else
|
186
243
|
raise MadMimiError, "You'll need to include either the [[tracking_beacon]] or [[peek_image]] macro in your HTML before sending."
|
@@ -192,89 +249,119 @@ class MadMimi
|
|
192
249
|
options[:raw_plain_text] = plaintext
|
193
250
|
if !options[:list_name].nil? || options[:to_all]
|
194
251
|
if plaintext.include?('[[unsubscribe]]') || plaintext.include?('[[opt_out]]')
|
195
|
-
do_request(options[:to_all] ?
|
252
|
+
do_request(path(options[:to_all] ? :mailer_to_all : :mailer_to_list), :post, options, true)
|
196
253
|
else
|
197
254
|
raise MadMimiError, "You'll need to include either the [[unsubscribe]] or [[opt_out]] macro in your text before sending."
|
198
255
|
end
|
199
256
|
else
|
200
|
-
do_request(
|
257
|
+
do_request(path(:mailer), :post, options, true)
|
201
258
|
end
|
202
259
|
end
|
203
|
-
|
260
|
+
|
204
261
|
def status(transaction_id)
|
205
|
-
do_request
|
262
|
+
do_request(path(:mailer_status, :transaction_id => transaction_id), :get, {}, true)
|
206
263
|
end
|
207
264
|
|
208
265
|
private
|
209
266
|
|
210
267
|
# Refactor this method asap
|
211
|
-
def do_request(path,
|
212
|
-
options =
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
end
|
224
|
-
|
225
|
-
case req_type
|
226
|
-
|
227
|
-
when :get then
|
228
|
-
begin
|
229
|
-
http.start do |http|
|
230
|
-
req = Net::HTTP::Get.new(path)
|
231
|
-
req.set_form_data(form_data)
|
232
|
-
response = http.request(req)
|
233
|
-
resp = response.body.strip
|
234
|
-
end
|
235
|
-
resp
|
236
|
-
rescue SocketError
|
237
|
-
raise "Host unreachable."
|
238
|
-
end
|
239
|
-
when :post then
|
240
|
-
begin
|
241
|
-
http.start do |http|
|
242
|
-
req = Net::HTTP::Post.new(path)
|
243
|
-
req.set_form_data(form_data)
|
244
|
-
response = http.request(req)
|
245
|
-
resp = response.body.strip
|
246
|
-
end
|
247
|
-
rescue SocketError
|
248
|
-
raise "Host unreachable."
|
249
|
-
end
|
250
|
-
end
|
268
|
+
def do_request(path, method = :get, options = {}, transactional = false)
|
269
|
+
options = default_options.deep_merge({
|
270
|
+
:format => options.delete(:format) || extract_format(path),
|
271
|
+
:body => options
|
272
|
+
})
|
273
|
+
|
274
|
+
path = convert_to_secure(path) if transactional
|
275
|
+
|
276
|
+
response = self.class.send(method, path, options)
|
277
|
+
|
278
|
+
response.value if raise_exceptions?
|
279
|
+
response.parsed_response
|
251
280
|
end
|
252
281
|
|
253
|
-
def build_csv(
|
282
|
+
def build_csv(hash_or_array)
|
283
|
+
hashes = Array.wrap(hash_or_array)
|
284
|
+
columns = hashes.map(&:keys).flatten.uniq
|
285
|
+
|
254
286
|
if CSV.respond_to?(:generate_row) # before Ruby 1.9
|
255
287
|
buffer = ''
|
256
|
-
CSV.generate_row(
|
257
|
-
|
288
|
+
CSV.generate_row(columns, columns.size, buffer)
|
289
|
+
hashes.each do |hash|
|
290
|
+
values = columns.map{ |c| hash[c] }
|
291
|
+
CSV.generate_row(values, values.size, buffer)
|
292
|
+
end
|
258
293
|
buffer
|
259
294
|
else # Ruby 1.9 and after
|
260
295
|
CSV.generate do |csv|
|
261
|
-
csv <<
|
262
|
-
|
296
|
+
csv << columns
|
297
|
+
hashes.each do |hash|
|
298
|
+
csv << columns.map{ |c| hash[c] }
|
299
|
+
end
|
263
300
|
end
|
264
301
|
end
|
265
302
|
end
|
266
|
-
|
303
|
+
|
267
304
|
def check_for_tracking_beacon(content)
|
268
305
|
unless content.include?('[[tracking_beacon]]') || content.include?('[[peek_image]]')
|
269
306
|
raise MadMimiError, "You'll need to include either the [[tracking_beacon]] or [[peek_image]] macro in your HTML before sending."
|
270
307
|
end
|
271
308
|
true
|
272
309
|
end
|
273
|
-
|
310
|
+
|
274
311
|
def check_for_opt_out(content)
|
275
312
|
unless content.include?('[[opt_out]]') || content.include?('[[unsubscribe]]')
|
276
313
|
raise MadMimiError, "When specifying list_name or sending to all, include the [[unsubscribe]] or [[opt_out]] macro in your HTML before sending."
|
277
314
|
end
|
278
315
|
true
|
279
316
|
end
|
280
|
-
|
317
|
+
|
318
|
+
def process_json_response
|
319
|
+
json_response = yield
|
320
|
+
begin
|
321
|
+
json_response["success"] ? '' : json_response["error"]
|
322
|
+
rescue JSON::ParserError
|
323
|
+
json_response
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
def default_options
|
328
|
+
{
|
329
|
+
:body => {
|
330
|
+
:username => username,
|
331
|
+
:api_key => api_key
|
332
|
+
},
|
333
|
+
:verify => verify_ssl?
|
334
|
+
}
|
335
|
+
end
|
336
|
+
|
337
|
+
def extract_format(path)
|
338
|
+
File.extname(path)[1..-1].try(:to_sym)
|
339
|
+
end
|
340
|
+
|
341
|
+
def convert_to_secure(path)
|
342
|
+
"#{ self.class.base_uri.gsub('http://', 'https://') }#{ path }"
|
343
|
+
end
|
344
|
+
|
345
|
+
def path(key, arguments={})
|
346
|
+
escaped_arguments = arguments.inject({}){ |h, (k, v)| h[k] = URI.escape(v.to_s); h }
|
347
|
+
paths[key] % escaped_arguments
|
348
|
+
end
|
349
|
+
|
350
|
+
def paths
|
351
|
+
@paths ||= YAML.load(File.read(paths_config_file))
|
352
|
+
end
|
353
|
+
|
354
|
+
def paths_config_file
|
355
|
+
@paths_config_file ||= File.join(File.dirname(File.expand_path(__FILE__)), '../config/paths.yml')
|
356
|
+
end
|
357
|
+
|
358
|
+
def wrap_with_array(*args)
|
359
|
+
yield.tap do |response|
|
360
|
+
obj = args[0..-2].inject(response){ |r, arg| r.try(:[], arg) }
|
361
|
+
|
362
|
+
if obj && obj[args.last] && obj[args.last].is_a?(Hash)
|
363
|
+
obj[args.last] = Array.wrap(obj[args.last])
|
364
|
+
end
|
365
|
+
end
|
366
|
+
end
|
367
|
+
end
|