SESv2: send_email(): raw emails are send as JSON (#6405)
This commit is contained in:
parent
759f26c6d0
commit
1dfbeed5a7
@ -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.")
|
||||
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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():
|
||||
|
Loading…
Reference in New Issue
Block a user