sailthru 1.0.0

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/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ pkg/*
2
+ sailthru-*.gem
3
+ github-test.rb
data/README.txt ADDED
@@ -0,0 +1,27 @@
1
+ = sailthru
2
+
3
+ == DESCRIPTION:
4
+
5
+ Sailthru Triggermail Client, with some added bonus features and simpler integration with your Rails stack, including YML config for keys and mailing list name.
6
+
7
+ == INSTALLATION:
8
+
9
+ $ gem install sailthru
10
+
11
+ == USAGE:
12
+
13
+ = Include in your app.
14
+
15
+ require 'sailthru/client'
16
+
17
+ = Set your configuration in RAILS_ROOT/config/sailthru.yml.
18
+
19
+ api_key: YOUR_API_KEY
20
+ secret: YOUR_SECRET
21
+ api_uri: http://api.sailthru.com
22
+ mailing_list_key: mailing_list
23
+
24
+ = Make calls to Sailthru as you would with the normal triggermail client:
25
+
26
+ client = Sailthru::Client.new
27
+ client.send(...)
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.0
@@ -0,0 +1,94 @@
1
+ require 'sailthru/triggermail_client'
2
+
3
+ module Sailthru
4
+ class Client
5
+ def initialize
6
+ @config = YAML.load(File.read(Rails.root + 'config' + 'sailthru.yml'))
7
+ @client = Sailthru::TriggermailClient.new(
8
+ @config['api_key'],
9
+ @config['secret'],
10
+ @config['api_uri'] || 'http://api.sailthru.com'
11
+ )
12
+ rescue Exception => ex
13
+ raise Exception.new(
14
+ "Please create a sailthru.yml file in your root config directory, containing api_key, secret, optional api_uri and optional mailing_list_key"
15
+ )
16
+ end
17
+
18
+ # Additional Helper Methods
19
+
20
+ def toggle_email_in_mailing_list(email, wants_email, vars={})
21
+ if !email.blank?
22
+ wants_email ? add_to_mailing_list(email, vars) : remove_from_mailing_list(email, vars)
23
+ end
24
+ rescue Exception => e
25
+ puts "EXCEPTION from Sailthru: #{e}"
26
+ end
27
+
28
+ def add_to_mailing_list(email, vars)
29
+ return @client.set_email(email, normalize_vars(vars), {mailing_list_key => 1}, {})
30
+ end
31
+
32
+ def remove_from_mailing_list(email, vars)
33
+ return @client.set_email(email, normalize_vars(vars), {mailing_list_key => 0}, {})
34
+ end
35
+
36
+ # Sailthru Triggermail Client passthru methods.
37
+
38
+ def send(template_name, email, vars, options = {}, schedule_time = nil)
39
+ @client.send(template_name, email, vars, options, schedule_time)
40
+ end
41
+
42
+ def get_send(send_id)
43
+ @client.get_send(send_id)
44
+ end
45
+
46
+ def schedule_blast(name, list, schedule_time, from_name, from_email, subject, content_html, content_text, options)
47
+ @client.schedule_blast(name, list, schedule_time, from_name, from_email, subject, content_html, content_text, options)
48
+ end
49
+
50
+ def get_blast(blast_id)
51
+ @client.get_blast(blast_id)
52
+ end
53
+
54
+ def get_email(email)
55
+ @client.get_email(email)
56
+ end
57
+
58
+ def set_email(email, vars = {}, lists = {}, templates = {})
59
+ @client.set_email(email, vars, lists, templates)
60
+ end
61
+
62
+ def import_contacts(email, password, with_names = false)
63
+ @client.import_contacts(email, password, with_names)
64
+ end
65
+
66
+ def get_template(template_name)
67
+ @client.get_template(template_name)
68
+ end
69
+
70
+ def save_template(template_name, template_fields)
71
+ @client.save_template(template_name, template_fields)
72
+ end
73
+
74
+ def receive_verify_post(params, request)
75
+ @client.receive_verify_post(params, request)
76
+ end
77
+
78
+ protected
79
+ def normalize_vars(vars)
80
+ result = {:first_name => "", :last_name => ""}
81
+ result[:first_name] = vars[:first_name] if vars_has_this?(vars, :first_name)
82
+ result[:last_name] = vars[:last_name] if vars_has_this?(vars, :last_name)
83
+ result
84
+ end
85
+
86
+ def vars_has_this?(vars, key)
87
+ return vars.has_key?(key) && !vars[key].blank?
88
+ end
89
+
90
+ def mailing_list_key
91
+ @config['mailing_list_key'] || 'main_list'
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,351 @@
1
+ ################################################################################
2
+ # Triggermail API Ruby Client
3
+ ################################################################################
4
+ #
5
+ # A simple client library to remotely access the Triggermail REST API.
6
+ #
7
+ ################################################################################
8
+ #
9
+ # Copyright (c) 2007 Sailthru, Inc.
10
+ # All rights reserved.
11
+ #
12
+ # Special thanks to the iminlikewithyou.com team for the development
13
+ # of this library.
14
+ #
15
+ # Redistribution and use in source and binary forms, with or without
16
+ # modification, are permitted provided that the following conditions
17
+ # are met:
18
+ #
19
+ # 1. Redistributions of source code must retain the above copyright
20
+ # notice, this list of conditions and the following disclaimer.
21
+ # 2. Redistributions in binary form must reproduce the above copyright
22
+ # notice, this list of conditions and the following disclaimer in the
23
+ # documentation and/or other materials provided with the distribution.
24
+ #
25
+ # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
26
+ # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
27
+ # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
28
+ # IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
29
+ # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
30
+ # NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
31
+ # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
32
+ # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
33
+ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
34
+ # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35
+ #
36
+ ################################################################################
37
+
38
+ require 'net/http'
39
+ require 'uri'
40
+ require 'rubygems'
41
+ require 'json'
42
+ require 'md5'
43
+
44
+ module Sailthru
45
+
46
+ class TriggermailClientException < Exception
47
+ end
48
+
49
+ class TriggermailClient
50
+ attr_accessor :api_uri, :api_key, :secret, :version, :last_request
51
+
52
+ VERSION = '1.0'
53
+
54
+ # params:
55
+ # api_key, String
56
+ # secret, String
57
+ # api_uri, String
58
+ #
59
+ # Instantiate a new client; constructor optionally takes overrides for key/secret/uri.
60
+ def initialize(api_key, secret, api_uri)
61
+ @api_key = api_key
62
+ @secret = secret
63
+ @api_uri = api_uri
64
+ end
65
+
66
+ # params:
67
+ # template_name, String
68
+ # email, String
69
+ # replacements, Hash
70
+ # options, Hash
71
+ # replyto: override Reply-To header
72
+ # test: send as test email (subject line will be marked, will not count towards stats)
73
+ # schedule_time, String (see schedule_blast schedule_time in API docs)
74
+ # returns:
75
+ # Hash, response data from server
76
+ def send(template_name, email, vars, options = {}, schedule_time = nil)
77
+ post = {}
78
+ post[:template] = template_name
79
+ post[:email] = email
80
+ post[:vars] = vars
81
+ post[:options] = options
82
+ post[:schedule_time] = schedule_time if schedule_time
83
+ result = self.api_post(:send, post)
84
+ end
85
+
86
+
87
+ # params:
88
+ # send_id, Fixnum
89
+ # returns:
90
+ # Hash, response data from server
91
+ #
92
+ # Get the status of a send.
93
+ def get_send(send_id)
94
+ self.api_get(:send, {:send_id => send_id.to_s})
95
+ end
96
+
97
+ # params:
98
+ # name, String
99
+ # list, String
100
+ # schedule_time, String
101
+ # from_name, String
102
+ # from_email, String
103
+ # subject, String
104
+ # content_html, String
105
+ # content_text, String
106
+ # options, Hash
107
+ # returns:
108
+ # Hash, response data from server
109
+ #
110
+ # Schedule a mass mail blast
111
+ def schedule_blast(name, list, schedule_time, from_name, from_email, subject, content_html, content_text, options)
112
+ post = options ? options : {}
113
+ post[:name] = name
114
+ post[:list] = list
115
+ post[:schedule_time] = schedule_time
116
+ post[:from_name] = from_name
117
+ post[:from_email] = from_email
118
+ post[:subject] = subject
119
+ post[:content_html] = content_html
120
+ post[:content_text] = content_text
121
+ self.api_post(:blast, post)
122
+ end
123
+
124
+
125
+ # params:
126
+ # blast_id, Fixnum
127
+ # returns:
128
+ # Hash, response data from server
129
+ #
130
+ # Get information on a previously scheduled email blast
131
+ def get_blast(blast_id)
132
+ self.api_get(:blast, {:blast_id => blast_id.to_s})
133
+ end
134
+
135
+ # params:
136
+ # email, String
137
+ # returns:
138
+ # Hash, response data from server
139
+ #
140
+ # Return information about an email address, including replacement vars and lists.
141
+ def get_email(email)
142
+ self.api_get(:email, {:email => email})
143
+ end
144
+
145
+ # params:
146
+ # email, String
147
+ # vars, Hash
148
+ # lists, Hash mapping list name => 1 for subscribed, 0 for unsubscribed
149
+ # returns:
150
+ # Hash, response data from server
151
+ #
152
+ # Set replacement vars and/or list subscriptions for an email address.
153
+ def set_email(email, vars = {}, lists = {}, templates = {})
154
+ data = {:email => email}
155
+ data[:vars] = vars unless vars.empty?
156
+ data[:lists] = lists unless lists.empty?
157
+ data[:templates] = templates unless templates.empty?
158
+ self.api_post(:email, data)
159
+ end
160
+
161
+ # params:
162
+ # email, String
163
+ # password, String
164
+ # with_names, Boolean
165
+ # returns:
166
+ # Hash, response data from server
167
+ #
168
+ # Fetch email contacts from an address book at one of the major email providers (aol/gmail/hotmail/yahoo)
169
+ # Use the with_names parameter if you want to fetch the contact names as well as emails
170
+ def import_contacts(email, password, with_names = false)
171
+ data = { :email => email, :password => password }
172
+ data[:names] = 1 if with_names
173
+ self.api_post(:contacts, data)
174
+ end
175
+
176
+
177
+ # params:
178
+ # template_name, String
179
+ # returns:
180
+ # Hash of response data.
181
+ #
182
+ # Get a template.
183
+ def get_template(template_name)
184
+ self.api_get(:template, {:template => template_name})
185
+ end
186
+
187
+
188
+ # params:
189
+ # template_name, String
190
+ # template_fields, Hash
191
+ # returns:
192
+ # Hash containg response from the server.
193
+ #
194
+ # Save a template.
195
+ def save_template(template_name, template_fields)
196
+ data = template_fields
197
+ data[:template] = template_name
198
+ self.api_post(:template, data)
199
+ end
200
+
201
+
202
+ # params:
203
+ # params, Hash
204
+ # request, String
205
+ # returns:
206
+ # TrueClass or FalseClass, Returns true if the incoming request is an authenticated verify post.
207
+ def receive_verify_post(params, request)
208
+ if request.post?
209
+ [:action, :email, :send_id, :sig].each { |key| return false unless params.has_key?(key) }
210
+
211
+ return false unless params[:action] == :verify
212
+
213
+ sig = params[:sig]
214
+ params.delete(:sig)
215
+ return false unless sig == TriggermailClient.get_signature_hash(params, @secret)
216
+
217
+ _send = self.get_send(params[:send_id])
218
+ return false unless _send.has_key?(:email)
219
+
220
+ return false unless _send[:email] == params[:email]
221
+
222
+ return true
223
+ else
224
+ return false
225
+ end
226
+ end
227
+
228
+
229
+ # Perform API GET request
230
+ def api_get(action, data)
231
+ api_request(action, data, 'GET')
232
+ end
233
+
234
+ # Perform API POST request
235
+ def api_post(action, data)
236
+ api_request(action, data, 'POST')
237
+ end
238
+
239
+ # params:
240
+ # action, String
241
+ # data, Hash
242
+ # request, String "GET" or "POST"
243
+ # returns:
244
+ # Hash
245
+ #
246
+ # Perform an API request, using the shared-secret auth hash.
247
+ #
248
+ def api_request(action, data, request_type)
249
+ data[:api_key] = @api_key
250
+ data[:format] ||= 'json'
251
+ data[:sig] = Sailthru::TriggermailClient.get_signature_hash(data, @secret)
252
+ _result = self.http_request("#{@api_uri}/#{action}", data, request_type)
253
+
254
+
255
+ # NOTE: don't do the unserialize here
256
+ unserialized = JSON.parse(_result)
257
+ return unserialized ? unserialized : _result
258
+ end
259
+
260
+
261
+ # params:
262
+ # uri, String
263
+ # data, Hash
264
+ # method, String "GET" or "POST"
265
+ # returns:
266
+ # String, body of response
267
+ def http_request(uri, data, method = 'POST')
268
+ data = flatten_nested_hash(data, false)
269
+ # puts data.inspect
270
+ if method == 'POST'
271
+ post_data = data
272
+ else
273
+ uri += "?" + data.map{ |key, value| "#{key}=#{value}" }.join("&")
274
+ end
275
+
276
+ req = nil
277
+ headers = {"User-Agent" => "Triggermail API Ruby Client #{VERSION}"}
278
+
279
+ _uri = URI.parse(uri)
280
+ if method == 'POST'
281
+ req = Net::HTTP::Post.new(_uri.path, headers)
282
+ req.set_form_data(data)
283
+ else
284
+ req = Net::HTTP::Get.new("#{_uri.path}?#{_uri.query}", headers)
285
+ end
286
+
287
+ @last_request = req
288
+ begin
289
+ response = Net::HTTP.start(_uri.host, _uri.port) {|http|
290
+ http.request(req)
291
+ }
292
+ rescue Exception => e
293
+ raise Sailthru::TriggermailClientException.new("Unable to open stream: #{_uri.to_s}");
294
+ end
295
+
296
+ if response.body
297
+ return response.body
298
+ else
299
+ raise Sailthru::TriggermailClientException.new("No response received from stream: #{_uri.to_s}")
300
+ end
301
+
302
+ end
303
+
304
+ # Flatten nested hash for GET / POST request.
305
+ def flatten_nested_hash(hash, brackets = true)
306
+ f = {}
307
+ hash.each do |key, value|
308
+ _key = brackets ? "[#{key}]" : key.to_s
309
+ if value.class == Hash
310
+ flatten_nested_hash(value).each do |k, v|
311
+ f["#{_key}#{k}"] = v
312
+ end
313
+ else
314
+ f[_key] = value
315
+ end
316
+ end
317
+ return f
318
+ end
319
+
320
+ # params:
321
+ # params, Hash
322
+ # returns:
323
+ # Array, values of each item in the Hash (and nested hashes)
324
+ #
325
+ # Extracts the values of a set of parameters, recursing into nested assoc arrays.
326
+ def self.extract_param_values(params)
327
+ values = []
328
+ params.each do |k, v|
329
+ # puts "k,v: #{k}, #{v}"
330
+ if v.class == Hash
331
+ values.concat Sailthru::TriggermailClient.extract_param_values(v)
332
+ else
333
+ values.push v
334
+ end
335
+ end
336
+ return values
337
+ end
338
+
339
+
340
+ # params:
341
+ # params, Hash
342
+ # secret, String
343
+ # returns:
344
+ # String, an MD5 hash of the secret + sorted list of parameter values for an API call.
345
+ def self.get_signature_hash(params, secret)
346
+ string = secret + self.extract_param_values(params).sort_by{|x| x.to_s}.join("")
347
+ MD5.md5(string) # debuggin
348
+ end
349
+
350
+ end
351
+ end
data/sailthru.gemspec ADDED
@@ -0,0 +1,36 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = %q{sailthru}
3
+ s.version = "1.0.0"
4
+
5
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
6
+ s.authors = ["Ian Enders"]
7
+ s.date = %q{2010-04-02}
8
+ s.description = %q{Sailthru triggermail client.}
9
+ s.email = %q{ian.enders@gmail.com}
10
+ s.extra_rdoc_files = [
11
+ "README.txt"
12
+ ]
13
+ s.files = [
14
+ ".gitignore",
15
+ "README.txt",
16
+ "VERSION",
17
+ "lib/sailthru/client.rb",
18
+ "lib/sailthru/triggermail_client.rb",
19
+ "sailthru.gemspec"
20
+ ]
21
+ s.homepage = %q{http://github.com/ienders/sailthru}
22
+ s.rdoc_options = ["--charset=UTF-8"]
23
+ s.require_paths = ["lib"]
24
+ s.rubygems_version = %q{1.3.6}
25
+ s.summary = %q{Gemified Sailthru Client Library}
26
+
27
+ if s.respond_to? :specification_version then
28
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
29
+ s.specification_version = 3
30
+
31
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
32
+ else
33
+ end
34
+ else
35
+ end
36
+ end
metadata ADDED
@@ -0,0 +1,67 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sailthru
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 1
7
+ - 0
8
+ - 0
9
+ version: 1.0.0
10
+ platform: ruby
11
+ authors:
12
+ - Ian Enders
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-04-02 00:00:00 -04:00
18
+ default_executable:
19
+ dependencies: []
20
+
21
+ description: Sailthru triggermail client.
22
+ email: ian.enders@gmail.com
23
+ executables: []
24
+
25
+ extensions: []
26
+
27
+ extra_rdoc_files:
28
+ - README.txt
29
+ files:
30
+ - .gitignore
31
+ - README.txt
32
+ - VERSION
33
+ - lib/sailthru/client.rb
34
+ - lib/sailthru/triggermail_client.rb
35
+ - sailthru.gemspec
36
+ has_rdoc: true
37
+ homepage: http://github.com/ienders/sailthru
38
+ licenses: []
39
+
40
+ post_install_message:
41
+ rdoc_options:
42
+ - --charset=UTF-8
43
+ require_paths:
44
+ - lib
45
+ required_ruby_version: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ segments:
50
+ - 0
51
+ version: "0"
52
+ required_rubygems_version: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ segments:
57
+ - 0
58
+ version: "0"
59
+ requirements: []
60
+
61
+ rubyforge_project:
62
+ rubygems_version: 1.3.6
63
+ signing_key:
64
+ specification_version: 3
65
+ summary: Gemified Sailthru Client Library
66
+ test_files: []
67
+