Index: boto/ses/connection.py |
diff --git a/boto/ses/connection.py b/boto/ses/connection.py |
index 57a2c7e00d3b3b4d6dfb6d5cb2ef400309b7081c..8cd80c39814f9a000e2179a7f717b805e71010c5 100644 |
--- a/boto/ses/connection.py |
+++ b/boto/ses/connection.py |
@@ -15,13 +15,14 @@ |
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL- |
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT |
-# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, |
+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, |
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS |
# IN THE SOFTWARE. |
from boto.connection import AWSAuthConnection |
from boto.exception import BotoServerError |
+from boto.regioninfo import RegionInfo |
import boto |
import boto.jsonresponse |
@@ -32,15 +33,23 @@ import base64 |
class SESConnection(AWSAuthConnection): |
ResponseError = BotoServerError |
- DefaultHost = 'email.us-east-1.amazonaws.com' |
+ DefaultRegionName = 'us-east-1' |
+ DefaultRegionEndpoint = 'email.us-east-1.amazonaws.com' |
APIVersion = '2010-12-01' |
def __init__(self, aws_access_key_id=None, aws_secret_access_key=None, |
- port=None, proxy=None, proxy_port=None, |
- host=DefaultHost, debug=0): |
- AWSAuthConnection.__init__(self, host, aws_access_key_id, |
- aws_secret_access_key, True, port, proxy, |
- proxy_port, debug=debug) |
+ is_secure=True, port=None, proxy=None, proxy_port=None, |
+ proxy_user=None, proxy_pass=None, debug=0, |
+ https_connection_factory=None, region=None, path='/'): |
+ if not region: |
+ region = RegionInfo(self, self.DefaultRegionName, |
+ self.DefaultRegionEndpoint) |
+ self.region = region |
+ AWSAuthConnection.__init__(self, self.region.endpoint, |
+ aws_access_key_id, aws_secret_access_key, |
+ is_secure, port, proxy, proxy_port, |
+ proxy_user, proxy_pass, debug, |
+ https_connection_factory, path) |
def _required_auth_capability(self): |
return ['ses'] |
@@ -57,7 +66,7 @@ class SESConnection(AWSAuthConnection): |
:type label: string |
:param label: The parameter list's name |
""" |
- if isinstance(items, str): |
+ if isinstance(items, basestring): |
items = [items] |
for i in range(1, len(items) + 1): |
params['%s.%d' % (label, i)] = items[i - 1] |
@@ -73,9 +82,15 @@ class SESConnection(AWSAuthConnection): |
:param params: Parameters that will be sent as POST data with the API |
call. |
""" |
- headers = {'Content-Type': 'application/x-www-form-urlencoded'} |
+ ct = 'application/x-www-form-urlencoded; charset=UTF-8' |
+ headers = {'Content-Type': ct} |
params = params or {} |
params['Action'] = action |
+ |
+ for k, v in params.items(): |
+ if isinstance(v, unicode): # UTF-8 encode only if it's Unicode |
+ params[k] = v.encode('utf-8') |
+ |
response = super(SESConnection, self).make_request( |
'POST', |
'/', |
@@ -96,7 +111,8 @@ class SESConnection(AWSAuthConnection): |
def send_email(self, source, subject, body, to_addresses, cc_addresses=None, |
- bcc_addresses=None, format='text'): |
+ bcc_addresses=None, format='text', reply_addresses=None, |
+ return_path=None, text_body=None, html_body=None): |
"""Composes an email message based on input data, and then immediately |
queues the message for sending. |
@@ -123,20 +139,57 @@ class SESConnection(AWSAuthConnection): |
:param format: The format of the message's body, must be either "text" |
or "html". |
+ :type reply_addresses: list of strings or string |
+ :param reply_addresses: The reply-to email address(es) for the |
+ message. If the recipient replies to the |
+ message, each reply-to address will |
+ receive the reply. |
+ |
+ :type return_path: string |
+ :param 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. |
+ |
+ :type text_body: string |
+ :param text_body: The text body to send with this email. |
+ |
+ :type html_body: string |
+ :param html_body: The html body to send with this email. |
+ |
""" |
+ format = format.lower().strip() |
+ if body is not None: |
+ if format == "text": |
+ if text_body is not None: |
+ raise Warning("You've passed in both a body and a text_body; please choose one or the other.") |
+ text_body = body |
+ else: |
+ if html_body is not None: |
+ raise Warning("You've passed in both a body and an html_body; please choose one or the other.") |
+ html_body = body |
+ |
params = { |
'Source': source, |
'Message.Subject.Data': subject, |
} |
- format = format.lower().strip() |
- if format == 'html': |
- params['Message.Body.Html.Data'] = body |
- elif format == 'text': |
- params['Message.Body.Text.Data'] = body |
- else: |
+ if return_path: |
+ params['ReturnPath'] = return_path |
+ |
+ if html_body is not None: |
+ params['Message.Body.Html.Data'] = html_body |
+ if text_body is not None: |
+ params['Message.Body.Text.Data'] = text_body |
+ |
+ if(format not in ("text","html")): |
raise ValueError("'format' argument must be 'text' or 'html'") |
+ if(not (html_body or text_body)): |
+ raise ValueError("No text or html body found for mail") |
+ |
self._build_list_params(params, to_addresses, |
'Destination.ToAddresses.member') |
if cc_addresses: |
@@ -147,9 +200,13 @@ class SESConnection(AWSAuthConnection): |
self._build_list_params(params, bcc_addresses, |
'Destination.BccAddresses.member') |
+ if reply_addresses: |
+ self._build_list_params(params, reply_addresses, |
+ 'ReplyToAddresses.member') |
+ |
return self._make_request('SendEmail', params) |
- def send_raw_email(self, source, raw_message, destinations=None): |
+ def send_raw_email(self, raw_message, source=None, destinations=None): |
"""Sends an email message, with header and content specified by the |
client. The SendRawEmail action is useful for sending multipart MIME |
emails, with attachments or inline content. The raw text of the message |
@@ -157,7 +214,12 @@ class SESConnection(AWSAuthConnection): |
cannot be sent. |
:type source: string |
- :param source: The sender's email address. |
+ :param source: The sender's email address. Amazon's docs say: |
+ |
+ If you specify the Source parameter, then bounce notifications and |
+ complaints will be sent to this email address. This takes precedence |
+ over any Return-Path header that you might include in the raw text of |
+ the message. |
:type raw_message: string |
:param raw_message: The raw text of the message. The client is |
@@ -175,12 +237,15 @@ class SESConnection(AWSAuthConnection): |
""" |
params = { |
- 'Source': source, |
'RawMessage.Data': base64.b64encode(raw_message), |
} |
+ |
+ if source: |
+ params['Source'] = source |
- self._build_list_params(params, destinations, |
- 'Destinations.member') |
+ if destinations: |
+ self._build_list_params(params, destinations, |
+ 'Destinations.member') |
return self._make_request('SendRawEmail', params) |
@@ -245,4 +310,3 @@ class SESConnection(AWSAuthConnection): |
return self._make_request('VerifyEmailAddress', { |
'EmailAddress': email_address, |
}) |
- |