| OLD | NEW | 
|---|
| (Empty) |  | 
|  | 1 # Copyright (c) 2010 Mitch Garnaat http://garnaat.org/ | 
|  | 2 # Copyright (c) 2011 Harry Marr http://hmarr.com/ | 
|  | 3 # | 
|  | 4 # Permission is hereby granted, free of charge, to any person obtaining a | 
|  | 5 # copy of this software and associated documentation files (the | 
|  | 6 # "Software"), to deal in the Software without restriction, including | 
|  | 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 | 
|  | 9 # persons to whom the Software is furnished to do so, subject to the fol- | 
|  | 10 # lowing conditions: | 
|  | 11 # | 
|  | 12 # The above copyright notice and this permission notice shall be included | 
|  | 13 # in all copies or substantial portions of the Software. | 
|  | 14 # | 
|  | 15 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS | 
|  | 16 # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL- | 
|  | 17 # ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT | 
|  | 18 # SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, | 
|  | 19 # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | 
|  | 20 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS | 
|  | 21 # IN THE SOFTWARE. | 
|  | 22 import re | 
|  | 23 import urllib | 
|  | 24 import base64 | 
|  | 25 | 
|  | 26 from boto.connection import AWSAuthConnection | 
|  | 27 from boto.exception import BotoServerError | 
|  | 28 from boto.regioninfo import RegionInfo | 
|  | 29 import boto | 
|  | 30 import boto.jsonresponse | 
|  | 31 from boto.ses import exceptions as ses_exceptions | 
|  | 32 | 
|  | 33 | 
|  | 34 class SESConnection(AWSAuthConnection): | 
|  | 35 | 
|  | 36     ResponseError = BotoServerError | 
|  | 37     DefaultRegionName = 'us-east-1' | 
|  | 38     DefaultRegionEndpoint = 'email.us-east-1.amazonaws.com' | 
|  | 39     APIVersion = '2010-12-01' | 
|  | 40 | 
|  | 41     def __init__(self, aws_access_key_id=None, aws_secret_access_key=None, | 
|  | 42                  is_secure=True, port=None, proxy=None, proxy_port=None, | 
|  | 43                  proxy_user=None, proxy_pass=None, debug=0, | 
|  | 44                  https_connection_factory=None, region=None, path='/', | 
|  | 45                  security_token=None, validate_certs=True): | 
|  | 46         if not region: | 
|  | 47             region = RegionInfo(self, self.DefaultRegionName, | 
|  | 48                                 self.DefaultRegionEndpoint) | 
|  | 49         self.region = region | 
|  | 50         AWSAuthConnection.__init__(self, self.region.endpoint, | 
|  | 51                                    aws_access_key_id, aws_secret_access_key, | 
|  | 52                                    is_secure, port, proxy, proxy_port, | 
|  | 53                                    proxy_user, proxy_pass, debug, | 
|  | 54                                    https_connection_factory, path, | 
|  | 55                                    security_token=security_token, | 
|  | 56                                    validate_certs=validate_certs) | 
|  | 57 | 
|  | 58     def _required_auth_capability(self): | 
|  | 59         return ['ses'] | 
|  | 60 | 
|  | 61     def _build_list_params(self, params, items, label): | 
|  | 62         """Add an AWS API-compatible parameter list to a dictionary. | 
|  | 63 | 
|  | 64         :type params: dict | 
|  | 65         :param params: The parameter dictionary | 
|  | 66 | 
|  | 67         :type items: list | 
|  | 68         :param items: Items to be included in the list | 
|  | 69 | 
|  | 70         :type label: string | 
|  | 71         :param label: The parameter list's name | 
|  | 72         """ | 
|  | 73         if isinstance(items, basestring): | 
|  | 74             items = [items] | 
|  | 75         for i in range(1, len(items) + 1): | 
|  | 76             params['%s.%d' % (label, i)] = items[i - 1] | 
|  | 77 | 
|  | 78     def _make_request(self, action, params=None): | 
|  | 79         """Make a call to the SES API. | 
|  | 80 | 
|  | 81         :type action: string | 
|  | 82         :param action: The API method to use (e.g. SendRawEmail) | 
|  | 83 | 
|  | 84         :type params: dict | 
|  | 85         :param params: Parameters that will be sent as POST data with the API | 
|  | 86             call. | 
|  | 87         """ | 
|  | 88         ct = 'application/x-www-form-urlencoded; charset=UTF-8' | 
|  | 89         headers = {'Content-Type': ct} | 
|  | 90         params = params or {} | 
|  | 91         params['Action'] = action | 
|  | 92 | 
|  | 93         for k, v in params.items(): | 
|  | 94             if isinstance(v, unicode):  # UTF-8 encode only if it's Unicode | 
|  | 95                 params[k] = v.encode('utf-8') | 
|  | 96 | 
|  | 97         response = super(SESConnection, self).make_request( | 
|  | 98             'POST', | 
|  | 99             '/', | 
|  | 100             headers=headers, | 
|  | 101             data=urllib.urlencode(params) | 
|  | 102         ) | 
|  | 103         body = response.read() | 
|  | 104         if response.status == 200: | 
|  | 105             list_markers = ('VerifiedEmailAddresses', 'Identities', | 
|  | 106                             'VerificationAttributes', 'SendDataPoints') | 
|  | 107             item_markers = ('member', 'item', 'entry') | 
|  | 108 | 
|  | 109             e = boto.jsonresponse.Element(list_marker=list_markers, | 
|  | 110                                           item_marker=item_markers) | 
|  | 111             h = boto.jsonresponse.XmlHandler(e, None) | 
|  | 112             h.parse(body) | 
|  | 113             return e | 
|  | 114         else: | 
|  | 115             # HTTP codes other than 200 are considered errors. Go through | 
|  | 116             # some error handling to determine which exception gets raised, | 
|  | 117             self._handle_error(response, body) | 
|  | 118 | 
|  | 119     def _handle_error(self, response, body): | 
|  | 120         """ | 
|  | 121         Handle raising the correct exception, depending on the error. Many | 
|  | 122         errors share the same HTTP response code, meaning we have to get really | 
|  | 123         kludgey and do string searches to figure out what went wrong. | 
|  | 124         """ | 
|  | 125         boto.log.error('%s %s' % (response.status, response.reason)) | 
|  | 126         boto.log.error('%s' % body) | 
|  | 127 | 
|  | 128         if "Address blacklisted." in body: | 
|  | 129             # Delivery failures happened frequently enough with the recipient's | 
|  | 130             # email address for Amazon to blacklist it. After a day or three, | 
|  | 131             # they'll be automatically removed, and delivery can be attempted | 
|  | 132             # again (if you write the code to do so in your application). | 
|  | 133             ExceptionToRaise = ses_exceptions.SESAddressBlacklistedError | 
|  | 134             exc_reason = "Address blacklisted." | 
|  | 135         elif "Email address is not verified." in body: | 
|  | 136             # This error happens when the "Reply-To" value passed to | 
|  | 137             # send_email() hasn't been verified yet. | 
|  | 138             ExceptionToRaise = ses_exceptions.SESAddressNotVerifiedError | 
|  | 139             exc_reason = "Email address is not verified." | 
|  | 140         elif "Daily message quota exceeded." in body: | 
|  | 141             # Encountered when your account exceeds the maximum total number | 
|  | 142             # of emails per 24 hours. | 
|  | 143             ExceptionToRaise = ses_exceptions.SESDailyQuotaExceededError | 
|  | 144             exc_reason = "Daily message quota exceeded." | 
|  | 145         elif "Maximum sending rate exceeded." in body: | 
|  | 146             # Your account has sent above its allowed requests a second rate. | 
|  | 147             ExceptionToRaise = ses_exceptions.SESMaxSendingRateExceededError | 
|  | 148             exc_reason = "Maximum sending rate exceeded." | 
|  | 149         elif "Domain ends with dot." in body: | 
|  | 150             # Recipient address ends with a dot/period. This is invalid. | 
|  | 151             ExceptionToRaise = ses_exceptions.SESDomainEndsWithDotError | 
|  | 152             exc_reason = "Domain ends with dot." | 
|  | 153         elif "Local address contains control or whitespace" in body: | 
|  | 154             # I think this pertains to the recipient address. | 
|  | 155             ExceptionToRaise = ses_exceptions.SESLocalAddressCharacterError | 
|  | 156             exc_reason = "Local address contains control or whitespace." | 
|  | 157         elif "Illegal address" in body: | 
|  | 158             # A clearly mal-formed address. | 
|  | 159             ExceptionToRaise = ses_exceptions.SESIllegalAddressError | 
|  | 160             exc_reason = "Illegal address" | 
|  | 161         # The re.search is to distinguish from the | 
|  | 162         # SESAddressNotVerifiedError error above. | 
|  | 163         elif re.search('Identity.*is not verified', body): | 
|  | 164             ExceptionToRaise = ses_exceptions.SESIdentityNotVerifiedError | 
|  | 165             exc_reason = "Identity is not verified." | 
|  | 166         elif "ownership not confirmed" in body: | 
|  | 167             ExceptionToRaise = ses_exceptions.SESDomainNotConfirmedError | 
|  | 168             exc_reason = "Domain ownership is not confirmed." | 
|  | 169         else: | 
|  | 170             # This is either a common AWS error, or one that we don't devote | 
|  | 171             # its own exception to. | 
|  | 172             ExceptionToRaise = self.ResponseError | 
|  | 173             exc_reason = response.reason | 
|  | 174 | 
|  | 175         raise ExceptionToRaise(response.status, exc_reason, body) | 
|  | 176 | 
|  | 177     def send_email(self, source, subject, body, to_addresses, | 
|  | 178                    cc_addresses=None, bcc_addresses=None, | 
|  | 179                    format='text', reply_addresses=None, | 
|  | 180                    return_path=None, text_body=None, html_body=None): | 
|  | 181         """Composes an email message based on input data, and then immediately | 
|  | 182         queues the message for sending. | 
|  | 183 | 
|  | 184         :type source: string | 
|  | 185         :param source: The sender's email address. | 
|  | 186 | 
|  | 187         :type subject: string | 
|  | 188         :param subject: The subject of the message: A short summary of the | 
|  | 189                         content, which will appear in the recipient's inbox. | 
|  | 190 | 
|  | 191         :type body: string | 
|  | 192         :param body: The message body. | 
|  | 193 | 
|  | 194         :type to_addresses: list of strings or string | 
|  | 195         :param to_addresses: The To: field(s) of the message. | 
|  | 196 | 
|  | 197         :type cc_addresses: list of strings or string | 
|  | 198         :param cc_addresses: The CC: field(s) of the message. | 
|  | 199 | 
|  | 200         :type bcc_addresses: list of strings or string | 
|  | 201         :param bcc_addresses: The BCC: field(s) of the message. | 
|  | 202 | 
|  | 203         :type format: string | 
|  | 204         :param format: The format of the message's body, must be either "text" | 
|  | 205                        or "html". | 
|  | 206 | 
|  | 207         :type reply_addresses: list of strings or string | 
|  | 208         :param reply_addresses: The reply-to email address(es) for the | 
|  | 209                                 message. If the recipient replies to the | 
|  | 210                                 message, each reply-to address will | 
|  | 211                                 receive the reply. | 
|  | 212 | 
|  | 213         :type return_path: string | 
|  | 214         :param return_path: The email address to which bounce notifications are | 
|  | 215                             to be forwarded. If the message cannot be delivered | 
|  | 216                             to the recipient, then an error message will be | 
|  | 217                             returned from the recipient's ISP; this message | 
|  | 218                             will then be forwarded to the email address | 
|  | 219                             specified by the ReturnPath parameter. | 
|  | 220 | 
|  | 221         :type text_body: string | 
|  | 222         :param text_body: The text body to send with this email. | 
|  | 223 | 
|  | 224         :type html_body: string | 
|  | 225         :param html_body: The html body to send with this email. | 
|  | 226 | 
|  | 227         """ | 
|  | 228         format = format.lower().strip() | 
|  | 229         if body is not None: | 
|  | 230             if format == "text": | 
|  | 231                 if text_body is not None: | 
|  | 232                     raise Warning("You've passed in both a body and a " | 
|  | 233                                   "text_body; please choose one or the other.") | 
|  | 234                 text_body = body | 
|  | 235             else: | 
|  | 236                 if html_body is not None: | 
|  | 237                     raise Warning("You've passed in both a body and an " | 
|  | 238                                   "html_body; please choose one or the other.") | 
|  | 239                 html_body = body | 
|  | 240 | 
|  | 241         params = { | 
|  | 242             'Source': source, | 
|  | 243             'Message.Subject.Data': subject, | 
|  | 244         } | 
|  | 245 | 
|  | 246         if return_path: | 
|  | 247             params['ReturnPath'] = return_path | 
|  | 248 | 
|  | 249         if html_body is not None: | 
|  | 250             params['Message.Body.Html.Data'] = html_body | 
|  | 251         if text_body is not None: | 
|  | 252             params['Message.Body.Text.Data'] = text_body | 
|  | 253 | 
|  | 254         if(format not in ("text", "html")): | 
|  | 255             raise ValueError("'format' argument must be 'text' or 'html'") | 
|  | 256 | 
|  | 257         if(not (html_body or text_body)): | 
|  | 258             raise ValueError("No text or html body found for mail") | 
|  | 259 | 
|  | 260         self._build_list_params(params, to_addresses, | 
|  | 261                                'Destination.ToAddresses.member') | 
|  | 262         if cc_addresses: | 
|  | 263             self._build_list_params(params, cc_addresses, | 
|  | 264                                    'Destination.CcAddresses.member') | 
|  | 265 | 
|  | 266         if bcc_addresses: | 
|  | 267             self._build_list_params(params, bcc_addresses, | 
|  | 268                                    'Destination.BccAddresses.member') | 
|  | 269 | 
|  | 270         if reply_addresses: | 
|  | 271             self._build_list_params(params, reply_addresses, | 
|  | 272                                    'ReplyToAddresses.member') | 
|  | 273 | 
|  | 274         return self._make_request('SendEmail', params) | 
|  | 275 | 
|  | 276     def send_raw_email(self, raw_message, source=None, destinations=None): | 
|  | 277         """Sends an email message, with header and content specified by the | 
|  | 278         client. The SendRawEmail action is useful for sending multipart MIME | 
|  | 279         emails, with attachments or inline content. The raw text of the message | 
|  | 280         must comply with Internet email standards; otherwise, the message | 
|  | 281         cannot be sent. | 
|  | 282 | 
|  | 283         :type source: string | 
|  | 284         :param source: The sender's email address. Amazon's docs say: | 
|  | 285 | 
|  | 286           If you specify the Source parameter, then bounce notifications and | 
|  | 287           complaints will be sent to this email address. This takes precedence | 
|  | 288           over any Return-Path header that you might include in the raw text of | 
|  | 289           the message. | 
|  | 290 | 
|  | 291         :type raw_message: string | 
|  | 292         :param raw_message: The raw text of the message. The client is | 
|  | 293           responsible for ensuring the following: | 
|  | 294 | 
|  | 295           - Message must contain a header and a body, separated by a blank line. | 
|  | 296           - All required header fields must be present. | 
|  | 297           - Each part of a multipart MIME message must be formatted properly. | 
|  | 298           - MIME content types must be among those supported by Amazon SES. | 
|  | 299             Refer to the Amazon SES Developer Guide for more details. | 
|  | 300           - Content must be base64-encoded, if MIME requires it. | 
|  | 301 | 
|  | 302         :type destinations: list of strings or string | 
|  | 303         :param destinations: A list of destinations for the message. | 
|  | 304 | 
|  | 305         """ | 
|  | 306 | 
|  | 307         if isinstance(raw_message, unicode): | 
|  | 308             raw_message = raw_message.encode('utf-8') | 
|  | 309 | 
|  | 310         params = { | 
|  | 311             'RawMessage.Data': base64.b64encode(raw_message), | 
|  | 312         } | 
|  | 313 | 
|  | 314         if source: | 
|  | 315             params['Source'] = source | 
|  | 316 | 
|  | 317         if destinations: | 
|  | 318             self._build_list_params(params, destinations, | 
|  | 319                                    'Destinations.member') | 
|  | 320 | 
|  | 321         return self._make_request('SendRawEmail', params) | 
|  | 322 | 
|  | 323     def list_verified_email_addresses(self): | 
|  | 324         """Fetch a list of the email addresses that have been verified. | 
|  | 325 | 
|  | 326         :rtype: dict | 
|  | 327         :returns: A ListVerifiedEmailAddressesResponse structure. Note that | 
|  | 328                   keys must be unicode strings. | 
|  | 329         """ | 
|  | 330         return self._make_request('ListVerifiedEmailAddresses') | 
|  | 331 | 
|  | 332     def get_send_quota(self): | 
|  | 333         """Fetches the user's current activity limits. | 
|  | 334 | 
|  | 335         :rtype: dict | 
|  | 336         :returns: A GetSendQuotaResponse structure. Note that keys must be | 
|  | 337                   unicode strings. | 
|  | 338         """ | 
|  | 339         return self._make_request('GetSendQuota') | 
|  | 340 | 
|  | 341     def get_send_statistics(self): | 
|  | 342         """Fetches the user's sending statistics. The result is a list of data | 
|  | 343         points, representing the last two weeks of sending activity. | 
|  | 344 | 
|  | 345         Each data point in the list contains statistics for a 15-minute | 
|  | 346         interval. | 
|  | 347 | 
|  | 348         :rtype: dict | 
|  | 349         :returns: A GetSendStatisticsResponse structure. Note that keys must be | 
|  | 350                   unicode strings. | 
|  | 351         """ | 
|  | 352         return self._make_request('GetSendStatistics') | 
|  | 353 | 
|  | 354     def delete_verified_email_address(self, email_address): | 
|  | 355         """Deletes the specified email address from the list of verified | 
|  | 356         addresses. | 
|  | 357 | 
|  | 358         :type email_adddress: string | 
|  | 359         :param email_address: The email address to be removed from the list of | 
|  | 360                               verified addreses. | 
|  | 361 | 
|  | 362         :rtype: dict | 
|  | 363         :returns: A DeleteVerifiedEmailAddressResponse structure. Note that | 
|  | 364                   keys must be unicode strings. | 
|  | 365         """ | 
|  | 366         return self._make_request('DeleteVerifiedEmailAddress', { | 
|  | 367             'EmailAddress': email_address, | 
|  | 368         }) | 
|  | 369 | 
|  | 370     def verify_email_address(self, email_address): | 
|  | 371         """Verifies an email address. This action causes a confirmation email | 
|  | 372         message to be sent to the specified address. | 
|  | 373 | 
|  | 374         :type email_adddress: string | 
|  | 375         :param email_address: The email address to be verified. | 
|  | 376 | 
|  | 377         :rtype: dict | 
|  | 378         :returns: A VerifyEmailAddressResponse structure. Note that keys must | 
|  | 379                   be unicode strings. | 
|  | 380         """ | 
|  | 381         return self._make_request('VerifyEmailAddress', { | 
|  | 382             'EmailAddress': email_address, | 
|  | 383         }) | 
|  | 384 | 
|  | 385     def verify_domain_dkim(self, domain): | 
|  | 386         """ | 
|  | 387         Returns a set of DNS records, or tokens, that must be published in the | 
|  | 388         domain name's DNS to complete the DKIM verification process. These | 
|  | 389         tokens are DNS ``CNAME`` records that point to DKIM public keys hosted | 
|  | 390         by Amazon SES. To complete the DKIM verification process, these tokens | 
|  | 391         must be published in the domain's DNS.  The tokens must remain | 
|  | 392         published in order for Easy DKIM signing to function correctly. | 
|  | 393 | 
|  | 394         After the tokens are added to the domain's DNS, Amazon SES will be able | 
|  | 395         to DKIM-sign email originating from that domain.  To enable or disable | 
|  | 396         Easy DKIM signing for a domain, use the ``SetIdentityDkimEnabled`` | 
|  | 397         action.  For more information about Easy DKIM, go to the `Amazon SES | 
|  | 398         Developer Guide | 
|  | 399         <http://docs.amazonwebservices.com/ses/latest/DeveloperGuide>`_. | 
|  | 400 | 
|  | 401         :type domain: string | 
|  | 402         :param domain: The domain name. | 
|  | 403 | 
|  | 404         """ | 
|  | 405         return self._make_request('VerifyDomainDkim', { | 
|  | 406             'Domain': domain, | 
|  | 407         }) | 
|  | 408 | 
|  | 409     def set_identity_dkim_enabled(self, identity, dkim_enabled): | 
|  | 410         """Enables or disables DKIM signing of email sent from an identity. | 
|  | 411 | 
|  | 412         * If Easy DKIM signing is enabled for a domain name identity (e.g., | 
|  | 413         * ``example.com``), | 
|  | 414           then Amazon SES will DKIM-sign all email sent by addresses under that | 
|  | 415           domain name (e.g., ``user@example.com``) | 
|  | 416         * If Easy DKIM signing is enabled for an email address, then Amazon SES | 
|  | 417           will DKIM-sign all email sent by that email address. | 
|  | 418 | 
|  | 419         For email addresses (e.g., ``user@example.com``), you can only enable | 
|  | 420         Easy DKIM signing  if the corresponding domain (e.g., ``example.com``) | 
|  | 421         has been set up for Easy DKIM using the AWS Console or the | 
|  | 422         ``VerifyDomainDkim`` action. | 
|  | 423 | 
|  | 424         :type identity: string | 
|  | 425         :param identity: An email address or domain name. | 
|  | 426 | 
|  | 427         :type dkim_enabled: bool | 
|  | 428         :param dkim_enabled: Specifies whether or not to enable DKIM signing. | 
|  | 429 | 
|  | 430         """ | 
|  | 431         return self._make_request('SetIdentityDkimEnabled', { | 
|  | 432             'Identity': identity, | 
|  | 433             'DkimEnabled': 'true' if dkim_enabled else 'false' | 
|  | 434         }) | 
|  | 435 | 
|  | 436     def get_identity_dkim_attributes(self, identities): | 
|  | 437         """Get attributes associated with a list of verified identities. | 
|  | 438 | 
|  | 439         Given a list of verified identities (email addresses and/or domains), | 
|  | 440         returns a structure describing identity notification attributes. | 
|  | 441 | 
|  | 442         :type identities: list | 
|  | 443         :param identities: A list of verified identities (email addresses | 
|  | 444             and/or domains). | 
|  | 445 | 
|  | 446         """ | 
|  | 447         params = {} | 
|  | 448         self._build_list_params(params, identities, 'Identities.member') | 
|  | 449         return self._make_request('GetIdentityDkimAttributes', params) | 
|  | 450 | 
|  | 451     def list_identities(self): | 
|  | 452         """Returns a list containing all of the identities (email addresses | 
|  | 453         and domains) for a specific AWS Account, regardless of | 
|  | 454         verification status. | 
|  | 455 | 
|  | 456         :rtype: dict | 
|  | 457         :returns: A ListIdentitiesResponse structure. Note that | 
|  | 458                   keys must be unicode strings. | 
|  | 459         """ | 
|  | 460         return self._make_request('ListIdentities') | 
|  | 461 | 
|  | 462     def get_identity_verification_attributes(self, identities): | 
|  | 463         """Given a list of identities (email addresses and/or domains), | 
|  | 464         returns the verification status and (for domain identities) | 
|  | 465         the verification token for each identity. | 
|  | 466 | 
|  | 467         :type identities: list of strings or string | 
|  | 468         :param identities: List of identities. | 
|  | 469 | 
|  | 470         :rtype: dict | 
|  | 471         :returns: A GetIdentityVerificationAttributesResponse structure. | 
|  | 472                   Note that keys must be unicode strings. | 
|  | 473         """ | 
|  | 474         params = {} | 
|  | 475         self._build_list_params(params, identities, | 
|  | 476                                'Identities.member') | 
|  | 477         return self._make_request('GetIdentityVerificationAttributes', params) | 
|  | 478 | 
|  | 479     def verify_domain_identity(self, domain): | 
|  | 480         """Verifies a domain. | 
|  | 481 | 
|  | 482         :type domain: string | 
|  | 483         :param domain: The domain to be verified. | 
|  | 484 | 
|  | 485         :rtype: dict | 
|  | 486         :returns: A VerifyDomainIdentityResponse structure. Note that | 
|  | 487                   keys must be unicode strings. | 
|  | 488         """ | 
|  | 489         return self._make_request('VerifyDomainIdentity', { | 
|  | 490             'Domain': domain, | 
|  | 491         }) | 
|  | 492 | 
|  | 493     def verify_email_identity(self, email_address): | 
|  | 494         """Verifies an email address. This action causes a confirmation | 
|  | 495         email message to be sent to the specified address. | 
|  | 496 | 
|  | 497         :type email_adddress: string | 
|  | 498         :param email_address: The email address to be verified. | 
|  | 499 | 
|  | 500         :rtype: dict | 
|  | 501         :returns: A VerifyEmailIdentityResponse structure. Note that keys must | 
|  | 502                   be unicode strings. | 
|  | 503         """ | 
|  | 504         return self._make_request('VerifyEmailIdentity', { | 
|  | 505             'EmailAddress': email_address, | 
|  | 506         }) | 
|  | 507 | 
|  | 508     def delete_identity(self, identity): | 
|  | 509         """Deletes the specified identity (email address or domain) from | 
|  | 510         the list of verified identities. | 
|  | 511 | 
|  | 512         :type identity: string | 
|  | 513         :param identity: The identity to be deleted. | 
|  | 514 | 
|  | 515         :rtype: dict | 
|  | 516         :returns: A DeleteIdentityResponse structure. Note that keys must | 
|  | 517                   be unicode strings. | 
|  | 518         """ | 
|  | 519         return self._make_request('DeleteIdentity', { | 
|  | 520             'Identity': identity, | 
|  | 521         }) | 
| OLD | NEW | 
|---|