OLD | NEW |
1 # Copyright (c) 2010 Mitch Garnaat http://garnaat.org/ | 1 # Copyright (c) 2010 Mitch Garnaat http://garnaat.org/ |
2 # Copyright (c) 2011 Harry Marr http://hmarr.com/ | 2 # Copyright (c) 2011 Harry Marr http://hmarr.com/ |
3 # | 3 # |
4 # Permission is hereby granted, free of charge, to any person obtaining a | 4 # Permission is hereby granted, free of charge, to any person obtaining a |
5 # copy of this software and associated documentation files (the | 5 # copy of this software and associated documentation files (the |
6 # "Software"), to deal in the Software without restriction, including | 6 # "Software"), to deal in the Software without restriction, including |
7 # without limitation the rights to use, copy, modify, merge, publish, dis- | 7 # without limitation the rights to use, copy, modify, merge, publish, dis- |
8 # tribute, sublicense, and/or sell copies of the Software, and to permit | 8 # tribute, sublicense, and/or sell copies of the Software, and to permit |
9 # persons to whom the Software is furnished to do so, subject to the fol- | 9 # persons to whom the Software is furnished to do so, subject to the fol- |
10 # lowing conditions: | 10 # lowing conditions: |
(...skipping 10 matching lines...) Expand all Loading... |
21 # IN THE SOFTWARE. | 21 # IN THE SOFTWARE. |
22 | 22 |
23 from boto.connection import AWSAuthConnection | 23 from boto.connection import AWSAuthConnection |
24 from boto.exception import BotoServerError | 24 from boto.exception import BotoServerError |
25 from boto.regioninfo import RegionInfo | 25 from boto.regioninfo import RegionInfo |
26 import boto | 26 import boto |
27 import boto.jsonresponse | 27 import boto.jsonresponse |
28 | 28 |
29 import urllib | 29 import urllib |
30 import base64 | 30 import base64 |
| 31 from boto.ses import exceptions as ses_exceptions |
31 | 32 |
32 | 33 |
33 class SESConnection(AWSAuthConnection): | 34 class SESConnection(AWSAuthConnection): |
34 | 35 |
35 ResponseError = BotoServerError | 36 ResponseError = BotoServerError |
36 DefaultRegionName = 'us-east-1' | 37 DefaultRegionName = 'us-east-1' |
37 DefaultRegionEndpoint = 'email.us-east-1.amazonaws.com' | 38 DefaultRegionEndpoint = 'email.us-east-1.amazonaws.com' |
38 APIVersion = '2010-12-01' | 39 APIVersion = '2010-12-01' |
39 | 40 |
40 def __init__(self, aws_access_key_id=None, aws_secret_access_key=None, | 41 def __init__(self, aws_access_key_id=None, aws_secret_access_key=None, |
(...skipping 23 matching lines...) Expand all Loading... |
64 :param items: Items to be included in the list | 65 :param items: Items to be included in the list |
65 | 66 |
66 :type label: string | 67 :type label: string |
67 :param label: The parameter list's name | 68 :param label: The parameter list's name |
68 """ | 69 """ |
69 if isinstance(items, basestring): | 70 if isinstance(items, basestring): |
70 items = [items] | 71 items = [items] |
71 for i in range(1, len(items) + 1): | 72 for i in range(1, len(items) + 1): |
72 params['%s.%d' % (label, i)] = items[i - 1] | 73 params['%s.%d' % (label, i)] = items[i - 1] |
73 | 74 |
74 | |
75 def _make_request(self, action, params=None): | 75 def _make_request(self, action, params=None): |
76 """Make a call to the SES API. | 76 """Make a call to the SES API. |
77 | 77 |
78 :type action: string | 78 :type action: string |
79 :param action: The API method to use (e.g. SendRawEmail) | 79 :param action: The API method to use (e.g. SendRawEmail) |
80 | 80 |
81 :type params: dict | 81 :type params: dict |
82 :param params: Parameters that will be sent as POST data with the API | 82 :param params: Parameters that will be sent as POST data with the API |
83 call. | 83 call. |
84 """ | 84 """ |
85 ct = 'application/x-www-form-urlencoded; charset=UTF-8' | 85 ct = 'application/x-www-form-urlencoded; charset=UTF-8' |
86 headers = {'Content-Type': ct} | 86 headers = {'Content-Type': ct} |
87 params = params or {} | 87 params = params or {} |
88 params['Action'] = action | 88 params['Action'] = action |
89 | 89 |
90 for k, v in params.items(): | 90 for k, v in params.items(): |
91 if isinstance(v, unicode): # UTF-8 encode only if it's Unicode | 91 if isinstance(v, unicode): # UTF-8 encode only if it's Unicode |
92 params[k] = v.encode('utf-8') | 92 params[k] = v.encode('utf-8') |
93 | 93 |
94 response = super(SESConnection, self).make_request( | 94 response = super(SESConnection, self).make_request( |
95 'POST', | 95 'POST', |
96 '/', | 96 '/', |
97 headers=headers, | 97 headers=headers, |
98 data=urllib.urlencode(params) | 98 data=urllib.urlencode(params) |
99 ) | 99 ) |
100 body = response.read() | 100 body = response.read() |
101 if response.status == 200: | 101 if response.status == 200: |
102 list_markers = ('VerifiedEmailAddresses', 'SendDataPoints') | 102 list_markers = ('VerifiedEmailAddresses', 'SendDataPoints') |
103 e = boto.jsonresponse.Element(list_marker=list_markers) | 103 e = boto.jsonresponse.Element(list_marker=list_markers) |
104 h = boto.jsonresponse.XmlHandler(e, None) | 104 h = boto.jsonresponse.XmlHandler(e, None) |
105 h.parse(body) | 105 h.parse(body) |
106 return e | 106 return e |
107 else: | 107 else: |
108 boto.log.error('%s %s' % (response.status, response.reason)) | 108 # HTTP codes other than 200 are considered errors. Go through |
109 boto.log.error('%s' % body) | 109 # some error handling to determine which exception gets raised, |
110 raise self.ResponseError(response.status, response.reason, body) | 110 self._handle_error(response, body) |
111 | 111 |
| 112 def _handle_error(self, response, body): |
| 113 """ |
| 114 Handle raising the correct exception, depending on the error. Many |
| 115 errors share the same HTTP response code, meaning we have to get really |
| 116 kludgey and do string searches to figure out what went wrong. |
| 117 """ |
| 118 boto.log.error('%s %s' % (response.status, response.reason)) |
| 119 boto.log.error('%s' % body) |
| 120 |
| 121 if "Address blacklisted." in body: |
| 122 # Delivery failures happened frequently enough with the recipient's |
| 123 # email address for Amazon to blacklist it. After a day or three, |
| 124 # they'll be automatically removed, and delivery can be attempted |
| 125 # again (if you write the code to do so in your application). |
| 126 ExceptionToRaise = ses_exceptions.SESAddressBlacklistedError |
| 127 exc_reason = "Address blacklisted." |
| 128 elif "Email address is not verified." in body: |
| 129 # This error happens when the "Reply-To" value passed to |
| 130 # send_email() hasn't been verified yet. |
| 131 ExceptionToRaise = ses_exceptions.SESAddressNotVerifiedError |
| 132 exc_reason = "Email address is not verified." |
| 133 elif "Daily message quota exceeded." in body: |
| 134 # Encountered when your account exceeds the maximum total number |
| 135 # of emails per 24 hours. |
| 136 ExceptionToRaise = ses_exceptions.SESDailyQuotaExceededError |
| 137 exc_reason = "Daily message quota exceeded." |
| 138 elif "Maximum sending rate exceeded." in body: |
| 139 # Your account has sent above its allowed requests a second rate. |
| 140 ExceptionToRaise = ses_exceptions.SESMaxSendingRateExceededError |
| 141 exc_reason = "Maximum sending rate exceeded." |
| 142 else: |
| 143 # This is either a common AWS error, or one that we don't devote |
| 144 # its own exception to. |
| 145 ExceptionToRaise = self.ResponseError |
| 146 exc_reason = response.reason |
| 147 |
| 148 raise ExceptionToRaise(response.status, exc_reason, body) |
112 | 149 |
113 def send_email(self, source, subject, body, to_addresses, cc_addresses=None, | 150 def send_email(self, source, subject, body, to_addresses, cc_addresses=None, |
114 bcc_addresses=None, format='text', reply_addresses=None, | 151 bcc_addresses=None, format='text', reply_addresses=None, |
115 return_path=None, text_body=None, html_body=None): | 152 return_path=None, text_body=None, html_body=None): |
116 """Composes an email message based on input data, and then immediately | 153 """Composes an email message based on input data, and then immediately |
117 queues the message for sending. | 154 queues the message for sending. |
118 | 155 |
119 :type source: string | 156 :type source: string |
120 :param source: The sender's email address. | 157 :param source: The sender's email address. |
121 | 158 |
(...skipping 110 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
232 Refer to the Amazon SES Developer Guide for more details. | 269 Refer to the Amazon SES Developer Guide for more details. |
233 - Content must be base64-encoded, if MIME requires it. | 270 - Content must be base64-encoded, if MIME requires it. |
234 | 271 |
235 :type destinations: list of strings or string | 272 :type destinations: list of strings or string |
236 :param destinations: A list of destinations for the message. | 273 :param destinations: A list of destinations for the message. |
237 | 274 |
238 """ | 275 """ |
239 params = { | 276 params = { |
240 'RawMessage.Data': base64.b64encode(raw_message), | 277 'RawMessage.Data': base64.b64encode(raw_message), |
241 } | 278 } |
242 | 279 |
243 if source: | 280 if source: |
244 params['Source'] = source | 281 params['Source'] = source |
245 | 282 |
246 if destinations: | 283 if destinations: |
247 self._build_list_params(params, destinations, | 284 self._build_list_params(params, destinations, |
248 'Destinations.member') | 285 'Destinations.member') |
249 | 286 |
250 return self._make_request('SendRawEmail', params) | 287 return self._make_request('SendRawEmail', params) |
251 | 288 |
252 def list_verified_email_addresses(self): | 289 def list_verified_email_addresses(self): |
(...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
303 :type email_adddress: string | 340 :type email_adddress: string |
304 :param email_address: The email address to be verified. | 341 :param email_address: The email address to be verified. |
305 | 342 |
306 :rtype: dict | 343 :rtype: dict |
307 :returns: A VerifyEmailAddressResponse structure. Note that keys must | 344 :returns: A VerifyEmailAddressResponse structure. Note that keys must |
308 be unicode strings. | 345 be unicode strings. |
309 """ | 346 """ |
310 return self._make_request('VerifyEmailAddress', { | 347 return self._make_request('VerifyEmailAddress', { |
311 'EmailAddress': email_address, | 348 'EmailAddress': email_address, |
312 }) | 349 }) |
OLD | NEW |