madmimi 1.0.6 → 1.0.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (7) hide show
  1. data/.gitignore +2 -1
  2. data/README.rdoc +51 -1
  3. data/Rakefile +4 -3
  4. data/VERSION +1 -1
  5. data/lib/madmimi.rb +136 -131
  6. data/madmimi.gemspec +10 -7
  7. metadata +23 -8
data/.gitignore CHANGED
@@ -1,2 +1,3 @@
1
1
  pkg
2
- *.gem
2
+ *.gem
3
+ tags
@@ -6,7 +6,7 @@ The power of Mad Mimi in your Ruby application. Deliver emails, track statistics
6
6
 
7
7
  gem install madmimi - or if you prefer to live on the edge, just clone this repository and build it from scratch.
8
8
 
9
- == A brief tutorial
9
+ == Basic Usage
10
10
 
11
11
  Dependencies:
12
12
  active_support (I intend to remove this in the not too distant future, and build my own implementation.)
@@ -33,6 +33,53 @@ mimi.promotions -> returns a hash of your promotions
33
33
 
34
34
  mimi.mailing_stats('promotion_id', 'mailing_id') -> get stats on a specific mailing
35
35
 
36
+ == Sending E-Mail (using the Mailer API)
37
+
38
+ === Replacing keys in your email body text:
39
+
40
+ options = { 'promotion_name' => 'Test Promotion', 'recipients' => 'Nicholas Young <nicholas@madmimi.com>', 'from' => 'MadMimi Ruby <rubygem@madmimi.com>', 'subject' => 'Test Subject' }
41
+
42
+ yaml_body = { 'greeting' => 'Hello', 'name' => 'Nicholas' }
43
+
44
+ mimi.send_mail(options, yaml_body)
45
+
46
+ === Sending Raw HTML (presumably generated by your app)
47
+
48
+ options = { 'promotion_name' => 'Test Promotion', 'recipients' => 'Nicholas Young <nicholas@madmimi.com>', 'from' => 'MadMimi Ruby <rubygem@madmimi.com>', 'subject' => 'Test Subject' }
49
+
50
+ raw_html = "<html><head><title>My great promotion!</title></head><body>Body stuff[[tracking_beacon]]</body></html>"
51
+
52
+ mimi.send_html(options, raw_html)
53
+
54
+ === Sending Plain Text
55
+
56
+ options = { 'promotion_name' => 'Test Promotion', 'recipients' => 'Nicholas Young <nicholas@madmimi.com>', 'from' => 'MadMimi Ruby <rubygem@madmimi.com>', 'subject' => 'Test Subject' }
57
+
58
+ plain_text = "Plain text email contents [[unsubscribe]]"
59
+
60
+ mimi.send_plaintext(options, plain_text)
61
+
62
+ === Return values
63
+
64
+ In most cases, a return value of a single space indicates success.
65
+
66
+ On success, #send_mail, #send_html, and #send_plaintext return String with a numeric mailing_id. This mailing_id can be used to look up stats with #mailing_stats.
67
+
68
+ Errors or issues preventing operation completing return a human-readable String.
69
+
70
+ Therefore, if the return value is not a space, or is not a numeric String value, then
71
+ there is probably an error or uncompleted operation.
72
+
73
+ === Specific options keys
74
+
75
+ 'raw_html': Must include at least one of the [[tracking_beacon]] or [[peek_image]] tags.
76
+
77
+ 'promotion_name': If a promotion doesn't exist under the given name, it will be created.
78
+ If it exists and you specify raw_html, the promotion body will be replaced.
79
+
80
+ 'list_name': For all of the #send methods, if 'list_name' is provided, the recipients
81
+ will be those for an already-existing "audience."
82
+
36
83
  == Note on Patches/Pull Requests
37
84
 
38
85
  * Fork the project.
@@ -43,6 +90,9 @@ mimi.mailing_stats('promotion_id', 'mailing_id') -> get stats on a specific mail
43
90
  (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
44
91
  * Send me a pull request. Bonus points for topic branches.
45
92
 
93
+ == Contributors
94
+ tuker
95
+
46
96
  == Copyright
47
97
 
48
98
  Copyright (c) 2010 Nicholas Young. See LICENSE for details.
data/Rakefile CHANGED
@@ -8,9 +8,10 @@ begin
8
8
  gem.summary = %Q{Mad Mimi API wrapper for Ruby}
9
9
  gem.description = %Q{Send emails, track statistics, and manage your subscriber base with ease.}
10
10
  gem.email = "nicholas@madmimi.com"
11
- gem.homepage = "http://github.com/nicholaswyoung/mad_mimi_ruby"
12
- gem.authors = ["Nicholas Young"]
13
- gem.add_development_dependency "activesupport", ">= 2.3.5"
11
+ gem.homepage = "http://github.com/madmimi/madmimi-gem"
12
+ gem.authors = ["Nicholas Young", "Marc Heiligers"]
13
+ gem.add_development_dependency "crack", ">= 0.1.7"
14
+ gem.add_development_dependency "shoulda", "2.10.3"
14
15
  # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
15
16
  end
16
17
  Jeweler::GemcutterTasks.new
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.0.6
1
+ 1.0.8
@@ -1,4 +1,4 @@
1
- # Mad Mimi - v.0.0.1 the incredibly basic, and barely usable version, in my opinion. (too many stinkin' dependencies!)
1
+ # Mad Mimi for Ruby
2
2
 
3
3
  # License
4
4
 
@@ -29,186 +29,191 @@
29
29
  require 'uri'
30
30
  require 'net/http'
31
31
  require 'net/https'
32
- require 'active_support'
32
+ require 'crack'
33
+ require 'csv'
33
34
 
34
35
  class MadMimi
35
-
36
- BASE_URL = "api.madmimi.com"
37
- NEW_LISTS_PATH = "/audience_lists"
38
- AUDIENCE_MEMBERS_PATH = "/audience_members"
39
- AUDIENCE_LISTS_PATH = "/audience_lists/lists.xml"
40
- MEMBERSHIPS_PATH = "/audience_members/%email%/lists.xml"
41
- SUPPRESSED_SINCE_PATH = "/audience_members/suppressed_since/%timestamp%.txt"
42
- PROMOTIONS_PATH = "/promotions.xml"
43
- MAILING_STATS_PATH = "/promotions/%promotion_id%/mailings/%mailing_id%.xml"
44
- SEARCH_PATH = "/audience_members/search.xml"
45
-
46
- @@api_settings = {}
36
+
37
+ class MadMimiError < StandardError; end
38
+
39
+ BASE_URL = 'api.madmimi.com'
40
+ NEW_LISTS_PATH = '/audience_lists'
41
+ AUDIENCE_MEMBERS_PATH = '/audience_members'
42
+ AUDIENCE_LISTS_PATH = '/audience_lists/lists.xml'
43
+ MEMBERSHIPS_PATH = '/audience_members/%email%/lists.xml'
44
+ SUPPRESSED_SINCE_PATH = '/audience_members/suppressed_since/%timestamp%.txt'
45
+ PROMOTIONS_PATH = '/promotions.xml'
46
+ MAILING_STATS_PATH = '/promotions/%promotion_id%/mailings/%mailing_id%.xml'
47
+ SEARCH_PATH = '/audience_members/search.xml'
48
+ MAILER_PATH = '/mailer'
49
+ MAILER_TO_LIST_PATH = '/mailer/to_list'
47
50
 
48
51
  def initialize(username, api_key)
49
- @@api_settings[:username] = username
50
- @@api_settings[:api_key] = api_key
52
+ @api_settings = { :username => username, :api_key => api_key }
51
53
  end
52
-
54
+
53
55
  def username
54
- @@api_settings[:username]
56
+ @api_settings[:username]
55
57
  end
56
-
58
+
57
59
  def api_key
58
- @@api_settings[:api_key]
60
+ @api_settings[:api_key]
59
61
  end
60
-
62
+
61
63
  def default_opt
62
- { 'username' => username, 'api_key' => api_key }
64
+ { :username => username, :api_key => api_key }
63
65
  end
64
-
65
- # Refactor this method asap
66
- def do_request(path, req_type = :get, options = {}, transactional = false)
67
- resp = href = "";
68
- case req_type
69
- when :get then
70
- begin
71
- http = Net::HTTP.new(BASE_URL, 80)
72
- http.start do |http|
73
- req = Net::HTTP::Get.new(path)
74
- req.set_form_data(options)
75
- response = http.request(req)
76
- resp = response.body
77
- end
78
- resp
79
- rescue SocketError
80
- raise "Host unreachable."
81
- end
82
- when :post then
83
- begin
84
- if transactional == true
85
- http = Net::HTTP.new(BASE_URL, 443)
86
- http.use_ssl = true
87
- http.verify_mode = OpenSSL::SSL::VERIFY_NONE
88
- else
89
- http = Net::HTTP.new(BASE_URL, 80)
90
- end
91
- http.start do |http|
92
- req = Net::HTTP::Post.new(path)
93
- req.set_form_data(options)
94
- response = http.request(req)
95
- resp = response.body
96
- end
97
- rescue SocketError
98
- raise "Host unreachable."
99
- end
100
- end
101
- end
102
-
66
+
103
67
  def lists
104
- request = do_request(AUDIENCE_LISTS_PATH, :get, default_opt)
105
- Hash.from_xml(request)
68
+ request = do_request(AUDIENCE_LISTS_PATH, :get)
69
+ Crack::XML.parse(request)
106
70
  end
107
-
71
+
108
72
  def memberships(email)
109
- request = do_request((MEMBERSHIPS_PATH.gsub('%email%', email)), :get, default_opt)
110
- Hash.from_xml(request)
73
+ request = do_request(MEMBERSHIPS_PATH.gsub('%email%', email), :get)
74
+ Crack::XML.parse(request)
111
75
  end
112
-
76
+
113
77
  def new_list(list_name)
114
- options = { 'name' => list_name }
115
- do_request(NEW_LISTS_PATH, :post, options.merge(default_opt))
78
+ do_request(NEW_LISTS_PATH, :post, :name => list_name)
116
79
  end
117
-
80
+
118
81
  def delete_list(list_name)
119
- options = { '_method' => 'delete' }
120
- do_request(NEW_LISTS_PATH + "/" + URI.escape(list_name), :post, options.merge(default_opt))
82
+ do_request("#{NEW_LISTS_PATH}/#{URI.escape(list_name)}", :post, :'_method' => 'delete')
121
83
  end
122
-
84
+
123
85
  def csv_import(csv_string)
124
- options = { 'csv_file' => csv_string }
125
- do_request(AUDIENCE_MEMBERS_PATH, :post, options.merge(default_opt))
126
- end
127
-
128
- def build_csv(hash)
129
- csv = ""
130
- hash.keys.each do |key|
131
- csv << "#{key},"
132
- end
133
- # strip out one char at the end
134
- csv << "\n"
135
- csv = csv[0..-1]
136
- hash.values.each do |value|
137
- csv << "#{value},"
138
- end
139
- csv = csv[0..-1]
140
- csv << "\n"
86
+ do_request(AUDIENCE_MEMBERS_PATH, :post, :csv_file => csv_string)
141
87
  end
142
-
88
+
143
89
  def add_user(options)
144
90
  csv_data = build_csv(options)
145
- opt = { 'csv_file' => csv_data }
146
- do_request(AUDIENCE_MEMBERS_PATH, :post, opt.merge(default_opt))
91
+ do_request(AUDIENCE_MEMBERS_PATH, :post, :csv_file => csv_data)
147
92
  end
148
-
93
+
149
94
  def add_to_list(email, list_name)
150
- options = { 'email' => email }
151
- do_request(NEW_LISTS_PATH + "/" + URI.escape(list_name) + "/add", :post, options.merge(default_opt))
95
+ do_request("#{NEW_LISTS_PATH}/#{URI.escape(list_name)}/add", :post, :email => email)
152
96
  end
153
-
97
+
154
98
  def remove_from_list(email, list_name)
155
- options = { 'email' => email }
156
- do_request(NEW_LISTS_PATH + "/" + URI.escape(list_name) + "/remove", :post, options.merge(default_opt))
99
+ do_request("#{NEW_LISTS_PATH}/#{URI.escape(list_name)}/remove", :post, :email => email)
157
100
  end
158
-
101
+
159
102
  def suppressed_since(timestamp)
160
- do_request((SUPPRESSED_SINCE_PATH.gsub('%timestamp%', timestamp)), :get, default_opt)
103
+ do_request(SUPPRESSED_SINCE_PATH.gsub('%timestamp%', timestamp), :get)
161
104
  end
162
-
105
+
163
106
  def promotions
164
- request = do_request(PROMOTIONS_PATH, :get, default_opt)
165
- Hash.from_xml(request)
107
+ request = do_request(PROMOTIONS_PATH, :get)
108
+ Crack::XML.parse(request)
166
109
  end
167
-
110
+
168
111
  def mailing_stats(promotion_id, mailing_id)
169
- path = (MAILING_STATS_PATH.gsub('%promotion_id%', promotion_id).gsub('%mailing_id%', mailing_id))
170
- request = do_request(path, :get, default_opt)
171
- Hash.from_xml(request)
112
+ path = MAILING_STATS_PATH.gsub('%promotion_id%', promotion_id).gsub('%mailing_id%', mailing_id)
113
+ request = do_request(path, :get)
114
+ Crack::XML.parse(request)
172
115
  end
173
-
116
+
174
117
  def audience_search(query_string, raw = false)
175
- options = { 'raw' => raw, 'query' => query_string }
176
- request = do_request(SEARCH_PATH, :get, options.merge(default_opt))
177
- Hash.from_xml(request)
118
+ request = do_request(SEARCH_PATH, :get, :raw => raw, :query => query_string)
119
+ Crack::XML.parse(request)
178
120
  end
179
-
121
+
180
122
  def send_mail(opt, yaml_body)
181
- opt['body'] = yaml_body.to_yaml
182
- if opt['list_name']
183
- do_request('/mailer/to_list', :post, opt.merge(default_opt), true)
123
+ options = opt.dup
124
+ options[:body] = yaml_body.to_yaml
125
+ if !options[:list_name].nil?
126
+ do_request(MAILER_TO_LIST_PATH, :post, options, true)
184
127
  else
185
- do_request('/mailer', :post, opt.merge(default_opt), true)
128
+ do_request(MAILER_PATH, :post, options, true)
186
129
  end
187
130
  end
188
-
131
+
189
132
  def send_html(opt, html)
133
+ options = opt.dup
190
134
  if html.include?('[[tracking_beacon]]') || html.include?('[[peek_image]]')
191
- opt['raw_html'] = html
192
- if opt['list_name']
193
- do_request('/mailer/to_list', :post, opt.merge(default_opt), true)
135
+ options[:raw_html] = html
136
+ if !options[:list_name].nil?
137
+ unless html.include?('[[unsubscribe]]') || html.include?('[[opt_out]]')
138
+ raise MadMimiError, "When specifying list_name, include the [[unsubscribe]] or [[opt_out]] macro in your HTML before sending."
139
+ end
140
+ do_request(MAILER_TO_LIST_PATH, :post, options, true)
194
141
  else
195
- do_request('/mailer', :post, opt.merge(default_opt), true)
142
+ do_request(MAILER_PATH, :post, options, true)
196
143
  end
197
144
  else
198
- raise StandardError, "You'll need to include either the [[tracking_beacon]] or [[peek_image]] tag in your HTML before sending."
145
+ raise MadMimiError, "You'll need to include either the [[tracking_beacon]] or [[peek_image]] macro in your HTML before sending."
199
146
  end
200
147
  end
201
-
148
+
202
149
  def send_plaintext(opt, plaintext)
203
- if plaintext.include?('[[unsubscribe]]')
204
- opt['raw_plain_text'] = plaintext
205
- if opt['list_name']
206
- do_request('/mailer/to_list', :post, opt.merge(default_opt), true)
150
+ options = opt.dup
151
+ options[:raw_plain_text] = plaintext
152
+ if !options[:list_name].nil?
153
+ if plaintext.include?('[[unsubscribe]]') || plaintext.include?('[[opt_out]]')
154
+ do_request(MAILER_TO_LIST_PATH, :post, options, true)
207
155
  else
208
- do_request('/mailer', :post, opt.merge(default_opt), true)
156
+ raise MadMimiError, "You'll need to include either the [[unsubscribe]] or [[opt_out]] macro in your text before sending."
209
157
  end
210
158
  else
211
- raise StandardError, "You'll need to include either the [[unsubscribe]] tag in your text before sending."
159
+ do_request(MAILER_PATH, :post, options, true)
160
+ end
161
+ end
162
+
163
+ private
164
+
165
+ # Refactor this method asap
166
+ def do_request(path, req_type = :get, options = {}, transactional = false)
167
+ options = options.merge(default_opt)
168
+ form_data = options.inject({}) { |m, (k, v)| m[k.to_s] = v; m }
169
+ resp = href = ""
170
+ case req_type
171
+ when :get then
172
+ begin
173
+ http = Net::HTTP.new(BASE_URL, 80)
174
+ http.start do |http|
175
+ req = Net::HTTP::Get.new(path)
176
+ req.set_form_data(form_data)
177
+ response = http.request(req)
178
+ resp = response.body.strip
179
+ end
180
+ resp
181
+ rescue SocketError
182
+ raise "Host unreachable."
183
+ end
184
+ when :post then
185
+ begin
186
+ if transactional == true
187
+ http = Net::HTTP.new(BASE_URL, 443)
188
+ http.use_ssl = true
189
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
190
+ else
191
+ http = Net::HTTP.new(BASE_URL, 80)
192
+ end
193
+ http.start do |http|
194
+ req = Net::HTTP::Post.new(path)
195
+ req.set_form_data(form_data)
196
+ response = http.request(req)
197
+ resp = response.body.strip
198
+ end
199
+ rescue SocketError
200
+ raise "Host unreachable."
201
+ end
212
202
  end
213
203
  end
214
- end
204
+
205
+ def build_csv(hash)
206
+ if CSV.respond_to?(:generate_row) # before Ruby 1.9
207
+ buffer = ''
208
+ CSV.generate_row(hash.keys, hash.keys.size, buffer)
209
+ CSV.generate_row(hash.values, hash.values.size, buffer)
210
+ buffer
211
+ else # Ruby 1.9 and after
212
+ CSV.generate do |csv|
213
+ csv << hash.keys
214
+ csv << hash.values
215
+ end
216
+ end
217
+ end
218
+
219
+ end
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{madmimi}
8
- s.version = "1.0.6"
8
+ s.version = "1.0.8"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
- s.authors = ["Nicholas Young"]
12
- s.date = %q{2010-05-08}
11
+ s.authors = ["Nicholas Young", "Marc Heiligers"]
12
+ s.date = %q{2010-05-26}
13
13
  s.description = %q{Send emails, track statistics, and manage your subscriber base with ease.}
14
14
  s.email = %q{nicholas@madmimi.com}
15
15
  s.extra_rdoc_files = [
@@ -27,7 +27,7 @@ Gem::Specification.new do |s|
27
27
  "test/helper.rb",
28
28
  "test/test_madmimi.rb"
29
29
  ]
30
- s.homepage = %q{http://github.com/nicholaswyoung/mad_mimi_ruby}
30
+ s.homepage = %q{http://github.com/madmimi/madmimi-gem}
31
31
  s.rdoc_options = ["--charset=UTF-8"]
32
32
  s.require_paths = ["lib"]
33
33
  s.rubygems_version = %q{1.3.6}
@@ -42,12 +42,15 @@ Gem::Specification.new do |s|
42
42
  s.specification_version = 3
43
43
 
44
44
  if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
45
- s.add_development_dependency(%q<activesupport>, [">= 2.3.5"])
45
+ s.add_development_dependency(%q<crack>, [">= 0.1.7"])
46
+ s.add_development_dependency(%q<shoulda>, ["= 2.10.3"])
46
47
  else
47
- s.add_dependency(%q<activesupport>, [">= 2.3.5"])
48
+ s.add_dependency(%q<crack>, [">= 0.1.7"])
49
+ s.add_dependency(%q<shoulda>, ["= 2.10.3"])
48
50
  end
49
51
  else
50
- s.add_dependency(%q<activesupport>, [">= 2.3.5"])
52
+ s.add_dependency(%q<crack>, [">= 0.1.7"])
53
+ s.add_dependency(%q<shoulda>, ["= 2.10.3"])
51
54
  end
52
55
  end
53
56
 
metadata CHANGED
@@ -5,32 +5,47 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 1
7
7
  - 0
8
- - 6
9
- version: 1.0.6
8
+ - 8
9
+ version: 1.0.8
10
10
  platform: ruby
11
11
  authors:
12
12
  - Nicholas Young
13
+ - Marc Heiligers
13
14
  autorequire:
14
15
  bindir: bin
15
16
  cert_chain: []
16
17
 
17
- date: 2010-05-08 00:00:00 -05:00
18
+ date: 2010-05-26 00:00:00 -05:00
18
19
  default_executable:
19
20
  dependencies:
20
21
  - !ruby/object:Gem::Dependency
21
- name: activesupport
22
+ name: crack
22
23
  prerelease: false
23
24
  requirement: &id001 !ruby/object:Gem::Requirement
24
25
  requirements:
25
26
  - - ">="
27
+ - !ruby/object:Gem::Version
28
+ segments:
29
+ - 0
30
+ - 1
31
+ - 7
32
+ version: 0.1.7
33
+ type: :development
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: shoulda
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - "="
26
41
  - !ruby/object:Gem::Version
27
42
  segments:
28
43
  - 2
44
+ - 10
29
45
  - 3
30
- - 5
31
- version: 2.3.5
46
+ version: 2.10.3
32
47
  type: :development
33
- version_requirements: *id001
48
+ version_requirements: *id002
34
49
  description: Send emails, track statistics, and manage your subscriber base with ease.
35
50
  email: nicholas@madmimi.com
36
51
  executables: []
@@ -51,7 +66,7 @@ files:
51
66
  - test/helper.rb
52
67
  - test/test_madmimi.rb
53
68
  has_rdoc: true
54
- homepage: http://github.com/nicholaswyoung/mad_mimi_ruby
69
+ homepage: http://github.com/madmimi/madmimi-gem
55
70
  licenses: []
56
71
 
57
72
  post_install_message: