SES: Implement SendBulkTemplatedEmail (#5361)

This commit is contained in:
deepaksrgm2010 2022-08-11 19:13:33 +02:00 committed by GitHub
parent 464404a817
commit 2d6f04d01c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 183 additions and 5 deletions

View File

@ -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
): ):

View File

@ -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>

View File

@ -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")