sailthru 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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
+