How do I verify a domain registered in AWS Route 53 with AWS Simple Email Service (SES) purely through the Python client, boto3?

0

I have read every ounce of documentation I could find related to this topic. I've been using the boto3 Python client to work with AWS to register a new domain and verify it to be used with the AWS Simple Email Service (SES).

This is an entirely painless task in just a few clicks with the GUI:

Enter image description here

And I thought, reading the SES boto3 documentation, I could achieve the same thing via API with the Python client.

But nothing happens. I get no error. No indication appears in the GUI that the verification is pending (the way it does when done through the GUI). Even after 72 hours, nothing changes. But when I do it in the GUI, it works pretty quickly, so presumably I just missed some important API step.

%pip install boto3 # a Jupyter-ism
import boto3
import time

class Domain:
    
    # AWS Route 53 Domains Documentation:
    
    # https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/route53domains.html#Route53Domains.Client.get_operation_detail
    
    def __init__(self, static_domain=False): # identity: Identity
        self.client = boto3.client('route53domains')
        
        if static_domain:
            
            domain_list_response = self.client.list_domains()
            print(domain_list_response)
            domain_owned = False
            
            for domain in domain_list_response['Domains']:
                if domain['DomainName'] == static_domain:
                    domain_owned = True
                    
            if domain_owned:
                print('Domain already owned...')
                self.name = static_domain
                
            else:
                availability_response = self.client.check_domain_availability(
                    DomainName=static_domain
                )

                first_name = 'First' # identity.fname
                last_name = 'Last' # identity.lname
                org_name = 'Testing123' # identity.org
                address = '123 Testing Drive' # idenity.addr
                city = 'TestingCity' # identity.city
                state = 'TestingState' # identity.state_code
                country = 'US' # identity.country_code
                zip_code = '12345' # identity.zip_code
                phone = '+1.1234567890' # identity.phone
                email = f'admin@{static_domain}'

                if availability_response['Availability'] == 'AVAILABLE':
                    print('Domain available...')

                    registration_response = self.client.register_domain(
                        DomainName=static_domain,
                        DurationInYears=1,
                        AutoRenew=False,
                        AdminContact={
                            'FirstName': first_name,
                            'LastName': last_name,
                            'ContactType': 'PERSON',
                            'OrganizationName': org_name,
                            'AddressLine1': address,
                            'City': city,
                            'State': state,
                            'CountryCode': country,
                            'ZipCode': zip_code,
                            'PhoneNumber': phone,
                            'Email': email
                        },
                        RegistrantContact={
                            'FirstName': first_name,
                            'LastName': last_name,
                            'ContactType': 'PERSON',
                            'OrganizationName': org_name,
                            'AddressLine1': address,
                            'City': city,
                            'State': state,
                            'CountryCode': country,
                            'ZipCode': zip_code,
                            'PhoneNumber': phone,
                            'Email': email
                        },
                        TechContact={
                            'FirstName': first_name,
                            'LastName': last_name,
                            'ContactType': 'PERSON',
                            'OrganizationName': org_name,
                            'AddressLine1': address,
                            'City': city,
                            'State': state,
                            'CountryCode': country,
                            'ZipCode': zip_code,
                            'PhoneNumber': phone,
                            'Email': email
                        },
                        PrivacyProtectAdminContact=False,
                        PrivacyProtectRegistrantContact=False,
                        PrivacyProtectTechContact=False
                    )
                    print(registration_response)
                    operation_id = registration_response['OperationId']
                    print(operation_id)
                    operation_detail_response = self.client.get_operation_detail(
                        OperationId=operation_id
                    )
                    print(operation_detail_response)
                    while operation_detail_response['Status'] == 'IN_PROGRESS':
                        time.sleep(10)
                        operation_detail_response = self.client.get_operation_detail(
                            OperationId=operation_id
                        )
                        print(operation_detail_response)
                        if operation_detail_response['Status'] == 'IN_PROGRESS':
                            print('Domain registration in progress...')
                        else:
                            print('Domain registration successful...')

        else:
            print('dynamic domain generation not yet supported')

class Inbox:
    def __init__(self, domain, username: str):
        self.client = boto3.client('ses')
        self.domain = domain
        self.username = username
        self.address = f'{self.username}@{self.domain.name}'
        print(f'Inbox ({self.domain.name}) ready...')
        
        verify_domain_id_response = self.client.verify_domain_identity(
            Domain=self.domain.name
        )
        print('verify_domain_id_response[\'VerificationToken\']', verify_domain_id_response['VerificationToken'])
        print('')
        
        domain_verified = False
        while domain_verified == False:
            get_identity_verification_attributes_response = self.client.get_identity_verification_attributes(
                Identities=[
                    self.domain.name,
                ]
            )
            print('get_identity_verification_attributes_response', get_identity_verification_attributes_response)
            print('')

            for identity in get_identity_verification_attributes_response['VerificationAttributes']:
                status = get_identity_verification_attributes_response['VerificationAttributes'][identity]['VerificationStatus']
                if status == 'Success':
                    domain_verified = True
                else:
                    print('Domain verification status:', status)
                    print('This could take up to 72 hours. Feel free to close this notebook and re-execute the code from this cell later. The process is happening on AWS\'s end and will not be interrupted. Once verified, re-executing the code won\'t reset the status, don\'t worry.')
                    time.sleep(100)
                    
        verify_domain_dkim_response = self.client.verify_domain_dkim(
            Domain=self.domain.name
        )
        print('verify_domain_dkim_response[\'DkimTokens\']', verify_domain_dkim_response['DkimTokens'])
        print('')
        
        enable_id_dkim_response = self.client.set_identity_dkim_enabled(
            Identity=self.domain.name,
            DkimEnabled=True
        )
        print('enable_id_dkim_response', enable_id_dkim_response)
        print('')

        # ... snip ... code to create actual inboxes + send & receive emails
        # from domain
        
        print('Inbox verified...')

domain = Domain(static_domain='testing.com')
inbox = Inbox(domain=domain, username='admin')

Output:

{'Domains': [{'DomainName': '... snip ...', 'AutoRenew': False, 'TransferLock': False, 'Expiry': datetime.datetime(2024, 1, 21, 16, 51, 56, tzinfo=tzlocal())}], 'ResponseMetadata': {'RequestId': '... snip ...', 'HTTPStatusCode': 200, 'HTTPHeaders': {'x-amzn-requestid': '... snip ...', 'content-type': 'application/x-amz-json-1.1', 'content-length': '104', 'date': 'Mon, 23 Jan 2023 02:31:27 GMT'}, 'RetryAttempts': 0}}
Domain already owned...
Inbox (... snip ...) ready...
verify_domain_id_response['VerificationToken'] ... snip ...

get_identity_verification_attributes_response {'VerificationAttributes': {'... snip ...': {'VerificationStatus': 'Pending', 'VerificationToken': '... snip ...'}}, 'ResponseMetadata': {'RequestId': '... snip ...', 'HTTPStatusCode': 200, 'HTTPHeaders': {'date': 'Mon, 23 Jan 2023 02:31:27 GMT', 'content-type': 'text/xml', 'content-length': '639', 'connection': 'keep-alive', 'x-amzn-requestid': '... snip ...'}, 'RetryAttempts': 0}}

Domain verification status: Pending
This could take up to 72 hours. Yes, really. Feel free to close this notebook and re-execute the code from this cell later. The process is happening on AWS's end and will not be interrupted. Once verified, re-executing the code won't reset the status, don't worry.

What am I missing here? Is what I'm trying to do a simple matter of something I missed in the documentation? Or is this not even viable purely via API in AWS? I suspect the former.

Maybe I need to handle the record assignment process manually with the Route53 API? I'd really appreciate an example of what that would like if so. Otherwise just a hint that this is a requirement, and that SES will not do it automatically like with the GUI functionality, would be helpful to know.

No Answers

You are not logged in. Log in to post an answer.

A good answer clearly answers the question and provides constructive feedback and encourages professional growth in the question asker.

Guidelines for Answering Questions