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
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):
def __init__(self, message_id, source, destinations, raw_data):
self.id = message_id
@ -181,6 +190,38 @@ class SESBackend(BaseBackend):
self.sent_message_count += recipient_count
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(
self, source, template, template_data, destinations, region
):

View File

@ -94,6 +94,40 @@ class EmailResponse(BaseResponse):
template = self.response_template(SEND_TEMPLATED_EMAIL_RESPONSE)
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):
source = self.querystring.get("Source")
if source is not None:
@ -401,6 +435,19 @@ SEND_TEMPLATED_EMAIL_RESPONSE = """<SendTemplatedEmailResponse xmlns="http://ses
</ResponseMetadata>
</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/">
<SendRawEmailResult>
<MessageId>{{ message.id }}</MessageId>
@ -486,7 +533,6 @@ CREATE_CONFIGURATION_SET = """<CreateConfigurationSetResponse xmlns="http://ses.
</ResponseMetadata>
</CreateConfigurationSetResponse>"""
CREATE_CONFIGURATION_SET_EVENT_DESTINATION = """<CreateConfigurationSetEventDestinationResponse xmlns="http://ses.amazonaws.com/doc/2010-12-01/">
<CreateConfigurationSetEventDestinationResult/>
<ResponseMetadata>
@ -522,7 +568,6 @@ GET_TEMPLATE = """<GetTemplateResponse xmlns="http://ses.amazonaws.com/doc/2010-
</ResponseMetadata>
</GetTemplateResponse>"""
LIST_TEMPLATES = """<ListTemplatesResponse xmlns="http://ses.amazonaws.com/doc/2010-12-01/">
<ListTemplatesResult>
<TemplatesMetadata>

View File

@ -1,14 +1,11 @@
import json
import boto3
from botocore.exceptions import ClientError
from botocore.exceptions import ParamValidationError
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
import pytest
import sure # noqa # pylint: disable=unused-import
from moto import mock_ses
@ -174,6 +171,101 @@ def test_send_email_invalid_address():
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
def test_send_templated_email():
conn = boto3.client("ses", region_name="us-east-1")