diff --git a/products/messaging/backend/providers/ses.py b/products/messaging/backend/providers/ses.py index 193323ef4066b..beefa5e4a35bb 100644 --- a/products/messaging/backend/providers/ses.py +++ b/products/messaging/backend/providers/ses.py @@ -20,11 +20,36 @@ def __init__(self): region_name=settings.SES_REGION, endpoint_url=settings.SES_ENDPOINT if settings.SES_ENDPOINT else None, ) + self.tenant_client = boto3.client( + "sesv2", + aws_access_key_id=settings.SES_ACCESS_KEY_ID, + aws_secret_access_key=settings.SES_SECRET_ACCESS_KEY, + region_name=settings.SES_REGION, + endpoint_url=settings.SES_ENDPOINT if settings.SES_ENDPOINT else None, + ) def create_email_domain(self, domain: str, team_id: int): - # NOTE: For sesv1 creation is done through verification + # NOTE: For sesv1, domain Identity creation is done through verification self.verify_email_domain(domain, team_id) + # Create a tenant for the domain if not exists + tenant_name = f"team-{team_id}" + try: + self.tenant_client.create_tenant(TenantName=tenant_name, Tags=[{"Key": "team_id", "Value": str(team_id)}]) + except ClientError as e: + if e.response["Error"]["Code"] != "AlreadyExistsException": + raise + + # Associate the new domain identity with the tenant + try: + self.tenant_client.create_tenant_resource_association( + TenantName=tenant_name, + ResourceArn=f"arn:aws:ses:{settings.SES_REGION}:{self.tenant_client.get_caller_identity()['Account']}:identity/{domain}", + ) + except ClientError as e: + if e.response["Error"]["Code"] != "AlreadyExistsException": + raise + def verify_email_domain(self, domain: str, team_id: int): # Validate the domain contains valid characters for a domain name DOMAIN_REGEX = r"(?i)^([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$" diff --git a/products/messaging/backend/test/test_ses_provider.py b/products/messaging/backend/test/test_ses_provider.py index 5cd0e9d6ec939..5494e7ba9f858 100644 --- a/products/messaging/backend/test/test_ses_provider.py +++ b/products/messaging/backend/test/test_ses_provider.py @@ -28,7 +28,37 @@ def test_init_with_valid_credentials(self): def test_create_email_domain_success(self): provider = SESProvider() - provider.create_email_domain(TEST_DOMAIN, team_id=1) + + # Mock the client on the provider instance + with ( + patch.object(provider, "client") as mock_client, + patch.object(provider, "tenant_client") as mock_tenant_client, + ): + # Mock the verification attributes to return a success status + mock_client.get_identity_verification_attributes.return_value = { + "VerificationAttributes": { + TEST_DOMAIN: { + "VerificationStatus": "Success", + "VerificationToken": "test-token-123", + } + } + } + + # Mock DKIM attributes to return a success status + mock_client.get_identity_dkim_attributes.return_value = { + "DkimAttributes": {TEST_DOMAIN: {"DkimVerificationStatus": "Success"}} + } + + # Mock the domain verification and DKIM setup calls + mock_client.verify_domain_identity.return_value = {"VerificationToken": "test-token-123"} + mock_client.verify_domain_dkim.return_value = {"DkimTokens": ["token1", "token2", "token3"]} + + # Mock tenant client methods + mock_tenant_client.create_tenant.return_value = {} + mock_tenant_client.get_caller_identity.return_value = {"Account": "123456789012"} + mock_tenant_client.create_tenant_resource_association.return_value = {} + + provider.create_email_domain(TEST_DOMAIN, team_id=1) @patch("products.messaging.backend.providers.ses.boto3.client") def test_create_email_domain_invalid_domain(self, mock_boto_client):