aws-ses-rails31 0.4.3
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/CHANGELOG +37 -0
- data/Gemfile +19 -0
- data/Gemfile.lock +39 -0
- data/LICENSE +20 -0
- data/README.erb +92 -0
- data/README.rdoc +196 -0
- data/Rakefile +88 -0
- data/TODO +3 -0
- data/VERSION +1 -0
- data/aws-ses-rails31.gemspec +114 -0
- data/lib/aws/actionmailer/ses_extension.rb +19 -0
- data/lib/aws/ses/addresses.rb +75 -0
- data/lib/aws/ses/base.rb +176 -0
- data/lib/aws/ses/extensions.rb +39 -0
- data/lib/aws/ses/info.rb +100 -0
- data/lib/aws/ses/response.rb +110 -0
- data/lib/aws/ses/send_email.rb +144 -0
- data/lib/aws/ses/version.rb +12 -0
- data/lib/aws/ses.rb +28 -0
- data/test/address_test.rb +72 -0
- data/test/base_test.rb +40 -0
- data/test/extensions_test.rb +111 -0
- data/test/fixtures.rb +89 -0
- data/test/helper.rb +56 -0
- data/test/info_test.rb +108 -0
- data/test/mocks/fake_response.rb +26 -0
- data/test/response_test.rb +26 -0
- data/test/send_email_test.rb +67 -0
- metadata +222 -0
@@ -0,0 +1,75 @@
|
|
1
|
+
module AWS
|
2
|
+
module SES
|
3
|
+
# AWS::SES::Addresses provides for:
|
4
|
+
# * Listing verified e-mail addresses
|
5
|
+
# * Adding new e-mail addresses to verify
|
6
|
+
# * Deleting verified e-mail addresses
|
7
|
+
#
|
8
|
+
# You can access these methods as follows:
|
9
|
+
#
|
10
|
+
# ses = AWS::SES::Base.new( ... connection info ... )
|
11
|
+
#
|
12
|
+
# # Get a list of verified addresses
|
13
|
+
# ses.addresses.list.result
|
14
|
+
#
|
15
|
+
# # Add a new e-mail address to verify
|
16
|
+
# ses.addresses.verify('jon@example.com')
|
17
|
+
#
|
18
|
+
# # Delete an e-mail address
|
19
|
+
# ses.addresses.delete('jon@example.com')
|
20
|
+
class Addresses < Base
|
21
|
+
def initialize(ses)
|
22
|
+
@ses = ses
|
23
|
+
end
|
24
|
+
|
25
|
+
# List all verified e-mail addresses
|
26
|
+
#
|
27
|
+
# Usage:
|
28
|
+
# ses.addresses.list.result
|
29
|
+
# =>
|
30
|
+
# ['email1@example.com', email2@example.com']
|
31
|
+
def list
|
32
|
+
@ses.request('ListVerifiedEmailAddresses')
|
33
|
+
end
|
34
|
+
|
35
|
+
def verify(email)
|
36
|
+
@ses.request('VerifyEmailAddress',
|
37
|
+
'EmailAddress' => email
|
38
|
+
)
|
39
|
+
end
|
40
|
+
|
41
|
+
def delete(email)
|
42
|
+
@ses.request('DeleteVerifiedEmailAddress',
|
43
|
+
'EmailAddress' => email
|
44
|
+
)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
class ListVerifiedEmailAddressesResponse < AWS::SES::Response
|
49
|
+
def result
|
50
|
+
if members = parsed['ListVerifiedEmailAddressesResult']['VerifiedEmailAddresses']
|
51
|
+
[members['member']].flatten
|
52
|
+
else
|
53
|
+
[]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
memoized :result
|
57
|
+
end
|
58
|
+
|
59
|
+
class VerifyEmailAddressResponse < AWS::SES::Response
|
60
|
+
end
|
61
|
+
|
62
|
+
class DeleteVerifiedEmailAddressResponse < AWS::SES::Response
|
63
|
+
def result
|
64
|
+
success?
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
class Base
|
69
|
+
def addresses
|
70
|
+
@addresses ||= Addresses.new(self)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
end
|
data/lib/aws/ses/base.rb
ADDED
@@ -0,0 +1,176 @@
|
|
1
|
+
module AWS #:nodoc:
|
2
|
+
# AWS::SES is a Ruby library for Amazon's Simple Email Service's REST API (http://aws.amazon.com/ses).
|
3
|
+
#
|
4
|
+
# == Getting started
|
5
|
+
#
|
6
|
+
# To get started you need to require 'aws/ses':
|
7
|
+
#
|
8
|
+
# % irb -rubygems
|
9
|
+
# irb(main):001:0> require 'aws/ses'
|
10
|
+
# # => true
|
11
|
+
#
|
12
|
+
# Before you can do anything, you must establish a connection using Base.new. A basic connection would look something like this:
|
13
|
+
#
|
14
|
+
# ses = AWS::SES::Base.new(
|
15
|
+
# :access_key_id => 'abc',
|
16
|
+
# :secret_access_key => '123'
|
17
|
+
# )
|
18
|
+
#
|
19
|
+
# The minimum connection options that you must specify are your access key id and your secret access key.
|
20
|
+
module SES
|
21
|
+
|
22
|
+
API_VERSION = '2010-12-01'
|
23
|
+
|
24
|
+
DEFAULT_HOST = 'email.us-east-1.amazonaws.com'
|
25
|
+
|
26
|
+
USER_AGENT = 'github-aws-ses-ruby-gem'
|
27
|
+
|
28
|
+
# Encodes the given string with the secret_access_key by taking the
|
29
|
+
# hmac-sha1 sum, and then base64 encoding it. Optionally, it will also
|
30
|
+
# url encode the result of that to protect the string if it's going to
|
31
|
+
# be used as a query string parameter.
|
32
|
+
#
|
33
|
+
# @param [String] secret_access_key the user's secret access key for signing.
|
34
|
+
# @param [String] str the string to be hashed and encoded.
|
35
|
+
# @param [Boolean] urlencode whether or not to url encode the result., true or false
|
36
|
+
# @return [String] the signed and encoded string.
|
37
|
+
def SES.encode(secret_access_key, str, urlencode=true)
|
38
|
+
digest = OpenSSL::Digest::Digest.new('sha256')
|
39
|
+
b64_hmac =
|
40
|
+
Base64.encode64(
|
41
|
+
OpenSSL::HMAC.digest(digest, secret_access_key, str)).gsub("\n","")
|
42
|
+
|
43
|
+
if urlencode
|
44
|
+
return CGI::escape(b64_hmac)
|
45
|
+
else
|
46
|
+
return b64_hmac
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Generates the HTTP Header String that Amazon looks for
|
51
|
+
#
|
52
|
+
# @param [String] key the AWS Access Key ID
|
53
|
+
# @param [String] alg the algorithm used for the signature
|
54
|
+
# @param [String] sig the signature itself
|
55
|
+
def SES.authorization_header(key, alg, sig)
|
56
|
+
"AWS3-HTTPS AWSAccessKeyId=#{key}, Algorithm=#{alg}, Signature=#{sig}"
|
57
|
+
end
|
58
|
+
|
59
|
+
# AWS::SES::Base is the abstract super class of all classes who make requests against SES
|
60
|
+
class Base
|
61
|
+
include SendEmail
|
62
|
+
include Info
|
63
|
+
|
64
|
+
attr_reader :use_ssl, :server, :proxy_server, :port
|
65
|
+
|
66
|
+
# @option options [String] :access_key_id ("") The user's AWS Access Key ID
|
67
|
+
# @option options [String] :secret_access_key ("") The user's AWS Secret Access Key
|
68
|
+
# @option options [Boolean] :use_ssl (true) Connect using SSL?
|
69
|
+
# @option options [String] :server ("email.us-east-1.amazonaws.com") The server API endpoint host
|
70
|
+
# @option options [String] :proxy_server (nil) An HTTP proxy server FQDN
|
71
|
+
# @option options [String] :user_agent ("github-aws-ses-ruby-gem") The HTTP User-Agent header value
|
72
|
+
# @return [Object] the object.
|
73
|
+
def initialize( options = {} )
|
74
|
+
|
75
|
+
options = { :access_key_id => "",
|
76
|
+
:secret_access_key => "",
|
77
|
+
:use_ssl => true,
|
78
|
+
:server => DEFAULT_HOST,
|
79
|
+
:path => "/",
|
80
|
+
:user_agent => USER_AGENT,
|
81
|
+
:proxy_server => nil
|
82
|
+
}.merge(options)
|
83
|
+
|
84
|
+
@server = options[:server]
|
85
|
+
@proxy_server = options[:proxy_server]
|
86
|
+
@use_ssl = options[:use_ssl]
|
87
|
+
@path = options[:path]
|
88
|
+
@user_agent = options[:user_agent]
|
89
|
+
|
90
|
+
raise ArgumentError, "No :access_key_id provided" if options[:access_key_id].nil? || options[:access_key_id].empty?
|
91
|
+
raise ArgumentError, "No :secret_access_key provided" if options[:secret_access_key].nil? || options[:secret_access_key].empty?
|
92
|
+
raise ArgumentError, "No :use_ssl value provided" if options[:use_ssl].nil?
|
93
|
+
raise ArgumentError, "Invalid :use_ssl value provided, only 'true' or 'false' allowed" unless options[:use_ssl] == true || options[:use_ssl] == false
|
94
|
+
raise ArgumentError, "No :server provided" if options[:server].nil? || options[:server].empty?
|
95
|
+
|
96
|
+
if options[:port]
|
97
|
+
# user-specified port
|
98
|
+
@port = options[:port]
|
99
|
+
elsif @use_ssl
|
100
|
+
# https
|
101
|
+
@port = 443
|
102
|
+
else
|
103
|
+
# http
|
104
|
+
@port = 80
|
105
|
+
end
|
106
|
+
|
107
|
+
@access_key_id = options[:access_key_id]
|
108
|
+
@secret_access_key = options[:secret_access_key]
|
109
|
+
|
110
|
+
# Use proxy server if defined
|
111
|
+
# Based on patch by Mathias Dalheimer. 20070217
|
112
|
+
proxy = @proxy_server ? URI.parse(@proxy_server) : OpenStruct.new
|
113
|
+
@http = Net::HTTP::Proxy( proxy.host,
|
114
|
+
proxy.port,
|
115
|
+
proxy.user,
|
116
|
+
proxy.password).new(options[:server], @port)
|
117
|
+
|
118
|
+
@http.use_ssl = @use_ssl
|
119
|
+
|
120
|
+
# Don't verify the SSL certificates. Avoids SSL Cert warning in log on every GET.
|
121
|
+
@http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
122
|
+
|
123
|
+
end
|
124
|
+
|
125
|
+
attr_accessor :settings
|
126
|
+
|
127
|
+
def connection
|
128
|
+
@http
|
129
|
+
end
|
130
|
+
|
131
|
+
# Make the connection to AWS passing in our request.
|
132
|
+
# allow us to have a one line call in each method which will do all of the work
|
133
|
+
# in making the actual request to AWS.
|
134
|
+
def request(action, params = {})
|
135
|
+
# Use a copy so that we don't modify the caller's Hash, remove any keys that have nil or empty values
|
136
|
+
params = params.reject { |key, value| value.nil? or value.empty?}
|
137
|
+
|
138
|
+
timestamp = Time.now.getutc
|
139
|
+
|
140
|
+
params.merge!( {"Action" => action,
|
141
|
+
"SignatureVersion" => "2",
|
142
|
+
"SignatureMethod" => 'HmacSHA256',
|
143
|
+
"AWSAccessKeyId" => @access_key_id,
|
144
|
+
"Version" => API_VERSION,
|
145
|
+
"Timestamp" => timestamp.iso8601 } )
|
146
|
+
|
147
|
+
query = params.sort.collect do |param|
|
148
|
+
CGI::escape(param[0]) + "=" + CGI::escape(param[1])
|
149
|
+
end.join("&")
|
150
|
+
|
151
|
+
req = {}
|
152
|
+
|
153
|
+
req['X-Amzn-Authorization'] = get_aws_auth_param(timestamp.httpdate, @secret_access_key)
|
154
|
+
req['Date'] = timestamp.httpdate
|
155
|
+
req['User-Agent'] = @user_agent
|
156
|
+
|
157
|
+
response = connection.post(@path, query, req)
|
158
|
+
|
159
|
+
response_class = AWS::SES.const_get( "#{action}Response" )
|
160
|
+
result = response_class.new(action, response)
|
161
|
+
|
162
|
+
if result.error?
|
163
|
+
raise ResponseError.new(result)
|
164
|
+
end
|
165
|
+
|
166
|
+
result
|
167
|
+
end
|
168
|
+
|
169
|
+
# Set the Authorization header using AWS signed header authentication
|
170
|
+
def get_aws_auth_param(timestamp, secret_access_key)
|
171
|
+
encoded_canonical = SES.encode(secret_access_key, timestamp, false)
|
172
|
+
SES.authorization_header(@access_key_id, 'HmacSHA256', encoded_canonical)
|
173
|
+
end
|
174
|
+
end # class Base
|
175
|
+
end # Module SES
|
176
|
+
end # Module AWS
|
@@ -0,0 +1,39 @@
|
|
1
|
+
#:stopdoc:
|
2
|
+
module Kernel
|
3
|
+
def __method__(depth = 0)
|
4
|
+
caller[depth][/`([^']+)'/, 1]
|
5
|
+
end if RUBY_VERSION <= '1.8.7'
|
6
|
+
|
7
|
+
def __called_from__
|
8
|
+
caller[1][/`([^']+)'/, 1]
|
9
|
+
end if RUBY_VERSION > '1.8.7'
|
10
|
+
|
11
|
+
def expirable_memoize(reload = false, storage = nil)
|
12
|
+
current_method = RUBY_VERSION > '1.8.7' ? __called_from__ : __method__(1)
|
13
|
+
storage = "@#{storage || current_method}"
|
14
|
+
if reload
|
15
|
+
instance_variable_set(storage, nil)
|
16
|
+
else
|
17
|
+
if cache = instance_variable_get(storage)
|
18
|
+
return cache
|
19
|
+
end
|
20
|
+
end
|
21
|
+
instance_variable_set(storage, yield)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class Module
|
26
|
+
def memoized(method_name)
|
27
|
+
original_method = "unmemoized_#{method_name}_#{Time.now.to_i}"
|
28
|
+
alias_method original_method, method_name
|
29
|
+
module_eval(<<-EVAL, __FILE__, __LINE__)
|
30
|
+
def #{method_name}(reload = false, *args, &block)
|
31
|
+
expirable_memoize(reload) do
|
32
|
+
send(:#{original_method}, *args, &block)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
EVAL
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
#:startdoc:
|
data/lib/aws/ses/info.rb
ADDED
@@ -0,0 +1,100 @@
|
|
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
|
+
# Returns quota information provided by SES
|
54
|
+
#
|
55
|
+
# The return format inside the response result will look like:
|
56
|
+
# {"SentLast24Hours"=>"0.0", "MaxSendRate"=>"1.0", "Max24HourSend"=>"200.0"}
|
57
|
+
def quota
|
58
|
+
request('GetSendQuota')
|
59
|
+
end
|
60
|
+
|
61
|
+
def statistics
|
62
|
+
request('GetSendStatistics')
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
class GetSendQuotaResponse < AWS::SES::Response
|
67
|
+
def result
|
68
|
+
parsed['GetSendQuotaResult']
|
69
|
+
end
|
70
|
+
|
71
|
+
def sent_last_24_hours
|
72
|
+
result['SentLast24Hours']
|
73
|
+
end
|
74
|
+
|
75
|
+
def max_24_hour_send
|
76
|
+
result['Max24HourSend']
|
77
|
+
end
|
78
|
+
|
79
|
+
def max_send_rate
|
80
|
+
result['MaxSendRate']
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
class GetSendStatisticsResponse < AWS::SES::Response
|
85
|
+
def result
|
86
|
+
if members = parsed['GetSendStatisticsResult']['SendDataPoints']
|
87
|
+
[members['member']].flatten
|
88
|
+
else
|
89
|
+
[]
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
memoized :result
|
94
|
+
|
95
|
+
def data_points
|
96
|
+
result
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
module AWS
|
2
|
+
module SES
|
3
|
+
class Response < String
|
4
|
+
attr_reader :response, :body, :parsed, :action
|
5
|
+
|
6
|
+
def initialize(action, response)
|
7
|
+
@action = action
|
8
|
+
@response = response
|
9
|
+
@body = response.body.to_s
|
10
|
+
super(body)
|
11
|
+
end
|
12
|
+
|
13
|
+
def headers
|
14
|
+
headers = {}
|
15
|
+
response.each do |header, value|
|
16
|
+
headers[header] = value
|
17
|
+
end
|
18
|
+
headers
|
19
|
+
end
|
20
|
+
memoized :headers
|
21
|
+
|
22
|
+
def [](header)
|
23
|
+
headers[header]
|
24
|
+
end
|
25
|
+
|
26
|
+
def each(&block)
|
27
|
+
headers.each(&block)
|
28
|
+
end
|
29
|
+
|
30
|
+
def code
|
31
|
+
response.code.to_i
|
32
|
+
end
|
33
|
+
|
34
|
+
{:success => 200..299, :redirect => 300..399,
|
35
|
+
:client_error => 400..499, :server_error => 500..599}.each do |result, code_range|
|
36
|
+
class_eval(<<-EVAL, __FILE__, __LINE__)
|
37
|
+
def #{result}?
|
38
|
+
return false unless response
|
39
|
+
(#{code_range}).include? code
|
40
|
+
end
|
41
|
+
EVAL
|
42
|
+
end
|
43
|
+
|
44
|
+
def error?
|
45
|
+
!success? && (response['content-type'] == 'application/xml' || response['content-type'] == 'text/xml')
|
46
|
+
end
|
47
|
+
|
48
|
+
def error
|
49
|
+
parsed['Error']
|
50
|
+
end
|
51
|
+
memoized :error
|
52
|
+
|
53
|
+
def parsed
|
54
|
+
parse_options = { 'forcearray' => ['item', 'member'], 'suppressempty' => nil, 'keeproot' => false }
|
55
|
+
# parse_options = { 'suppressempty' => nil, 'keeproot' => false }
|
56
|
+
|
57
|
+
XmlSimple.xml_in(body, parse_options)
|
58
|
+
end
|
59
|
+
memoized :parsed
|
60
|
+
|
61
|
+
# It's expected that each subclass of Response will override this method with what part of response is relevant
|
62
|
+
def result
|
63
|
+
parsed
|
64
|
+
end
|
65
|
+
|
66
|
+
def request_id
|
67
|
+
error? ? parsed['RequestId'] : parsed['ResponseMetadata']['RequestId']
|
68
|
+
end
|
69
|
+
|
70
|
+
def inspect
|
71
|
+
"#<%s:0x%s %s %s %s>" % [self.class, object_id, request_id, response.code, response.message]
|
72
|
+
end
|
73
|
+
end # class Response
|
74
|
+
|
75
|
+
# Requests whose response code is between 300 and 599 and contain an <Error></Error> in their body
|
76
|
+
# are wrapped in an Error::Response. This Error::Response contains an Error object which raises an exception
|
77
|
+
# that corresponds to the error in the response body. The exception object contains the ErrorResponse, so
|
78
|
+
# in all cases where a request happens, you can rescue ResponseError and have access to the ErrorResponse and
|
79
|
+
# its Error object which contains information about the ResponseError.
|
80
|
+
#
|
81
|
+
# begin
|
82
|
+
# Bucket.create(..)
|
83
|
+
# rescue ResponseError => exception
|
84
|
+
# exception.response
|
85
|
+
# # => <Error::Response>
|
86
|
+
# exception.response.error
|
87
|
+
# # => <Error>
|
88
|
+
# end
|
89
|
+
class ResponseError < StandardError
|
90
|
+
attr_reader :response
|
91
|
+
def initialize(response)
|
92
|
+
@response = response
|
93
|
+
super("AWS::SES Response Error: #{message}")
|
94
|
+
end
|
95
|
+
|
96
|
+
def code
|
97
|
+
@response.code
|
98
|
+
end
|
99
|
+
|
100
|
+
def message
|
101
|
+
@response.error['Code'] + @response.error['Message']
|
102
|
+
end
|
103
|
+
|
104
|
+
def inspect
|
105
|
+
"#<%s:0x%s %s %s '%s'>" % [self.class.name, object_id, @response.request_id, code, message]
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end # module SES
|
109
|
+
end # module AWS
|
110
|
+
|
@@ -0,0 +1,144 @@
|
|
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.is_a?(Hash) ? Mail.new(mail).to_s : mail.to_s
|
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
|
+
# Adds all elements of the ary with the appropriate member elements
|
117
|
+
def add_array_to_hash!(hash, key, ary)
|
118
|
+
cnt = 1
|
119
|
+
[*ary].each do |o|
|
120
|
+
hash["#{key}.member.#{cnt}"] = o
|
121
|
+
cnt += 1
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
class EmailResponse < AWS::SES::Response
|
127
|
+
def result
|
128
|
+
super["#{action}Result"]
|
129
|
+
end
|
130
|
+
|
131
|
+
def message_id
|
132
|
+
result['MessageId']
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
class SendEmailResponse < EmailResponse
|
137
|
+
|
138
|
+
end
|
139
|
+
|
140
|
+
class SendRawEmailResponse < EmailResponse
|
141
|
+
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
data/lib/aws/ses.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
%w[ base64 cgi openssl digest/sha1 net/https net/http rexml/document time ostruct mail].each { |f| require f }
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'URI' unless defined? URI
|
5
|
+
rescue Exception => e
|
6
|
+
# nothing
|
7
|
+
end
|
8
|
+
|
9
|
+
begin
|
10
|
+
require 'xmlsimple' unless defined? XmlSimple
|
11
|
+
rescue Exception => e
|
12
|
+
require 'xml-simple' unless defined? XmlSimple
|
13
|
+
end
|
14
|
+
|
15
|
+
$:.unshift(File.dirname(__FILE__))
|
16
|
+
require 'ses/extensions'
|
17
|
+
|
18
|
+
require 'ses/response'
|
19
|
+
require 'ses/send_email'
|
20
|
+
require 'ses/info'
|
21
|
+
require 'ses/base'
|
22
|
+
require 'ses/version'
|
23
|
+
require 'ses/addresses'
|
24
|
+
|
25
|
+
if defined?(Rails)
|
26
|
+
major, minor = Rails.version.split('.')
|
27
|
+
require 'actionmailer/ses_extension' if major == '2' && minor == '3'
|
28
|
+
end
|