SES: Implement SendBulkTemplatedEmail (#5361)
This commit is contained in:
		
							parent
							
								
									464404a817
								
							
						
					
					
						commit
						2d6f04d01c
					
				| @ -79,6 +79,15 @@ class TemplateMessage(BaseModel): | |||||||
|         self.destinations = destinations |         self.destinations = destinations | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | class BulkTemplateMessage(BaseModel): | ||||||
|  |     def __init__(self, message_ids, source, template, template_data, destinations): | ||||||
|  |         self.ids = message_ids | ||||||
|  |         self.source = source | ||||||
|  |         self.template = template | ||||||
|  |         self.template_data = template_data | ||||||
|  |         self.destinations = destinations | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| class RawMessage(BaseModel): | class RawMessage(BaseModel): | ||||||
|     def __init__(self, message_id, source, destinations, raw_data): |     def __init__(self, message_id, source, destinations, raw_data): | ||||||
|         self.id = message_id |         self.id = message_id | ||||||
| @ -181,6 +190,38 @@ class SESBackend(BaseBackend): | |||||||
|         self.sent_message_count += recipient_count |         self.sent_message_count += recipient_count | ||||||
|         return message |         return message | ||||||
| 
 | 
 | ||||||
|  |     def send_bulk_templated_email( | ||||||
|  |         self, source, template, template_data, destinations, region | ||||||
|  |     ): | ||||||
|  |         recipient_count = len(destinations) | ||||||
|  |         if recipient_count > RECIPIENT_LIMIT: | ||||||
|  |             raise MessageRejectedError("Too many destinations.") | ||||||
|  | 
 | ||||||
|  |         total_recipient_count = sum( | ||||||
|  |             map(lambda d: sum(map(len, d["Destination"].values())), destinations) | ||||||
|  |         ) | ||||||
|  |         if total_recipient_count > RECIPIENT_LIMIT: | ||||||
|  |             raise MessageRejectedError("Too many destinations.") | ||||||
|  | 
 | ||||||
|  |         if not self._is_verified_address(source): | ||||||
|  |             self.rejected_messages_count += 1 | ||||||
|  |             raise MessageRejectedError("Email address not verified %s" % source) | ||||||
|  | 
 | ||||||
|  |         if not self.templates.get(template[0]): | ||||||
|  |             raise TemplateDoesNotExist("Template (%s) does not exist" % template[0]) | ||||||
|  | 
 | ||||||
|  |         self.__process_sns_feedback__(source, destinations, region) | ||||||
|  | 
 | ||||||
|  |         message_id = get_random_message_id() | ||||||
|  |         message = TemplateMessage( | ||||||
|  |             message_id, source, template, template_data, destinations | ||||||
|  |         ) | ||||||
|  |         self.sent_messages.append(message) | ||||||
|  |         self.sent_message_count += total_recipient_count | ||||||
|  | 
 | ||||||
|  |         ids = list(map(lambda x: get_random_message_id(), range(len(destinations)))) | ||||||
|  |         return BulkTemplateMessage(ids, source, template, template_data, destinations) | ||||||
|  | 
 | ||||||
|     def send_templated_email( |     def send_templated_email( | ||||||
|         self, source, template, template_data, destinations, region |         self, source, template, template_data, destinations, region | ||||||
|     ): |     ): | ||||||
|  | |||||||
| @ -94,6 +94,40 @@ class EmailResponse(BaseResponse): | |||||||
|         template = self.response_template(SEND_TEMPLATED_EMAIL_RESPONSE) |         template = self.response_template(SEND_TEMPLATED_EMAIL_RESPONSE) | ||||||
|         return template.render(message=message) |         return template.render(message=message) | ||||||
| 
 | 
 | ||||||
|  |     def send_bulk_templated_email(self): | ||||||
|  |         source = self.querystring.get("Source")[0] | ||||||
|  |         template = self.querystring.get("Template") | ||||||
|  |         template_data = self.querystring.get("DefaultTemplateData") | ||||||
|  | 
 | ||||||
|  |         destinations = [] | ||||||
|  |         for i in range(1, 52): | ||||||
|  |             destination_field = ( | ||||||
|  |                 "Destinations.member.%s.Destination.ToAddresses.member.1" % (i) | ||||||
|  |             ) | ||||||
|  |             if self.querystring.get(destination_field) is None: | ||||||
|  |                 break | ||||||
|  |             destination = {"ToAddresses": [], "CcAddresses": [], "BccAddresses": []} | ||||||
|  |             for dest_type in destination: | ||||||
|  |                 # consume up to 51 to allow exception | ||||||
|  |                 for j in range(1, 52): | ||||||
|  |                     field = "Destinations.member.%s.Destination.%s.member.%s" % ( | ||||||
|  |                         i, | ||||||
|  |                         dest_type, | ||||||
|  |                         j, | ||||||
|  |                     ) | ||||||
|  |                     address = self.querystring.get(field) | ||||||
|  |                     if address is None: | ||||||
|  |                         break | ||||||
|  |                     destination[dest_type].append(address[0]) | ||||||
|  |             destinations.append({"Destination": destination}) | ||||||
|  | 
 | ||||||
|  |         message = self.backend.send_bulk_templated_email( | ||||||
|  |             source, template, template_data, destinations, self.region | ||||||
|  |         ) | ||||||
|  |         template = self.response_template(SEND_BULK_TEMPLATED_EMAIL_RESPONSE) | ||||||
|  |         result = template.render(message=message) | ||||||
|  |         return result | ||||||
|  | 
 | ||||||
|     def send_raw_email(self): |     def send_raw_email(self): | ||||||
|         source = self.querystring.get("Source") |         source = self.querystring.get("Source") | ||||||
|         if source is not None: |         if source is not None: | ||||||
| @ -401,6 +435,19 @@ SEND_TEMPLATED_EMAIL_RESPONSE = """<SendTemplatedEmailResponse xmlns="http://ses | |||||||
|   </ResponseMetadata> |   </ResponseMetadata> | ||||||
| </SendTemplatedEmailResponse>""" | </SendTemplatedEmailResponse>""" | ||||||
| 
 | 
 | ||||||
|  | SEND_BULK_TEMPLATED_EMAIL_RESPONSE = """<SendBulkTemplatedEmailResponse xmlns="http://ses.amazonaws.com/doc/2010-12-01/"> | ||||||
|  |   <SendBulkTemplatedEmailResult> | ||||||
|  |     {% for id in message.ids %} | ||||||
|  |         <BulkEmailDestinationStatus> | ||||||
|  |             <MessageId>{{ id }}</MessageId> | ||||||
|  |         </BulkEmailDestinationStatus> | ||||||
|  |     {% endfor %} | ||||||
|  |   </SendBulkTemplatedEmailResult> | ||||||
|  |   <ResponseMetadata> | ||||||
|  |     <RequestId>d5964849-c866-11e0-9beb-01a62d68c57f</RequestId> | ||||||
|  |   </ResponseMetadata> | ||||||
|  | </SendBulkTemplatedEmailResponse>""" | ||||||
|  | 
 | ||||||
| SEND_RAW_EMAIL_RESPONSE = """<SendRawEmailResponse xmlns="http://ses.amazonaws.com/doc/2010-12-01/"> | SEND_RAW_EMAIL_RESPONSE = """<SendRawEmailResponse xmlns="http://ses.amazonaws.com/doc/2010-12-01/"> | ||||||
|   <SendRawEmailResult> |   <SendRawEmailResult> | ||||||
|     <MessageId>{{ message.id }}</MessageId> |     <MessageId>{{ message.id }}</MessageId> | ||||||
| @ -486,7 +533,6 @@ CREATE_CONFIGURATION_SET = """<CreateConfigurationSetResponse xmlns="http://ses. | |||||||
|   </ResponseMetadata> |   </ResponseMetadata> | ||||||
| </CreateConfigurationSetResponse>""" | </CreateConfigurationSetResponse>""" | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| CREATE_CONFIGURATION_SET_EVENT_DESTINATION = """<CreateConfigurationSetEventDestinationResponse xmlns="http://ses.amazonaws.com/doc/2010-12-01/"> | CREATE_CONFIGURATION_SET_EVENT_DESTINATION = """<CreateConfigurationSetEventDestinationResponse xmlns="http://ses.amazonaws.com/doc/2010-12-01/"> | ||||||
|   <CreateConfigurationSetEventDestinationResult/> |   <CreateConfigurationSetEventDestinationResult/> | ||||||
|   <ResponseMetadata> |   <ResponseMetadata> | ||||||
| @ -522,7 +568,6 @@ GET_TEMPLATE = """<GetTemplateResponse xmlns="http://ses.amazonaws.com/doc/2010- | |||||||
|     </ResponseMetadata> |     </ResponseMetadata> | ||||||
| </GetTemplateResponse>""" | </GetTemplateResponse>""" | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| LIST_TEMPLATES = """<ListTemplatesResponse xmlns="http://ses.amazonaws.com/doc/2010-12-01/"> | LIST_TEMPLATES = """<ListTemplatesResponse xmlns="http://ses.amazonaws.com/doc/2010-12-01/"> | ||||||
|     <ListTemplatesResult> |     <ListTemplatesResult> | ||||||
|         <TemplatesMetadata> |         <TemplatesMetadata> | ||||||
|  | |||||||
| @ -1,14 +1,11 @@ | |||||||
| import json | import json | ||||||
| 
 |  | ||||||
| import boto3 | import boto3 | ||||||
| from botocore.exceptions import ClientError | from botocore.exceptions import ClientError | ||||||
| from botocore.exceptions import ParamValidationError | from botocore.exceptions import ParamValidationError | ||||||
| from email.mime.multipart import MIMEMultipart | from email.mime.multipart import MIMEMultipart | ||||||
| from email.mime.text import MIMEText | from email.mime.text import MIMEText | ||||||
| import pytest | import pytest | ||||||
| 
 |  | ||||||
| import sure  # noqa # pylint: disable=unused-import | import sure  # noqa # pylint: disable=unused-import | ||||||
| 
 |  | ||||||
| from moto import mock_ses | from moto import mock_ses | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @ -174,6 +171,101 @@ def test_send_email_invalid_address(): | |||||||
|     err["Message"].should.equal("Missing domain") |     err["Message"].should.equal("Missing domain") | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @mock_ses | ||||||
|  | def test_send_bulk_templated_email(): | ||||||
|  |     conn = boto3.client("ses", region_name="us-east-1") | ||||||
|  | 
 | ||||||
|  |     kwargs = dict( | ||||||
|  |         Source="test@example.com", | ||||||
|  |         Destinations=[ | ||||||
|  |             { | ||||||
|  |                 "Destination": { | ||||||
|  |                     "ToAddresses": ["test_to@example.com"], | ||||||
|  |                     "CcAddresses": ["test_cc@example.com"], | ||||||
|  |                     "BccAddresses": ["test_bcc@example.com"], | ||||||
|  |                 } | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |                 "Destination": { | ||||||
|  |                     "ToAddresses": ["test_to1@example.com"], | ||||||
|  |                     "CcAddresses": ["test_cc1@example.com"], | ||||||
|  |                     "BccAddresses": ["test_bcc1@example.com"], | ||||||
|  |                 } | ||||||
|  |             }, | ||||||
|  |         ], | ||||||
|  |         Template="test_template", | ||||||
|  |         DefaultTemplateData='{"name": "test"}', | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     with pytest.raises(ClientError) as ex: | ||||||
|  |         conn.send_bulk_templated_email(**kwargs) | ||||||
|  | 
 | ||||||
|  |     ex.value.response["Error"]["Code"].should.equal("MessageRejected") | ||||||
|  |     ex.value.response["Error"]["Message"].should.equal( | ||||||
|  |         "Email address not verified test@example.com" | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     conn.verify_domain_identity(Domain="example.com") | ||||||
|  | 
 | ||||||
|  |     with pytest.raises(ClientError) as ex: | ||||||
|  |         conn.send_bulk_templated_email(**kwargs) | ||||||
|  | 
 | ||||||
|  |     ex.value.response["Error"]["Code"].should.equal("TemplateDoesNotExist") | ||||||
|  | 
 | ||||||
|  |     conn.create_template( | ||||||
|  |         Template={ | ||||||
|  |             "TemplateName": "test_template", | ||||||
|  |             "SubjectPart": "lalala", | ||||||
|  |             "HtmlPart": "", | ||||||
|  |             "TextPart": "", | ||||||
|  |         } | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     conn.send_bulk_templated_email(**kwargs) | ||||||
|  | 
 | ||||||
|  |     too_many_destinations = list( | ||||||
|  |         { | ||||||
|  |             "Destination": { | ||||||
|  |                 "ToAddresses": ["to%s@example.com" % i], | ||||||
|  |                 "CcAddresses": [], | ||||||
|  |                 "BccAddresses": [], | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         for i in range(51) | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     with pytest.raises(ClientError) as ex: | ||||||
|  |         args = dict(kwargs, Destinations=too_many_destinations) | ||||||
|  |         conn.send_bulk_templated_email(**args) | ||||||
|  | 
 | ||||||
|  |     ex.value.response["Error"]["Code"].should.equal("MessageRejected") | ||||||
|  |     ex.value.response["Error"]["Message"].should.equal("Too many destinations.") | ||||||
|  | 
 | ||||||
|  |     too_many_destinations = list("to%s@example.com" % i for i in range(51)) | ||||||
|  | 
 | ||||||
|  |     with pytest.raises(ClientError) as ex: | ||||||
|  |         args = dict( | ||||||
|  |             kwargs, | ||||||
|  |             Destinations=[ | ||||||
|  |                 { | ||||||
|  |                     "Destination": { | ||||||
|  |                         "ToAddresses": too_many_destinations, | ||||||
|  |                         "CcAddresses": [], | ||||||
|  |                         "BccAddresses": [], | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             ], | ||||||
|  |         ) | ||||||
|  |         conn.send_bulk_templated_email(**args) | ||||||
|  | 
 | ||||||
|  |     ex.value.response["Error"]["Code"].should.equal("MessageRejected") | ||||||
|  |     ex.value.response["Error"]["Message"].should.equal("Too many destinations.") | ||||||
|  | 
 | ||||||
|  |     send_quota = conn.get_send_quota() | ||||||
|  |     sent_count = int(send_quota["SentLast24Hours"]) | ||||||
|  |     sent_count.should.equal(6) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| @mock_ses | @mock_ses | ||||||
| def test_send_templated_email(): | def test_send_templated_email(): | ||||||
|     conn = boto3.client("ses", region_name="us-east-1") |     conn = boto3.client("ses", region_name="us-east-1") | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user