madmimi 1.0.6 → 1.0.8

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.
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: