Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 26 additions & 1 deletion products/messaging/backend/providers/ses.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,}$"
Expand Down
32 changes: 31 additions & 1 deletion products/messaging/backend/test/test_ses_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
Loading