| 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,
|
| })
|
| -
|
|
|