pat-maddox-sailthru 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Pat Maddox
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,7 @@
1
+ = sailthru
2
+
3
+ Description goes here.
4
+
5
+ == Copyright
6
+
7
+ Copyright (c) 2009 Pat Maddox. See LICENSE for details.
data/VERSION.yml ADDED
@@ -0,0 +1,4 @@
1
+ ---
2
+ :minor: 1
3
+ :patch: 1
4
+ :major: 0
@@ -0,0 +1,20 @@
1
+ module Sailthru
2
+ class CallbackResult
3
+ attr_reader :email
4
+
5
+ def initialize(options={})
6
+ @send_id = options["send_id"]
7
+ @email = options["email"]
8
+ end
9
+
10
+ def verified?
11
+ @email && response["email"] == @email
12
+ end
13
+
14
+ private
15
+ def response
16
+ return {} unless @send_id
17
+ @response ||= Sailthru.new_client.get_send(@send_id)
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,11 @@
1
+ module Sailthru
2
+ class Delivery
3
+ attr_reader :send_id, :email
4
+
5
+ def initialize(options={})
6
+ @send_id = options["send_id"]
7
+ @email = options["email"]
8
+ @options = options.clone
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,49 @@
1
+ module Sailthru
2
+ class Mailer
3
+ @deliveries = []
4
+ class << self
5
+ attr_reader :deliveries
6
+ end
7
+
8
+ def initialize
9
+ @replacements = { }
10
+ @options = { }
11
+ end
12
+
13
+ def deliver(client, template)
14
+ raise NoRecipientsSetError unless @recipients
15
+ result = client.send(@template || template, @recipients, @replacements, @options)
16
+ delivery = Delivery.new result
17
+ Mailer.deliveries << delivery
18
+ delivery
19
+ end
20
+
21
+ def recipients(*list)
22
+ @recipients = list.join(', ')
23
+ end
24
+
25
+ def template(name)
26
+ @template = name
27
+ end
28
+
29
+ def body(replacements)
30
+ @replacements.merge! replacements
31
+ end
32
+
33
+ def reply_to(email)
34
+ @options[:replyto] = email
35
+ end
36
+
37
+ class << self
38
+ def method_missing(m, *args, &block)
39
+ if /^deliver_(.*)$/ =~ m.to_s && instance_methods.include?($1)
40
+ message = new
41
+ message.send($1, *args, &block)
42
+ message.deliver Sailthru::new_client, $1
43
+ else
44
+ super
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,7 @@
1
+ module Sailthru
2
+ class FakeClient
3
+ def send(template, recipients, replacements, options)
4
+ {"send_id" => Guid.new.to_s, "email" => recipients}
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,339 @@
1
+ ################################################################################
2
+ #
3
+ # Copyright (c) 2007 Sailthru, Inc.
4
+ # All rights reserved.
5
+ #
6
+ # Special thanks to the iminlikewithyou.com team for the development
7
+ # of this library.
8
+ #
9
+ # Redistribution and use in source and binary forms, with or without
10
+ # modification, are permitted provided that the following conditions
11
+ # are met:
12
+ #
13
+ # 1. Redistributions of source code must retain the above copyright
14
+ # notice, this list of conditions and the following disclaimer.
15
+ # 2. Redistributions in binary form must reproduce the above copyright
16
+ # notice, this list of conditions and the following disclaimer in the
17
+ # documentation and/or other materials provided with the distribution.
18
+ #
19
+ # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
20
+ # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
21
+ # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
22
+ # IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
23
+ # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
24
+ # NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25
+ # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26
+ # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27
+ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28
+ # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
+ #
30
+ ################################################################################
31
+
32
+ require 'net/http'
33
+ require 'uri'
34
+ require 'rubygems'
35
+ require 'json'
36
+ require 'md5'
37
+
38
+ module Sailthru
39
+ class TriggermailClientException < Exception
40
+ end
41
+
42
+ class TriggermailClient
43
+ attr_accessor :api_uri, :api_key, :secret, :version, :last_request
44
+
45
+ # params:
46
+ # api_key, String
47
+ # secret, String
48
+ # api_uri, String
49
+ #
50
+ # Instantiate a new client; constructor optionally takes overrides for key/secret/uri.
51
+ def initialize(api_key =nil, secret=nil, api_uri=nil)
52
+ @api_key = api_key || API_KEY
53
+ @secret = secret || SECRET
54
+ @api_uri = api_uri || API_URI
55
+ end
56
+
57
+ # params:
58
+ # template_name, String
59
+ # email, String
60
+ # replacements, Hash
61
+ # options, Hash
62
+ # replyto: override Reply-To header
63
+ # test: send as test email (subject line will be marked, will not count towards stats)
64
+ # returns:
65
+ # Hash, response data from server
66
+ def send(template_name, email, vars, options = {})
67
+ post = {}
68
+ post[:template] = template_name
69
+ post[:email] = email
70
+ post[:vars] = vars
71
+ post[:options] = options
72
+ result = self.api_post(:send, post)
73
+ end
74
+
75
+
76
+ # params:
77
+ # send_id, Fixnum
78
+ # returns:
79
+ # Hash, response data from server
80
+ #
81
+ # Get the status of a send.
82
+ def get_send(send_id)
83
+ self.api_get(:send, {:send_id => send_id.to_s})
84
+ end
85
+
86
+ # params:
87
+ # name, String
88
+ # list, String
89
+ # schedule_time, String
90
+ # from_name, String
91
+ # from_email, String
92
+ # subject, String
93
+ # content_html, String
94
+ # content_text, String
95
+ # options, Hash
96
+ # returns:
97
+ # Hash, response data from server
98
+ #
99
+ # Schedule a mass mail blast
100
+ def schedule_blast(name, list, schedule_time, from_name, from_email, subject, content_html, content_text, options)
101
+ post = options ? options : {}
102
+ post[:name] = name
103
+ post[:list] = list
104
+ post[:schedule_time] = schedule_time
105
+ post[:from_name] = from_name
106
+ post[:from_email] = from_email
107
+ post[:subject] = subject
108
+ post[:content_html] = content_html
109
+ post[:content_text] = content_text
110
+ self.api_post(:blast, post)
111
+ end
112
+
113
+
114
+ # params:
115
+ # blast_id, Fixnum
116
+ # returns:
117
+ # Hash, response data from server
118
+ #
119
+ # Get information on a previously scheduled email blast
120
+ def get_blast(blast_id)
121
+ self.api_get(:blast, {:blast_id => blast_id.to_s})
122
+ end
123
+
124
+ # params:
125
+ # email, String
126
+ # returns:
127
+ # Hash, response data from server
128
+ #
129
+ # Return information about an email address, including replacement vars and lists.
130
+ def get_email(email)
131
+ self.api_get(:email, {:email => email})
132
+ end
133
+
134
+ # params:
135
+ # email, String
136
+ # vars, Hash
137
+ # lists, Hash mapping list name => 1 for subscribed, 0 for unsubscribed
138
+ # returns:
139
+ # Hash, response data from server
140
+ #
141
+ # Set replacement vars and/or list subscriptions for an email address.
142
+ def set_email(email, vars = {}, lists = {})
143
+ data = {:email => email}
144
+ data[:vars] = vars unless vars.empty?
145
+ data[:lists] = lists unless lists.empty?
146
+ self.api_post(:email, data)
147
+ end
148
+
149
+ # params:
150
+ # email, String
151
+ # password, String
152
+ # with_names, Boolean
153
+ # returns:
154
+ # Hash, response data from server
155
+ #
156
+ # Fetch email contacts from an address book at one of the major email providers (aol/gmail/hotmail/yahoo)
157
+ # Use the with_names parameter if you want to fetch the contact names as well as emails
158
+ def import_contacts(email, password, with_names = false)
159
+ data = { :email => email, :password => password }
160
+ data[:names] = 1 if with_names
161
+ self.api_post(:contacts, data)
162
+ end
163
+
164
+
165
+ # params:
166
+ # template_name, String
167
+ # returns:
168
+ # Hash of response data.
169
+ #
170
+ # Get a template.
171
+ def get_template(template_name)
172
+ self.api_get(:template, {:template => template_name})
173
+ end
174
+
175
+
176
+ # params:
177
+ # template_name, String
178
+ # template_fields, Hash
179
+ # returns:
180
+ # Hash containg response from the server.
181
+ #
182
+ # Save a template.
183
+ def save_template(template_name, template_fields)
184
+ data = template_fields
185
+ data[:template] = template_name
186
+ self.api_post(:template, data)
187
+ end
188
+
189
+
190
+ # params:
191
+ # params, Hash
192
+ # request, String
193
+ # returns:
194
+ # TrueClass or FalseClass, Returns true if the incoming request is an authenticated verify post.
195
+ def receive_verify_post(params, request)
196
+ if request.post?
197
+ [:action, :email, :send_id, :sig].each { |key| return false unless params.has_key?(key) }
198
+
199
+ return false unless params[:action] == :verify
200
+
201
+ sig = params[:sig]
202
+ params.delete(:sig)
203
+ return false unless sig == TriggermailClient.get_signature_hash(params, @secret)
204
+
205
+ _send = self.get_send(params[:send_id])
206
+ return false unless _send.has_key?(:email)
207
+
208
+ return false unless _send[:email] == params[:email]
209
+
210
+ return true
211
+ else
212
+ return false
213
+ end
214
+ end
215
+
216
+
217
+ # Perform API GET request
218
+ def api_get(action, data)
219
+ api_request(action, data, 'GET')
220
+ end
221
+
222
+ # Perform API POST request
223
+ def api_post(action, data)
224
+ api_request(action, data, 'POST')
225
+ end
226
+
227
+ # params:
228
+ # action, String
229
+ # data, Hash
230
+ # request, String "GET" or "POST"
231
+ # returns:
232
+ # Hash
233
+ #
234
+ # Perform an API request, using the shared-secret auth hash.
235
+ #
236
+ def api_request(action, data, request_type)
237
+ data[:api_key] = @api_key
238
+ data[:format] ||= 'json'
239
+ data[:sig] = TriggermailClient.get_signature_hash(data, @secret)
240
+ _result = self.http_request("#{@api_uri}/#{action}", data, request_type)
241
+
242
+
243
+ # NOTE: don't do the unserialize here
244
+ unserialized = JSON.parse(_result)
245
+ return unserialized ? unserialized : _result
246
+ end
247
+
248
+
249
+ # params:
250
+ # uri, String
251
+ # data, Hash
252
+ # method, String "GET" or "POST"
253
+ # returns:
254
+ # String, body of response
255
+ def http_request(uri, data, method = 'POST')
256
+ data = flatten_nested_hash(data, false)
257
+ # puts data.inspect
258
+ if method == 'POST'
259
+ post_data = data
260
+ else
261
+ uri += "?" + data.map{ |key, value| "#{key}=#{value}" }.join("&")
262
+ end
263
+
264
+ req = nil
265
+ headers = {"User-Agent" => "Triggermail API Ruby Client #{VERSION}"}
266
+
267
+ _uri = URI.parse(uri)
268
+ if method == 'POST'
269
+ req = Net::HTTP::Post.new(_uri.path, headers)
270
+ req.set_form_data(data)
271
+ else
272
+ req = Net::HTTP::Get.new("#{_uri.path}?#{_uri.query}", headers)
273
+ end
274
+
275
+ @last_request = req
276
+ begin
277
+ response = Net::HTTP.start(_uri.host, _uri.port) {|http|
278
+ http.request(req)
279
+ }
280
+ rescue Exception => e
281
+ raise TriggermailClientException.new("Unable to open stream: #{_uri.to_s}");
282
+ end
283
+
284
+ if response.body
285
+ return response.body
286
+ else
287
+ raise TriggermailClientException.new("No response received from stream: #{_uri.to_s}")
288
+ end
289
+
290
+ end
291
+
292
+ # Flatten nested hash for GET / POST request.
293
+ def flatten_nested_hash(hash, brackets = true)
294
+ f = {}
295
+ hash.each do |key, value|
296
+ _key = brackets ? "[#{key}]" : key.to_s
297
+ if value.class == Hash
298
+ flatten_nested_hash(value).each do |k, v|
299
+ f["#{_key}#{k}"] = v
300
+ end
301
+ else
302
+ f[_key] = value
303
+ end
304
+ end
305
+ return f
306
+ end
307
+
308
+ # params:
309
+ # params, Hash
310
+ # returns:
311
+ # Array, values of each item in the Hash (and nested hashes)
312
+ #
313
+ # Extracts the values of a set of parameters, recursing into nested assoc arrays.
314
+ def self.extract_param_values(params)
315
+ values = []
316
+ params.each do |k, v|
317
+ # puts "k,v: #{k}, #{v}"
318
+ if v.class == Hash
319
+ values.concat TriggermailClient.extract_param_values(v)
320
+ else
321
+ values.push v
322
+ end
323
+ end
324
+ return values
325
+ end
326
+
327
+
328
+ # params:
329
+ # params, Hash
330
+ # secret, String
331
+ # returns:
332
+ # String, an MD5 hash of the secret + sorted list of parameter values for an API call.
333
+ def self.get_signature_hash(params, secret)
334
+ string = secret + self.extract_param_values(params).sort.join("")
335
+ MD5.md5(string) # debuggin
336
+ end
337
+
338
+ end
339
+ end
data/lib/sailthru.rb ADDED
@@ -0,0 +1,19 @@
1
+ require 'sailthru/triggermailer_client'
2
+ require 'sailthru/mailer'
3
+ require 'sailthru/delivery'
4
+ require 'sailthru/callback_result'
5
+
6
+ module Sailthru
7
+ API_URI = 'http://api.sailthru.com'
8
+ VERSION = '1.0'
9
+
10
+ class << self
11
+ attr_writer :mode
12
+
13
+ def new_client
14
+ @mode.to_s == "test" ? FakeClient.new : TriggermailClient.new
15
+ end
16
+ end
17
+
18
+ class NoRecipientsSetError < RuntimeError; end
19
+ end
@@ -0,0 +1,43 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe Sailthru::CallbackResult do
4
+ it "should not be verified when built without a send_id" do
5
+ Sailthru::CallbackResult.new("email" => "foo").should_not be_verified
6
+ end
7
+
8
+ it "should not be verified when built without an email" do
9
+ Sailthru::CallbackResult.new("send_id" => "abc123").should_not be_verified
10
+ end
11
+
12
+ it "should expose the email" do
13
+ Sailthru::CallbackResult.new("email" => "foo").email.should == "foo"
14
+ end
15
+
16
+ describe "verified?" do
17
+ before(:each) do
18
+ @callback = Sailthru::CallbackResult.new "send_id" => "abc123", "email" => "pat@example.com"
19
+ @client = mock("client")
20
+ Sailthru.stub!(:new_client).and_return @client
21
+ end
22
+
23
+ it "should query the client once" do
24
+ @client.should_receive(:get_send).with("abc123").and_return({})
25
+ 2.times { @callback.verified? }
26
+ end
27
+
28
+ it "should be true if the response contains the email" do
29
+ @client.stub!(:get_send).and_return({"email" => "pat@example.com"})
30
+ @callback.should be_verified
31
+ end
32
+
33
+ it "should be false if the response does not contain the email" do
34
+ @client.stub!(:get_send).and_return({})
35
+ @callback.should_not be_verified
36
+ end
37
+
38
+ it "should be false if the response contains a different email" do
39
+ @client.stub!(:get_send).and_return({"email" => "wrong@address.com"})
40
+ @callback.should_not be_verified
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,25 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe Sailthru::Delivery do
4
+ describe "when sent" do
5
+ before(:each) do
6
+ @delivery = MyMailer.deliver_invitation 'pat.maddox@gmail.com'
7
+ end
8
+
9
+ it "should be a kind of delivery" do
10
+ @delivery.should be_instance_of(Sailthru::Delivery)
11
+ end
12
+
13
+ it "should add the message to the list of deliveries" do
14
+ Sailthru::Mailer.deliveries.should == [@delivery]
15
+ end
16
+
17
+ it "should expose the send_id" do
18
+ @delivery.send_id.should_not be_nil
19
+ end
20
+
21
+ it "should expose the email" do
22
+ @delivery.email.should_not be_nil
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,79 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+
4
+ describe Sailthru::Mailer do
5
+ it "should create a client" do
6
+ Sailthru.mode = :deliver
7
+ Sailthru.new_client.should be_kind_of(Sailthru::TriggermailClient)
8
+ end
9
+
10
+ describe "mode" do
11
+ it "should create a client when mode is deliver" do
12
+ Sailthru.mode = :deliver
13
+ c = stub("client", :null_object => true)
14
+ Sailthru::TriggermailClient.should_receive(:new).and_return c
15
+ MyMailer.deliver_invitation 'pat.maddox@gmail.com'
16
+ end
17
+
18
+ it "should not create a client when mode is test" do
19
+ Sailthru.mode = :test
20
+ Sailthru::TriggermailClient.should_not_receive(:new)
21
+ MyMailer.deliver_invitation 'pat.maddox@gmail.com'
22
+ end
23
+ end
24
+
25
+ describe "sending an email" do
26
+ before(:each) do
27
+ @mock_client = mock('client', :send => {:send_id => "abc123"})
28
+ Sailthru::TriggermailClient.stub!(:new).and_return @mock_client
29
+ Sailthru.mode = :deliver
30
+ end
31
+
32
+ it "should create a client for each email sent" do
33
+ c = stub("client", :null_object => true)
34
+ Sailthru::TriggermailClient.should_receive(:new).twice.and_return c
35
+ 2.times { MyMailer.deliver_invitation('pat@example.com') }
36
+ end
37
+
38
+ it "should infer the template name" do
39
+ @mock_client.should_receive(:send).with("invitation", anything, anything, anything)
40
+ MyMailer.deliver_invitation('pat@example.com')
41
+ end
42
+
43
+ it "should get the recipients from the mailer" do
44
+ @mock_client.should_receive(:send).with(anything, "pat@example.com", anything, anything)
45
+ MyMailer.deliver_invitation('pat@example.com')
46
+ end
47
+
48
+ it "should pass body options to the mailer" do
49
+ @mock_client.should_receive(:send).with(anything, anything, {:foo => "passed in"}, anything)
50
+ MyMailer.deliver_invitation('pat@example.com')
51
+ end
52
+
53
+ it "should pass reply-to to the mailer" do
54
+ @mock_client.should_receive(:send).with(anything, anything, anything, {:replyto => "admin@example.com"})
55
+ MyMailer.deliver_invitation('pat@example.com')
56
+ end
57
+
58
+ it "should allow the template to be overridden" do
59
+ @mock_client.should_receive(:send).with('super_template', anything, anything, anything)
60
+ MyMailer.deliver_with_custom_template
61
+ end
62
+
63
+ it "should blow up if recipients is not set" do
64
+ lambda { MyMailer.deliver_with_no_recipients }.
65
+ should raise_error(Sailthru::NoRecipientsSetError)
66
+ end
67
+
68
+ it "should send an empty hash if no body params are set" do
69
+ @mock_client.should_receive(:send).with(anything, anything, { }, anything)
70
+ MyMailer.deliver_with_no_body
71
+ end
72
+
73
+ it "should send an empty hash if reply-to is not set" do
74
+ @mock_client.should_receive(:send).with(anything, anything, anything, { })
75
+ MyMailer.deliver_with_no_reply_to
76
+ end
77
+ end
78
+ end
79
+
@@ -0,0 +1,45 @@
1
+ require 'rubygems'
2
+ require 'spec'
3
+
4
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
5
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
+ require 'sailthru'
7
+ require 'sailthru/test'
8
+ require 'guid'
9
+
10
+ Spec::Runner.configure do |config|
11
+ config.before(:each) do
12
+ Sailthru.mode = :test
13
+ Sailthru::Mailer.deliveries.clear
14
+ end
15
+ end
16
+
17
+ Sailthru::API_KEY = 'fake-api-key'
18
+ Sailthru::SECRET = 'fake-api-secret'
19
+
20
+ class MyMailer < Sailthru::Mailer
21
+ def invitation(email_address)
22
+ recipients email_address
23
+ body :foo => "passed in"
24
+ reply_to "admin@example.com"
25
+ end
26
+
27
+ def with_custom_template
28
+ template 'super_template'
29
+ recipients 'ignore@ignore.com'
30
+ end
31
+
32
+ def with_no_recipients
33
+ body :ignore => 'ignore'
34
+ end
35
+
36
+ def with_no_body
37
+ recipients "foo@blah.com"
38
+ reply_to "ignore"
39
+ end
40
+
41
+ def with_no_reply_to
42
+ recipients "foo@blah.com"
43
+ body :ignore => 'ignore'
44
+ end
45
+ end
metadata ADDED
@@ -0,0 +1,68 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pat-maddox-sailthru
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Pat Maddox
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-03-09 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description:
17
+ email: pat.maddox@gmail.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README.rdoc
24
+ - LICENSE
25
+ files:
26
+ - README.rdoc
27
+ - VERSION.yml
28
+ - lib/sailthru
29
+ - lib/sailthru/callback_result.rb
30
+ - lib/sailthru/delivery.rb
31
+ - lib/sailthru/mailer.rb
32
+ - lib/sailthru/test.rb
33
+ - lib/sailthru/triggermailer_client.rb
34
+ - lib/sailthru.rb
35
+ - spec/sailthru_callback_result_spec.rb
36
+ - spec/sailthru_delivery_spec.rb
37
+ - spec/sailthru_mailer_spec.rb
38
+ - spec/spec_helper.rb
39
+ - LICENSE
40
+ has_rdoc: true
41
+ homepage: http://github.com/pat-maddox/sailthru
42
+ post_install_message:
43
+ rdoc_options:
44
+ - --inline-source
45
+ - --charset=UTF-8
46
+ require_paths:
47
+ - lib
48
+ required_ruby_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: "0"
53
+ version:
54
+ required_rubygems_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: "0"
59
+ version:
60
+ requirements: []
61
+
62
+ rubyforge_project:
63
+ rubygems_version: 1.2.0
64
+ signing_key:
65
+ specification_version: 2
66
+ summary: TODO
67
+ test_files: []
68
+