SESv2: send_email(): raw emails are send as JSON (#6405)

This commit is contained in:
Bert Blommers 2023-06-14 17:33:48 +00:00 committed by GitHub
parent 759f26c6d0
commit 1dfbeed5a7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 44 additions and 17 deletions

View File

@ -32,7 +32,6 @@ RECIPIENT_LIMIT = 50
class SESFeedback(BaseModel):
BOUNCE = "Bounce"
COMPLAINT = "Complaint"
DELIVERY = "Delivery"
@ -340,23 +339,20 @@ class SESBackend(BaseBackend):
f"Did not have authority to send from email {source_email_address}"
)
recipient_count = len(destinations)
message = email.message_from_string(raw_data)
if source is None:
if message["from"] is None:
raise MessageRejectedError("Source not specified")
_, source_email_address = parseaddr(message["from"])
if not self._is_verified_address(source_email_address):
_, source = parseaddr(message["from"])
if not self._is_verified_address(source):
raise MessageRejectedError(
f"Did not have authority to send from email {source_email_address}"
f"Did not have authority to send from email {source}"
)
for header in "TO", "CC", "BCC":
recipient_count += sum(
d.strip() and 1 or 0 for d in message.get(header, "").split(",")
)
if recipient_count > RECIPIENT_LIMIT:
destinations += [d.strip() for d in message.get(header, "").split(",") if d]
if len(destinations) > RECIPIENT_LIMIT:
raise MessageRejectedError("Too many recipients.")
for address in [addr for addr in [source, *destinations] if addr is not None]:
msg = is_valid_address(address)
@ -365,7 +361,7 @@ class SESBackend(BaseBackend):
self.__process_sns_feedback__(source, destinations)
self.sent_message_count += recipient_count
self.sent_message_count += len(destinations)
message_id = get_random_message_id()
raw_message = RawMessage(message_id, source, destinations, raw_data)
self.sent_messages.append(raw_message)
@ -416,7 +412,6 @@ class SESBackend(BaseBackend):
def create_configuration_set_event_destination(
self, configuration_set_name: str, event_destination: Dict[str, Any]
) -> None:
if self.config_set.get(configuration_set_name) is None:
raise ConfigurationSetDoesNotExist("Invalid Configuration Set Name.")

View File

@ -181,7 +181,6 @@ class EmailResponse(BaseResponse):
return template.render()
def set_identity_notification_topic(self) -> str:
identity = self.querystring.get("Identity")[0] # type: ignore
not_type = self.querystring.get("NotificationType")[0] # type: ignore
sns_topic = self.querystring.get("SnsTopic") # type: ignore
@ -212,7 +211,6 @@ class EmailResponse(BaseResponse):
return template.render(name=configuration_set_name)
def create_configuration_set_event_destination(self) -> str:
configuration_set_name = self._get_param("ConfigurationSetName") # type: ignore
is_configuration_event_enabled = self.querystring.get(
"EventDestination.Enabled"

View File

@ -1,4 +1,5 @@
"""Handles incoming sesv2 requests, invokes methods, returns responses."""
import base64
import json
from moto.core.responses import BaseResponse
@ -23,9 +24,9 @@ class SESV2Response(BaseResponse):
def send_email(self) -> str:
"""Piggy back on functionality from v1 mostly"""
params = get_params_dict(self.querystring)
params = json.loads(self.body)
from_email_address = params.get("FromEmailAddress")
destination = params.get("Destination")
destination = params.get("Destination", {})
content = params.get("Content")
if "Raw" in content:
all_destinations: List[str] = []
@ -38,7 +39,7 @@ class SESV2Response(BaseResponse):
message = self.sesv2_backend.send_raw_email(
source=from_email_address,
destinations=all_destinations,
raw_data=content["Raw"]["Data"],
raw_data=base64.b64decode(content["Raw"]["Data"]).decode("utf-8"),
)
elif "Simple" in content:
message = self.sesv2_backend.send_email( # type: ignore

View File

@ -1,7 +1,9 @@
import boto3
from botocore.exceptions import ClientError
import pytest
from moto import mock_sesv2, mock_ses
from moto import mock_sesv2, mock_ses, settings
from moto.ses.models import ses_backends, RawMessage
from tests import DEFAULT_ACCOUNT_ID
from ..test_ses.test_ses_boto3 import get_raw_email
@ -63,11 +65,42 @@ def test_send_raw_email(ses_v1): # pylint: disable=redefined-outer-name
ses_v1.verify_email_identity(EmailAddress="test@example.com")
conn.send_email(**kwargs)
# Verify
send_quota = ses_v1.get_send_quota()
# 2 destinations in the message, two in the 'Destination'-argument
assert int(send_quota["SentLast24Hours"]) == 4
@mock_sesv2
def test_send_raw_email__with_specific_message(
ses_v1,
): # pylint: disable=redefined-outer-name
# Setup
conn = boto3.client("sesv2", region_name="us-east-1")
message = get_raw_email()
# This particular message means that our base64-encoded body contains a '='
# Testing this to ensure that we parse the body as JSON, not as a query-dict
message["Subject"] = "Test-2"
kwargs = dict(
Content={"Raw": {"Data": message.as_bytes()}},
)
# Execute
ses_v1.verify_email_identity(EmailAddress="test@example.com")
conn.send_email(**kwargs)
# Verify
send_quota = ses_v1.get_send_quota()
sent_count = int(send_quota["SentLast24Hours"])
assert sent_count == 2
if not settings.TEST_SERVER_MODE:
backend = ses_backends[DEFAULT_ACCOUNT_ID]["us-east-1"]
msg: RawMessage = backend.sent_messages[0]
assert message.as_bytes() == msg.raw_data.encode("utf-8")
assert msg.source == "test@example.com"
assert msg.destinations == ["to@example.com", "foo@example.com"]
@mock_sesv2
def test_create_contact_list():