axtro-aws-ses 0.4.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,45 @@
1
+ #:stopdoc:
2
+ module AWS
3
+ module SES
4
+ module ExpirableMemoize
5
+ module InstanceMethods
6
+ def __method__(depth = 0)
7
+ caller[depth][/`([^']+)'/, 1]
8
+ end if RUBY_VERSION <= '1.8.7'
9
+
10
+ def __called_from__
11
+ caller[1][/`([^']+)'/, 1]
12
+ end if RUBY_VERSION > '1.8.7'
13
+
14
+ def expirable_memoize(reload = false, storage = nil)
15
+ current_method = RUBY_VERSION > '1.8.7' ? __called_from__ : __method__(1)
16
+ storage = "@#{storage || current_method}"
17
+ if reload
18
+ instance_variable_set(storage, nil)
19
+ else
20
+ if cache = instance_variable_get(storage)
21
+ return cache
22
+ end
23
+ end
24
+ instance_variable_set(storage, yield)
25
+ end
26
+ end
27
+
28
+ module ClassMethods
29
+ def memoized(method_name)
30
+ original_method = "unmemoized_#{method_name}_#{Time.now.to_i}"
31
+ alias_method original_method, method_name
32
+ module_eval(<<-EVAL, __FILE__, __LINE__)
33
+ def #{method_name}(reload = false, *args, &block)
34
+ expirable_memoize(reload) do
35
+ send(:#{original_method}, *args, &block)
36
+ end
37
+ end
38
+ EVAL
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+
45
+ #:startdoc:
@@ -0,0 +1,103 @@
1
+ module AWS
2
+ module SES
3
+ # Adds functionality for the statistics and info send quota data that Amazon SES makes available
4
+ #
5
+ # You can access these methods as follows:
6
+ #
7
+ # ses = AWS::SES::Base.new( ... connection info ... )
8
+ #
9
+ # == Get the quota information
10
+ # response = ses.quota
11
+ # # How many e-mails you've sent in the last 24 hours
12
+ # response.sent_last_24_hours
13
+ # # How many e-mails you're allowed to send in 24 hours
14
+ # response.max_24_hour_send
15
+ # # How many e-mails you can send per second
16
+ # response.max_send_rate
17
+ #
18
+ # == Get detailed send statistics
19
+ # The result is a list of data points, representing the last two weeks of sending activity.
20
+ # Each data point in the list contains statistics for a 15-minute interval.
21
+ # GetSendStatisticsResponse#data_points is an array where each element is a hash with give string keys:
22
+ #
23
+ # * +Bounces+
24
+ # * +DeliveryAttempts+
25
+ # * +Rejects+
26
+ # * +Complaints+
27
+ # * +Timestamp+
28
+ #
29
+ # response = ses.statistics
30
+ # response.data_points # =>
31
+ # [{"Bounces"=>"0",
32
+ # "Timestamp"=>"2011-01-26T16:30:00Z",
33
+ # "DeliveryAttempts"=>"1",
34
+ # "Rejects"=>"0",
35
+ # "Complaints"=>"0"},
36
+ # {"Bounces"=>"0",
37
+ # "Timestamp"=>"2011-02-09T14:45:00Z",
38
+ # "DeliveryAttempts"=>"3",
39
+ # "Rejects"=>"0",
40
+ # "Complaints"=>"0"},
41
+ # {"Bounces"=>"0",
42
+ # "Timestamp"=>"2011-01-31T15:30:00Z",
43
+ # "DeliveryAttempts"=>"3",
44
+ # "Rejects"=>"0",
45
+ # "Complaints"=>"0"},
46
+ # {"Bounces"=>"0",
47
+ # "Timestamp"=>"2011-01-31T16:00:00Z",
48
+ # "DeliveryAttempts"=>"3",
49
+ # "Rejects"=>"0",
50
+ # "Complaints"=>"0"}]
51
+
52
+ module Info
53
+ include AWS::SES::ExpirableMemoize::InstanceMethods
54
+ extend AWS::SES::ExpirableMemoize::ClassMethods
55
+
56
+ # Returns quota information provided by SES
57
+ #
58
+ # The return format inside the response result will look like:
59
+ # {"SentLast24Hours"=>"0.0", "MaxSendRate"=>"1.0", "Max24HourSend"=>"200.0"}
60
+ def quota
61
+ request('GetSendQuota')
62
+ end
63
+
64
+ def statistics
65
+ request('GetSendStatistics')
66
+ end
67
+ end
68
+
69
+ class GetSendQuotaResponse < AWS::SES::Response
70
+ def result
71
+ parsed['GetSendQuotaResult']
72
+ end
73
+
74
+ def sent_last_24_hours
75
+ result['SentLast24Hours']
76
+ end
77
+
78
+ def max_24_hour_send
79
+ result['Max24HourSend']
80
+ end
81
+
82
+ def max_send_rate
83
+ result['MaxSendRate']
84
+ end
85
+ end
86
+
87
+ class GetSendStatisticsResponse < AWS::SES::Response
88
+ def result
89
+ if members = parsed['GetSendStatisticsResult']['SendDataPoints']
90
+ [members['member']].flatten
91
+ else
92
+ []
93
+ end
94
+ end
95
+
96
+ memoized :result
97
+
98
+ def data_points
99
+ result
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,113 @@
1
+ module AWS
2
+ module SES
3
+ class Response < String
4
+ include AWS::SES::ExpirableMemoize::InstanceMethods
5
+ extend AWS::SES::ExpirableMemoize::ClassMethods
6
+
7
+ attr_reader :response, :body, :parsed, :action
8
+
9
+ def initialize(action, response)
10
+ @action = action
11
+ @response = response
12
+ @body = response.body.to_s
13
+ super(body)
14
+ end
15
+
16
+ def headers
17
+ headers = {}
18
+ response.each do |header, value|
19
+ headers[header] = value
20
+ end
21
+ headers
22
+ end
23
+ memoized :headers
24
+
25
+ def [](header)
26
+ headers[header]
27
+ end
28
+
29
+ def each(&block)
30
+ headers.each(&block)
31
+ end
32
+
33
+ def code
34
+ response.code.to_i
35
+ end
36
+
37
+ {:success => 200..299, :redirect => 300..399,
38
+ :client_error => 400..499, :server_error => 500..599}.each do |result, code_range|
39
+ class_eval(<<-EVAL, __FILE__, __LINE__)
40
+ def #{result}?
41
+ return false unless response
42
+ (#{code_range}).include? code
43
+ end
44
+ EVAL
45
+ end
46
+
47
+ def error?
48
+ !success? && (response['content-type'] == 'application/xml' || response['content-type'] == 'text/xml')
49
+ end
50
+
51
+ def error
52
+ parsed['Error']
53
+ end
54
+ memoized :error
55
+
56
+ def parsed
57
+ parse_options = { 'forcearray' => ['item', 'member'], 'suppressempty' => nil, 'keeproot' => false }
58
+ # parse_options = { 'suppressempty' => nil, 'keeproot' => false }
59
+
60
+ XmlSimple.xml_in(body, parse_options)
61
+ end
62
+ memoized :parsed
63
+
64
+ # It's expected that each subclass of Response will override this method with what part of response is relevant
65
+ def result
66
+ parsed
67
+ end
68
+
69
+ def request_id
70
+ error? ? parsed['RequestId'] : parsed['ResponseMetadata']['RequestId']
71
+ end
72
+
73
+ def inspect
74
+ "#<%s:0x%s %s %s %s>" % [self.class, object_id, request_id, response.code, response.message]
75
+ end
76
+ end # class Response
77
+
78
+ # Requests whose response code is between 300 and 599 and contain an <Error></Error> in their body
79
+ # are wrapped in an Error::Response. This Error::Response contains an Error object which raises an exception
80
+ # that corresponds to the error in the response body. The exception object contains the ErrorResponse, so
81
+ # in all cases where a request happens, you can rescue ResponseError and have access to the ErrorResponse and
82
+ # its Error object which contains information about the ResponseError.
83
+ #
84
+ # begin
85
+ # Bucket.create(..)
86
+ # rescue ResponseError => exception
87
+ # exception.response
88
+ # # => <Error::Response>
89
+ # exception.response.error
90
+ # # => <Error>
91
+ # end
92
+ class ResponseError < StandardError
93
+ attr_reader :response
94
+ def initialize(response)
95
+ @response = response
96
+ super("AWS::SES Response Error: #{message}")
97
+ end
98
+
99
+ def code
100
+ @response.code
101
+ end
102
+
103
+ def message
104
+ "#{@response.error['Code']} - #{@response.error['Message']}"
105
+ end
106
+
107
+ def inspect
108
+ "#<%s:0x%s %s %s '%s'>" % [self.class.name, object_id, @response.request_id, code, message]
109
+ end
110
+ end
111
+ end # module SES
112
+ end # module AWS
113
+
@@ -0,0 +1,156 @@
1
+ module AWS
2
+ module SES
3
+ # Adds functionality for send_email and send_raw_email
4
+ # Use the following to send an e-mail:
5
+ #
6
+ # ses = AWS::SES::Base.new( ... connection info ... )
7
+ # ses.send_email :to => ['jon@example.com', 'dave@example.com'],
8
+ # :source => '"Steve Smith" <steve@example.com>',
9
+ # :subject => 'Subject Line'
10
+ # :text_body => 'Internal text body'
11
+ #
12
+ # By default, the email "from" display address is whatever is before the @.
13
+ # To change the display from, use the format:
14
+ #
15
+ # "Steve Smith" <steve@example.com>
16
+ #
17
+ # You can also send Mail objects using send_raw_email:
18
+ #
19
+ # m = Mail.new( :to => ..., :from => ... )
20
+ # ses.send_raw_email(m)
21
+ #
22
+ # send_raw_email will also take a hash and pass it through Mail.new automatically as well.
23
+ #
24
+ module SendEmail
25
+
26
+ # Sends an email through SES
27
+ #
28
+ # the destination parameters can be:
29
+ #
30
+ # [A single e-mail string] "jon@example.com"
31
+ # [A array of e-mail addresses] ['jon@example.com', 'dave@example.com']
32
+ #
33
+ # ---
34
+ # = "Email address is not verified.MessageRejected (AWS::Error)"
35
+ # If you are receiving this message and you HAVE verified the [source] please <b>check to be sure you are not in sandbox mode!</b>
36
+ # If you have not been granted production access, you will have to <b>verify all recipients</b> as well.
37
+ # http://docs.amazonwebservices.com/ses/2010-12-01/DeveloperGuide/index.html?InitialSetup.Customer.html
38
+ # ---
39
+ #
40
+ # @option options [String] :source Source e-mail (from)
41
+ # @option options [String] :from alias for :source
42
+ # @option options [String] :to Destination e-mails
43
+ # @option options [String] :cc Destination e-mails
44
+ # @option options [String] :bcc Destination e-mails
45
+ # @option options [String] :subject
46
+ # @option options [String] :html_body
47
+ # @option options [String] :text_body
48
+ # @option options [String] :return_path The email address to which bounce notifications are to be forwarded. If the message cannot be delivered to the recipient, then an error message will be returned from the recipient's ISP; this message will then be forwarded to the email address specified by the ReturnPath parameter.
49
+ # @option options [String] :reply_to The reploy-to email address(es) for the message. If the recipient replies to the message, each reply-to address will receive the reply.
50
+ # @option options
51
+ # @return [Response] the response to sending this e-mail
52
+ def send_email(options = {})
53
+ package = {}
54
+
55
+ package['Source'] = options[:source] || options[:from]
56
+
57
+ add_array_to_hash!(package, 'Destination.ToAddresses', options[:to]) if options[:to]
58
+ add_array_to_hash!(package, 'Destination.CcAddresses', options[:cc]) if options[:cc]
59
+ add_array_to_hash!(package, 'Destination.BccAddresses', options[:bcc]) if options[:bcc]
60
+
61
+ package['Message.Subject.Data'] = options[:subject]
62
+
63
+ package['Message.Body.Html.Data'] = options[:html_body] if options[:html_body]
64
+ package['Message.Body.Text.Data'] = options[:text_body] || options[:body] if options[:text_body] || options[:body]
65
+
66
+ package['ReturnPath'] = options[:return_path] if options[:return_path]
67
+
68
+ add_array_to_hash!(package, 'ReplyToAddresses', options[:reply_to]) if options[:reply_to]
69
+
70
+ request('SendEmail', package)
71
+ end
72
+
73
+ # Sends using the SendRawEmail method
74
+ # This gives the most control and flexibility
75
+ #
76
+ # This uses the underlying Mail object from the mail gem
77
+ # You can pass in a Mail object, a Hash of params that will be parsed by Mail.new, or just a string
78
+ #
79
+ # Note that the params are different from send_email
80
+ # Specifically, the following fields from send_email will NOT work:
81
+ #
82
+ # * :source
83
+ # * :html_body
84
+ # * :text_body
85
+ #
86
+ # send_email accepts the aliases of :from & :body in order to be more compatible with the Mail gem
87
+ #
88
+ # This method is aliased as deliver and deliver! for compatibility (especially with Rails)
89
+ #
90
+ # @option mail [String] A raw string that is a properly formatted e-mail message
91
+ # @option mail [Hash] A hash that will be parsed by Mail.new
92
+ # @option mail [Mail] A mail object, ready to be encoded
93
+ # @option args [String] :source The sender's email address
94
+ # @option args [String] :destinations A list of destinations for the message.
95
+ # @option args [String] :from alias for :source
96
+ # @option args [String] :to alias for :destinations
97
+ # @return [Response]
98
+ def send_raw_email(mail, args = {})
99
+ message = mail_to_raw_message(mail)
100
+ package = { 'RawMessage.Data' => Base64::encode64(message) }
101
+ package['Source'] = args[:from] if args[:from]
102
+ package['Source'] = args[:source] if args[:source]
103
+ if args[:destinations]
104
+ add_array_to_hash!(package, 'Destinations', args[:destinations])
105
+ else
106
+ add_array_to_hash!(package, 'Destinations', args[:to]) if args[:to]
107
+ end
108
+ request('SendRawEmail', package)
109
+ end
110
+
111
+ alias :deliver! :send_raw_email
112
+ alias :deliver :send_raw_email
113
+
114
+ private
115
+
116
+ def mail_to_raw_message(mail)
117
+ if mail.is_a?(Hash)
118
+ if defined?(::Mail)
119
+ Mail.new(mail).to_s
120
+ else
121
+ raise "To use #send_raw_email with a hash, please install the Mail gem"
122
+ end
123
+ else
124
+ mail.to_s
125
+ end
126
+ end
127
+
128
+ # Adds all elements of the ary with the appropriate member elements
129
+ def add_array_to_hash!(hash, key, ary)
130
+ cnt = 1
131
+ [*ary].each do |o|
132
+ hash["#{key}.member.#{cnt}"] = o
133
+ cnt += 1
134
+ end
135
+ end
136
+ end
137
+
138
+ class EmailResponse < AWS::SES::Response
139
+ def result
140
+ super["#{action}Result"]
141
+ end
142
+
143
+ def message_id
144
+ result['MessageId']
145
+ end
146
+ end
147
+
148
+ class SendEmailResponse < EmailResponse
149
+
150
+ end
151
+
152
+ class SendRawEmailResponse < EmailResponse
153
+
154
+ end
155
+ end
156
+ end
@@ -0,0 +1,12 @@
1
+ module AWS
2
+ module SES
3
+ module VERSION #:nodoc:
4
+ MAJOR = '0'
5
+ MINOR = '4'
6
+ TINY = '2'
7
+ BETA = Time.now.to_i.to_s
8
+ end
9
+
10
+ Version = [VERSION::MAJOR, VERSION::MINOR, VERSION::TINY, VERSION::BETA].compact * '.'
11
+ end
12
+ end
@@ -0,0 +1,72 @@
1
+ require File.expand_path('../helper', __FILE__)
2
+
3
+ class AddressTest < Test::Unit::TestCase
4
+ context 'verifying an address' do
5
+ setup do
6
+ @base = generate_base
7
+ end
8
+
9
+ should 'return the correct response on success' do
10
+ mock_connection(@base, :body => %{
11
+ <VerifyEmailAddressResponse xmlns="http://ses.amazonaws.com/doc/2010-12-01/">
12
+ <ResponseMetadata>
13
+ <RequestId>abc-123</RequestId>
14
+ </ResponseMetadata>
15
+ </VerifyEmailAddressResponse>
16
+ })
17
+
18
+ result = @base.addresses.verify('user1@example.com')
19
+ assert result.success?
20
+ assert_equal 'abc-123', result.request_id
21
+ end
22
+ end
23
+
24
+ context 'listing verified addressess' do
25
+ setup do
26
+ @base = generate_base
27
+ end
28
+
29
+ should 'return the correct response on success' do
30
+ mock_connection(@base, :body => %{
31
+ <ListVerifiedEmailAddressesResponse xmlns="http://ses.amazonaws.com/doc/2010-12-01/">
32
+ <ListVerifiedEmailAddressesResult>
33
+ <VerifiedEmailAddresses>
34
+ <member>user1@example.com</member>
35
+ </VerifiedEmailAddresses>
36
+ </ListVerifiedEmailAddressesResult>
37
+ <ResponseMetadata>
38
+ <RequestId>abc-123</RequestId>
39
+ </ResponseMetadata>
40
+ </ListVerifiedEmailAddressesResponse>
41
+ })
42
+
43
+ result = @base.addresses.list
44
+
45
+ assert result.success?
46
+ assert_equal 'abc-123', result.request_id
47
+ assert_equal %w{user1@example.com}, result.result
48
+ end
49
+ end
50
+
51
+
52
+ context 'deleting a verified addressess' do
53
+ setup do
54
+ @base = generate_base
55
+ end
56
+
57
+ should 'return the correct response on success' do
58
+ mock_connection(@base, :body => %{
59
+ <DeleteVerifiedEmailAddressResponse xmlns="http://ses.amazonaws.com/doc/2010-12-01/">
60
+ <ResponseMetadata>
61
+ <RequestId>abc-123</RequestId>
62
+ </ResponseMetadata>
63
+ </DeleteVerifiedEmailAddressResponse>
64
+ })
65
+
66
+ result = @base.addresses.delete('user1@example.com')
67
+
68
+ assert result.success?
69
+ assert_equal 'abc-123', result.request_id
70
+ end
71
+ end
72
+ end