Email Resource Management: Automating end to end resource creation

Get started with Azure PowerShell to automate the creation of Azure Communication Services (ACS), Email Communication Services (ECS), manage custom domains, configure DNS records, verify domains, and domain linking to communication resource.

In this sample, we cover what the sample does and the prerequisites you need before running it locally on your machine.

This documentation provides a detailed guide on using Azure PowerShell to automate the creation of Azure Communication Services (ACS) and Email Communication Services (ECS). It also covers the process of managing custom domains, configuring DNS records (such as Domain, SPF, DKIM, DKIM2), verifying domains and domain linking to communication resource.

Prerequisites

Prerequisite check

  • In a command prompt, run the powershell -command $PSVersionTable.PSVersion command to check whether the PowerShell is installed or not.

Initialize all the Parameters

Before proceeding, define the variables needed for setting up the ACS, ECS, and domains, along with DNS configuration for the domains. Modify these variables based on your environment:

# Parameters for configuration

# Define the name of the Azure resource group where resources will be created
$resourceGroup = "ContosoResourceProvider1"

# Specify the region where the resources will be created
$dataLocation = "United States"

# Define the name of the Azure Communication Service resource
$commServiceName = "ContosoAcsResource1"

# Define the name of the Email Communication Service resource
$emailServiceName = "ContosoEcsResource1"

# Define the DNS zone name where the domains will be managed (replace with actual DNS zone)
$dnsZoneName = "contoso.net"

# Define the list of domains to be created and managed (replace with your own list of domains)
$domains = @(
    "sales.contoso.net",
    "marketing.contoso.net",
    "support.contoso.net",
    "technical.contoso.net",
    "info.contoso.net"
)

Connect to Azure Account

Before performing any actions with Azure resources, authenticate using the Connect-AzAccount cmdlet. This process allows you to log in and authenticate your Azure account for further tasks:

# Attempt to authenticate the Azure session using the Connect-AzAccount cmdlet
try {
    # Output message to indicate the authentication process is starting
    Write-Host "Authenticating to Azure"

    # The Connect-AzAccount cmdlet is used to authenticate the Azure account
    Connect-AzAccount
}
catch {
    # If there is an error during authentication, display the error message
    Write-Host "Error authenticating to Azure"

    # Exit the script with an error code (1) if authentication fails
    exit 1
}

Create an Azure Communication Service (ACS) resource

This part of the script creates an Azure Communication Service resource:

# Attempt to create the Communication Service resource in the specified resource group
try {
    # Output message to indicate the creation of the Communication Service resource is starting
    Write-Host "Creating Communication resource - $commServiceName"

    # The New-AzCommunicationService cmdlet is used to create a new Communication Service resource
    New-AzCommunicationService -ResourceGroupName $resourceGroup -Name $commServiceName -Location "Global" -DataLocation $dataLocation
}
catch {
    # If there is an error during the creation of the Communication Service resource, display the error message
    Write-Host "Error creating Communication resource"

    # Exit the script with an error code (1) if the creation of the Communication Service resource fails
    exit 1
}

Create Email Communication Service (ECS) resource

This part of the script creates an Email Communication Service resource:

# Attempt to create the Email Communication Service resource in the specified resource group
try {
    # Output message to indicate the creation of the Email Communication Service resource is starting
    Write-Host "Creating Email Communication resource - $emailServiceName"

    # The New-AzEmailService cmdlet is used to create a new Email Communication Service resource
    New-AzEmailService -ResourceGroupName $resourceGroup -Name $emailServiceName -DataLocation $dataLocation
}
catch {
    # If there is an error during the creation of the Email Communication Service resource, display the error message
    Write-Host "Error creating Email Communication resource: $_"

    # Exit the script with an error code (1) if the creation of the Email Communication Service resource fails
    exit 1
}

Create domains and add records set to DNS

Automate domain creation, configuration, and DNS record setup (including Domain, SPF, DKIM, DKIM2) for each domain:

Note

The maximum limit for domain creation is 800 per Email Communication Service.

Note

In our code, we work with five predefined domains, for which DNS records are added and configured.

# Loop through each domain in the predefined list of domains to create and configure them
foreach ($domainName in $domains){
    # Extract the subdomain prefix from the fully qualified domain name (e.g., "sales" from "sales.contoso.net")
    $subDomainPrefix = $domainName.split('.')[0]

    # Output the domain name that is being created
    Write-Host "Creating domain: $domainName"
    try {
        # Attempt to create the domain in the Email Communication Service resource
        # The "CustomerManaged" option means that the domain management will be done by the customer
        New-AzEmailServiceDomain -ResourceGroupName $resourceGroup -EmailServiceName $emailServiceName -Name $domainName -DomainManagement "CustomerManaged"
    }
    catch {
        # If domain creation fails, display an error message and continue with the next domain
        Write-Host "Error creating domain $domainName"
        continue
    }

    # Wait for 5 seconds before proceeding to allow time for the domain creation to be processed
    Start-Sleep -Seconds 5

    # Retrieve the domain details after creation
    # The domain details will be used during the DNS record setting request
    $domainDetailsJson = Get-AzEmailServiceDomain -ResourceGroupName $resourceGroup -EmailServiceName $emailServiceName -Name $domainName
    $domainDetails = $domainDetailsJson | ConvertFrom-Json
    
    # Add DNS records for the domain
    Add-RecordSetToDNS -subDomainPrefix $subDomainPrefix -domainName $domainName -dnsZoneName $dnsZoneName -resourceGroup $resourceGroup -emailServiceName $emailServiceName -domainDetails $domainDetails    
    
    # Check if domain details were successfully retrieved
    if ($domainDetails) {
        # Initiate domain verification process (Domain, SPF, DKIM, DKIM2)
        $result = Verify-Domain -domainName $domainName -resourceGroup $resourceGroup -emailServiceName $emailServiceName
        if ($result) {
            # If the domain is successfully verified, add it to the list of linked domains
            $linkedDomainIds += $($domainDetails.Id)
        }
        else {
            # If domain verification fails, display an error message
            Write-Host "Domain $domainName verification failed."
        }
    }
    else {
        # If domain details were not retrieved, display an error message
        Write-Host "Failed to add DNS records for domain $domainName."
    }
}

# Function to add DNS records (DKIM, DKIM2) for the domain
function Add-DkimRecord {
    param (
        [string]$dnsZoneName,
        [string]$resourceGroup,
        [string]$recordName,
        [string]$recordValue,
        [string]$recordType
    )
    try {
        # Output the attempt to check if the DNS record already exists
        Write-Host "Checking for existing $recordType record: $recordName"

        # Retrieves the DNS record set for the given subdomain prefix and type (CNAME) in the specified DNS zone and resource group. 
        # The first instance uses -ErrorAction SilentlyContinue to suppress any errors if the record set doesn't exist.
        $recordSet = Get-AzDnsRecordSet -ZoneName $dnsZoneName -ResourceGroupName $resourceGroup -name $recordName -RecordType $recordType -ErrorAction SilentlyContinue

        # If no existing record set is found (i.e., recordSet.Count is 0)
        if ($recordSet.Count -eq 0) {
            # Output a message stating that a new record is being created
            Write-Host "Creating new $recordType record: $recordName"

            # Creates a new DNS record set for the specified record type (CNAME) in the given zone and resource group
            # The TTL is set to 3600 seconds (1 hour)
            New-AzDnsRecordSet -Name $recordName -RecordType $recordType -ZoneName $dnsZoneName -ResourceGroupName $resourceGroup -Ttl 3600
            
            # Retrieve the newly created record set to add the record value
            $recordSet = Get-AzDnsRecordSet -ZoneName $dnsZoneName -ResourceGroupName $resourceGroup -name $recordName -RecordType $recordType
            
            # Add the provided record value to the newly created record set (e.g., CNAME)
            Add-AzDnsRecordConfig -RecordSet $recordSet -Cname $recordValue
            
            # Apply the changes and save the updated record set back to Azure DNS
            Set-AzDnsRecordSet -RecordSet $recordSet
        }
        else {
            # If the record already exists, notify that it has been found
            Write-Host "$recordType record already exists for: $recordName"
        }
    }
    catch {
        # If an error occurs during the execution of the try block, output an error message
        Write-Host "Error adding $recordType record for $recordName"
    }
}

# Function to add DNS records (Domain, SPF, DKIM, DKIM2) for the domain
function Add-RecordSetToDNS {
    param (
        [string]$subDomainPrefix,
        [string]$domainName,
        [string]$dnsZoneName,
        [string]$resourceGroup,
        [string]$emailServiceName,
        [PSObject]$domainDetails
    )
    try {
        # Output message indicating that DNS records are being added for the domain
        Write-Host "Adding DNS records for domain: $domainName"

        # Check if domain details are available
        if ($domainDetails) {
            # Retrieve the TXT record set for the domain
            # Retrieves the DNS record set for the given subdomain prefix and type (TXT) in the specified DNS zone and resource group. 
            # The first instance uses -ErrorAction SilentlyContinue to suppress any errors if the record set doesn't exist.
            $recordSetDomain = Get-AzDnsRecordSet -ZoneName $dnsZoneName -ResourceGroupName $resourceGroup -name $subDomainPrefix -recordtype txt -ErrorAction SilentlyContinue

            # If the TXT record set does not exist, create a new one
            if ($recordSetDomain.Count -eq 0) {
                New-AzDnsRecordSet -Name $subDomainPrefix -RecordType "TXT" -ZoneName $dnsZoneName -ResourceGroupName $resourceGroup -Ttl 3600
                $recordSetDomain = Get-AzDnsRecordSet -ZoneName $dnsZoneName -ResourceGroupName $resourceGroup -name $subDomainPrefix -recordtype txt
            }

            # Add the Domain verification record to the TXT record set
            Add-AzDnsRecordConfig -RecordSet $recordSetDomain -Value $($domainDetails.properties.VerificationRecords.Domain.Value)
            Set-AzDnsRecordSet -RecordSet $recordSetDomain

            # Check if the SPF record already added; if not, create and add it
            $existingSpfRecord = $recordSetDomain.Records | Where-Object { $_.Value -eq $domainDetails.properties.VerificationRecords.SPF.Value }
            if (-not $existingSpfRecord) {
                # Create and add the SPF record
                $RecordSetSPF = Get-AzDnsRecordSet -ZoneName $dnsZoneName -ResourceGroupName $resourceGroup -name $subDomainPrefix -recordtype txt
                Add-AzDnsRecordConfig -RecordSet $RecordSetSPF -Value $($domainDetails.properties.VerificationRecords.SPF.Value)
                Set-AzDnsRecordSet -RecordSet $RecordSetSPF                    
            }
            else {
                # If SPF record already exists, notify the user
                Write-Host "SPF record already exists for domain: $domainName"
            }

            # Call the Add-DkimRecord function for DKIM
            Add-DkimRecord -dnsZoneName $dnsZoneName -resourceGroup $resourceGroup -recordName "$($domainDetails.properties.VerificationRecords.DKIM.Name).$subDomainPrefix" -recordValue $domainDetails.properties.VerificationRecords.DKIM.Value -recordType "CNAME"

            # Call the Add-DkimRecord function for DKIM2
            Add-DkimRecord -dnsZoneName $dnsZoneName -resourceGroup $resourceGroup -recordName "$($domainDetails.properties.VerificationRecords.DKIM2.Name).$subDomainPrefix" -recordValue $domainDetails.properties.VerificationRecords.DKIM2.Value -recordType "CNAME"
        }
        else {
            # If domain details are not found, output an error message
            Write-Host "No domain details found for $domainName"
        }
    }
    catch {     
        # If an error occurs during the DNS record setup, output an error message
        Write-Host "Error adding DNS records for domain $domainName"
    }
}

Verification of domains

Initiates the domain verification process for the domains, including Domain, SPF, DKIM, and DKIM2 verifications.

# This function initiates the domain verification process for the specified domain.
# It checks verification for four types: Domain, SPF, DKIM, and DKIM2.
function Verify-Domain {
    param (
        [string]$domainName,
        [string]$resourceGroup,
        [string]$emailServiceName
    )
    try {
        Write-Host "Initiating domain verification for $domainName"

        # Define the verification types: Domain, SPF, DKIM, and DKIM2
        $verificationTypes = @('Domain', 'SPF', 'DKIM', 'DKIM2')

        # Loop through each verification type and initiate the verification process
        foreach ($verificationType in $verificationTypes) {
            Invoke-AzEmailServiceInitiateDomainVerification -ResourceGroupName $resourceGroup -EmailServiceName $emailServiceName -DomainName $domainName -VerificationType $verificationType
        }

        # After initiating the verification, call the Poll function to check the verification status
        return Poll-ForDomainVerification -domainName $domainName -resourceGroup $resourceGroup -emailServiceName $emailServiceName
    }
    catch {
        Write-Host "Error during domain verification for $domainName" # Handle any error during the process
        return $false # Return false if verification fails
    }
}

Once the domains are verified and DNS records are configured, you can link the domains to the Azure Communication Service:

Note

The maximum limit for domain linking is 1000 per Azure Communication Service.

# Link domains to the communication service

# Once the domains have been verified and the necessary DNS records are configured, 
# this section of the script links those domains to the Azure Communication Service.
# Ensure that domain verification and DNS setup are completed before linking.

# Check if there are any domains that need to be linked (i.e., domains that were successfully verified)
if ($linkedDomainIds.Count -gt 0) {
    try {
        # Output message indicating that the domains are being linked to the communication service
        Write-Host "Linking domains to communication service."

        # Link the verified domains to the Azure Communication Service
        Update-AzCommunicationService -ResourceGroupName $resourceGroup -Name $commServiceName -LinkedDomain $linkedDomainIds

        # Output message indicating that the domains have been successfully linked
        Write-Host "Domains linked successfully."
    }
    catch {
        # If there is an error during the domain linking process, display an error message
        Write-Host "Error linking domains"
    }
}
else {
    # If there are no domains to link, output a message indicating that no domains are linked
    Write-Host "No domains linked."
}

Complete PowerShell Script for Automating end to end resource creation

# Parameters for configuration

# Define the name of the Azure resource group where resources will be created
$resourceGroup = "ContosoResourceProvider1"

# Specify the region where the resources will be created
$dataLocation = "United States"

# Define the name of the Azure Communication Service resource
$commServiceName = "ContosoAcsResource1"

# Define the name of the Email Communication Service resource
$emailServiceName = "ContosoEcsResource1"

# Define the DNS zone name where the domains will be managed (replace with actual DNS zone)
$dnsZoneName = "contoso.net"

# Define the list of domains to be created and managed (replace with your own list of domains)
$domains = @(
    "sales.contoso.net",
    "marketing.contoso.net",
    "support.contoso.net",
    "technical.contoso.net",
    "info.contoso.net"
)

# Function to add DNS records (DKIM, DKIM2) for the domain
function Add-DkimRecord {
    param (
        [string]$dnsZoneName,
        [string]$resourceGroup,
        [string]$recordName,
        [string]$recordValue,
        [string]$recordType
    )
    try {
        # Output the attempt to check if the DNS record already exists
        Write-Host "Checking for existing $recordType record: $recordName"

        # Retrieves the DNS record set for the given subdomain prefix and type (CNAME) in the specified DNS zone and resource group. 
        # The first instance uses -ErrorAction SilentlyContinue to suppress any errors if the record set doesn't exist.
        $recordSet = Get-AzDnsRecordSet -ZoneName $dnsZoneName -ResourceGroupName $resourceGroup -name $recordName -RecordType $recordType -ErrorAction SilentlyContinue

        # If no existing record set is found (i.e., recordSet.Count is 0)
        if ($recordSet.Count -eq 0) {
            # Output a message stating that a new record is being created
            Write-Host "Creating new $recordType record: $recordName"

            # Creates a new DNS record set for the specified record type (CNAME) in the given zone and resource group
            # The TTL is set to 3600 seconds (1 hour)
            New-AzDnsRecordSet -Name $recordName -RecordType $recordType -ZoneName $dnsZoneName -ResourceGroupName $resourceGroup -Ttl 3600
            
            # Retrieve the newly created record set to add the record value
            $recordSet = Get-AzDnsRecordSet -ZoneName $dnsZoneName -ResourceGroupName $resourceGroup -name $recordName -RecordType $recordType
            
            # Add the provided record value to the newly created record set (e.g., CNAME)
            Add-AzDnsRecordConfig -RecordSet $recordSet -Cname $recordValue
            
            # Apply the changes and save the updated record set back to Azure DNS
            Set-AzDnsRecordSet -RecordSet $recordSet
        }
        else {
            # If the record already exists, notify that it has been found
            Write-Host "$recordType record already exists for: $recordName"
        }
    }
    catch {
        # If an error occurs during the execution of the try block, output an error message
        Write-Host "Error adding $recordType record for $recordName"
    }
}

# Function to add DNS records (Domain, SPF, DKIM, DKIM2) for the domain
function Add-RecordSetToDNS {
    param (
        [string]$subDomainPrefix,
        [string]$domainName,
        [string]$dnsZoneName,
        [string]$resourceGroup,
        [string]$emailServiceName,
        [PSObject]$domainDetails
    )
    try {
        # Output message indicating that DNS records are being added for the domain
        Write-Host "Adding DNS records for domain: $domainName"

        # Check if domain details are available
        if ($domainDetails) {
            # Retrieve the TXT record set for the domain
            # Retrieves the DNS record set for the given subdomain prefix and type (TXT) in the specified DNS zone and resource group. 
            # The first instance uses -ErrorAction SilentlyContinue to suppress any errors if the record set doesn't exist.
            $recordSetDomain = Get-AzDnsRecordSet -ZoneName $dnsZoneName -ResourceGroupName $resourceGroup -name $subDomainPrefix -recordtype txt -ErrorAction SilentlyContinue

            # If the TXT record set does not exist, create a new one
            if ($recordSetDomain.Count -eq 0) {
                New-AzDnsRecordSet -Name $subDomainPrefix -RecordType "TXT" -ZoneName $dnsZoneName -ResourceGroupName $resourceGroup -Ttl 3600
                $recordSetDomain = Get-AzDnsRecordSet -ZoneName $dnsZoneName -ResourceGroupName $resourceGroup -name $subDomainPrefix -recordtype txt
            }

            # Add the Domain verification record to the TXT record set
            Add-AzDnsRecordConfig -RecordSet $recordSetDomain -Value $($domainDetails.properties.VerificationRecords.Domain.Value)
            Set-AzDnsRecordSet -RecordSet $recordSetDomain

            # Check if the SPF record already added; if not, create and add it
            $existingSpfRecord = $recordSetDomain.Records | Where-Object { $_.Value -eq $domainDetails.properties.VerificationRecords.SPF.Value }
            if (-not $existingSpfRecord) {
                # Create and add the SPF record
                $RecordSetSPF = Get-AzDnsRecordSet -ZoneName $dnsZoneName -ResourceGroupName $resourceGroup -name $subDomainPrefix -recordtype txt
                Add-AzDnsRecordConfig -RecordSet $RecordSetSPF -Value $($domainDetails.properties.VerificationRecords.SPF.Value)
                Set-AzDnsRecordSet -RecordSet $RecordSetSPF                    
            }
            else {
                # If SPF record already exists, notify the user
                Write-Host "SPF record already exists for domain: $domainName"
            }

            # Call the Add-DkimRecord function for DKIM
            Add-DkimRecord -dnsZoneName $dnsZoneName -resourceGroup $resourceGroup -recordName "$($domainDetails.properties.VerificationRecords.DKIM.Name).$subDomainPrefix" -recordValue $domainDetails.properties.VerificationRecords.DKIM.Value -recordType "CNAME"

            # Call the Add-DkimRecord function for DKIM2
            Add-DkimRecord -dnsZoneName $dnsZoneName -resourceGroup $resourceGroup -recordName "$($domainDetails.properties.VerificationRecords.DKIM2.Name).$subDomainPrefix" -recordValue $domainDetails.properties.VerificationRecords.DKIM2.Value -recordType "CNAME"
        }
        else {
            # If domain details are not found, output an error message
            Write-Host "No domain details found for $domainName"
        }
    }
    catch {     
        # If an error occurs during the DNS record setup, output an error message
        Write-Host "Error adding DNS records for domain $domainName"
    }
}

# Verification of domains
# This function initiates the domain verification process for the specified domain.
# It checks verification for four types: Domain, SPF, DKIM, and DKIM2.
function Verify-Domain {
    param (
        [string]$domainName,
        [string]$resourceGroup,
        [string]$emailServiceName
    )
    try {
        Write-Host "Initiating domain verification for $domainName"

        # Define the verification types: Domain, SPF, DKIM, and DKIM2
        $verificationTypes = @('Domain', 'SPF', 'DKIM', 'DKIM2')

        # Loop through each verification type and initiate the verification process
        foreach ($verificationType in $verificationTypes) {
            Invoke-AzEmailServiceInitiateDomainVerification -ResourceGroupName $resourceGroup -EmailServiceName $emailServiceName -DomainName $domainName -VerificationType $verificationType
        }

        # After initiating the verification, call the Poll function to check the verification status
        return Poll-ForDomainVerification -domainName $domainName -resourceGroup $resourceGroup -emailServiceName $emailServiceName
    }
    catch {
        Write-Host "Error during domain verification for $domainName" # Handle any error during the process
        return $false # Return false if verification fails
    }
}

# Function to poll for domain verification

# This function checks the verification status of a domain, including Domain, SPF, DKIM, and DKIM2.
# It will keep checking the verification status at regular intervals (defined by $delayBetweenAttempts)
# until the domain is verified or the maximum number of attempts ($maxAttempts) is reached.
function Poll-ForDomainVerification {
    param (
        [string]$domainName,
        [string]$resourceGroup,
        [string]$emailServiceName,
        [int]$maxAttempts = 10, # Maximum number of attempts to check the domain verification status (default: 10)
        [int]$delayBetweenAttempts = 10000 # Delay between attempts in milliseconds (default: 10 seconds)
    )

    try {
        # Loop through the attempts to check the domain verification status
        for ($attempt = 1; $attempt -le $maxAttempts; $attempt++) {
			# Fetch domain details to check the verification status
            $domainDetailsJson = Get-AzEmailServiceDomain -ResourceGroupName $resourceGroup -EmailServiceName $emailServiceName -Name $domainName
            $domainDetails = $domainDetailsJson | ConvertFrom-Json
			
            if ($domainDetails) {
				# Check if all verification states (Domain, SPF, DKIM, DKIM2) are 'Verified'
                if ($($domainDetails.properties.verificationStates.Domain.Status) -eq 'Verified' -and
                    $($domainDetails.properties.verificationStates.SPF.status) -eq 'Verified' -and
                    $($domainDetails.properties.verificationStates.DKIM.status) -eq 'Verified' -and
                    $($domainDetails.properties.verificationStates.DKIM2.status) -eq 'Verified') {
                    Write-Host "Domain verified successfully."
                    return $true # Return true if all verification states are 'Verified'
                }
            }
			
            # Wait for the specified delay before checking again
            Start-Sleep -Milliseconds $delayBetweenAttempts
        }
		
        # If the maximum attempts are reached and domain is still not verified, return false
        Write-Host "Domain verification failed or timed out."
        return $false
    }
    catch {
        # Catch any errors during the polling process and return false
        Write-Host "Error polling for domain verification"
        return $false
    }
}

# Connect to Azure
# Attempt to authenticate the Azure session using the Connect-AzAccount cmdlet
try {
    # Output message to indicate the authentication process is starting
    Write-Host "Authenticating to Azure"

    # The Connect-AzAccount cmdlet is used to authenticate the Azure account
    Connect-AzAccount
}
catch {
    # If there is an error during authentication, display the error message
    Write-Host "Error authenticating to Azure"

    # Exit the script with an error code (1) if authentication fails
    exit 1
}

# Create Communication resource
# Attempt to create the Communication Service resource in the specified resource group
try {
    # Output message to indicate the creation of the Communication Service resource is starting
    Write-Host "Creating Communication resource - $commServiceName"

    # The New-AzCommunicationService cmdlet is used to create a new Communication Service resource
    New-AzCommunicationService -ResourceGroupName $resourceGroup -Name $commServiceName -Location "Global" -DataLocation $dataLocation
}
catch {
    # If there is an error during the creation of the Communication Service resource, display the error message
    Write-Host "Error creating Communication resource"

    # Exit the script with an error code (1) if the creation of the Communication Service resource fails
    exit 1
}

# Create Email Communication resource
# Attempt to create the Email Communication Service resource in the specified resource group
try {
    # Output message to indicate the creation of the Email Communication Service resource is starting
    Write-Host "Creating Email Communication resource - $emailServiceName"

    # The New-AzEmailService cmdlet is used to create a new Email Communication Service resource
    New-AzEmailService -ResourceGroupName $resourceGroup -Name $emailServiceName -DataLocation $dataLocation
}
catch {
    # If there is an error during the creation of the Email Communication Service resource, display the error message
    Write-Host "Error creating Email Communication resource: $_"

    # Exit the script with an error code (1) if the creation of the Email Communication Service resource fails
    exit 1
}

# Initialize list to store linked domains
$linkedDomainIds = @()

# Create domains and DNS records
# Loop through each domain in the predefined list of domains to create and configure them
foreach ($domainName in $domains){
    # Extract the subdomain prefix from the fully qualified domain name (e.g., "sales" from "sales.contoso.net")
    $subDomainPrefix = $domainName.split('.')[0]

    # Output the domain name that is being created
    Write-Host "Creating domain: $domainName"
    try {
        # Attempt to create the domain in the Email Communication Service resource
        # The "CustomerManaged" option means that the domain management will be done by the customer
        New-AzEmailServiceDomain -ResourceGroupName $resourceGroup -EmailServiceName $emailServiceName -Name $domainName -DomainManagement "CustomerManaged"
    }
    catch {
        # If domain creation fails, display an error message and continue with the next domain
        Write-Host "Error creating domain $domainName"
        continue
    }

    # Wait for 5 seconds before proceeding to allow time for the domain creation to be processed
    Start-Sleep -Seconds 5

    # Retrieve the domain details after creation
    $domainDetailsJson = Get-AzEmailServiceDomain -ResourceGroupName $resourceGroup -EmailServiceName $emailServiceName -Name $domainName
    $domainDetails = $domainDetailsJson | ConvertFrom-Json
    
    # Add DNS records for the domain
    Add-RecordSetToDNS -subDomainPrefix $subDomainPrefix -domainName $domainName -dnsZoneName $dnsZoneName -resourceGroup $resourceGroup -emailServiceName $emailServiceName -domainDetails $domainDetails    
    
    # Check if domain details were successfully retrieved
    if ($domainDetails) {
        # Initiate domain verification process (Domain, SPF, DKIM, DKIM2)
        $result = Verify-Domain -domainName $domainName -resourceGroup $resourceGroup -emailServiceName $emailServiceName
        if ($result) {
            # If the domain is successfully verified, add it to the list of linked domains
            $linkedDomainIds += $($domainDetails.Id)
        }
        else {
            # If domain verification fails, display an error message
            Write-Host "Domain $domainName verification failed."
        }
    }
    else {
        # If domain details were not retrieved, display an error message
        Write-Host "Failed to add DNS records for domain $domainName."
    }
}

# Link domains to the communication service
# Once the domains have been verified and the necessary DNS records are configured, 
# this section of the script links those domains to the Azure Communication Service.
# Ensure that domain verification and DNS setup are completed before linking.

# Check if there are any domains that need to be linked (i.e., domains that were successfully verified)
if ($linkedDomainIds.Count -gt 0) {
    try {
        # Output message indicating that the domains are being linked to the communication service
        Write-Host "Linking domains to communication service."

        # Link the verified domains to the Azure Communication Service
        Update-AzCommunicationService -ResourceGroupName $resourceGroup -Name $commServiceName -LinkedDomain $linkedDomainIds

        # Output message indicating that the domains have been successfully linked
        Write-Host "Domains linked successfully."
    }
    catch {
        # If there is an error during the domain linking process, display an error message
        Write-Host "Error linking domains"
    }
}
else {
    # If there are no domains to link, output a message indicating that no domains are linked
    Write-Host "No domains linked."
}

Get started with Azure CLI PowerShell to automate the creation of Azure Communication Services (ACS), Email Communication Services (ECS), manage custom domains, configure DNS records, verify domains, and domain linking to communication resource.

In this sample, we cover what the sample does and the prerequisites you need before running it locally on your machine.

This documentation provides a detailed guide on using Azure CLI PowerShell to automate the creation of Azure Communication Services (ACS) and Email Communication Services (ECS). It also covers the process of managing custom domains, configuring DNS records (such as Domain, SPF, DKIM, DKIM2), verifying domains and domain linking to communication resource.

Prerequisites

Prerequisite check

  • In a command prompt, run the powershell -command $PSVersionTable.PSVersion command to check whether the PowerShell is installed or not.
powershell -command $PSVersionTable.PSVersion
  • In a command prompt, run the az --version command to check whether the Azure CLI is installed or not.
az --version
  • Before you can use the Azure CLI to manage your resources, sign in to Azure CLI. You can sign in running the az login command from the terminal and providing your credentials.
az login

Initialize all the Parameters

Before proceeding, define the variables needed for setting up the ACS, ECS, and domains, along with DNS configuration for the domains. Modify these variables based on your environment:

# Variables
# Define the name of the Azure resource group where resources will be created
$resourceGroup = "ContosoResourceProvider1"

# Specify the region where the resources will be created. In this case, Europe.
$dataLocation = "europe"

# Define the name of the Azure Communication Service resource
$commServiceName = "ContosoAcsResource1"

# Define the name of the Email Communication Service resource
$emailServiceName = "ContosoEcsResource1"

# Define the DNS zone name where the domains will be managed (replace with actual DNS zone)
$dnsZoneName = "contoso.net"

# Define the list of domains to be created and managed (replace with your own list of domains)
$domains = @(
    "sales.contoso.net",
    "marketing.contoso.net",
    "support.contoso.net",
    "technical.contoso.net",
    "info.contoso.net"
)

Login to Azure Account

Before performing any actions with Azure resources, authenticate using the az login cmdlet. This process allows you to log in and authenticate your Azure account for further tasks:

# Log in to Azure
# Output message to indicate the authentication process is starting
Write-Host "Logging in to Azure..."
try {
    # Execute the Azure CLI login command
    az login
} catch {
    # If there is an error during authentication, display the error message
    Write-Host "Error during Azure login"

    # Exit the script with an error code (1) if authentication fails
    exit 1
}

Create an Azure Communication Service (ACS) resource

This part of the script creates an Azure Communication Service resource:

# Create Communication resource
# Output message to indicate the creation of the Communication Service resource is starting
Write-Host "Creating Communication resource - $commServiceName"
try {
    # The az communication create cmdlet is used to create a new Communication Service resource
    az communication create --name $commServiceName --resource-group $resourceGroup --location global --data-location $dataLocation
} catch {
    # If there is an error during the creation of the Communication Service resource, display the error message
    Write-Host "Error while creating Communication resource"

    # Exit the script with an error code (1) if the creation of the Communication Service resource fails
    exit 1
}

Create Email Communication Service (ECS) resource

This part of the script creates an Email Communication Service resource:

# Create Email Communication resource
# Output message to indicate the creation of the Email Communication Service resource is starting
Write-Host "Creating Email Communication resource - $emailServiceName"
try {
    # The az communication email create cmdlet is used to create a new Email Communication Service resource
    az communication email create --name $emailServiceName --resource-group $resourceGroup --location global --data-location $dataLocation
} catch {
    # If there is an error during the creation of the Email Communication Service resource, display the error message
    Write-Host "Error while creating Email Communication resource"

    # Exit the script with an error code (1) if the creation of the Email Communication Service resource fails
    exit 1
}

Create domains

Automate the creation of email domains, by following command:

Note

The maximum limit for domain creation is 800 per Email Communication Service.

Note

In our code, we work with five predefined domains, for which DNS records are added and configured.

# Create the email domain in Email Communication Service.
# The command includes the domain name, resource group, email service name, location, and domain management type (CustomerManaged)
az communication email domain create --name $domainName --resource-group $resourceGroup --email-service-name $emailServiceName --location global --domain-management CustomerManaged

Add records set to DNS

Add records to DNS record setup (including Domain, SPF, DKIM, DKIM2) for each domain.

# Function to add DNS records
function Add-RecordSetToDNS {
    param (
        [string]$domainName,
        [string]$subDomainPrefix
    )

    # Output a message indicating that DNS record sets are being added for the specified domain
    Write-Host "Adding DNS record sets for domain: $domainName"
    try {
        # Run the Azure CLI command to fetch domain details for the specified domain name
        $domainDetailsJson = az communication email domain show --resource-group $resourceGroup --email-service-name $emailServiceName --name $domainName
    } catch {
        # If an error occurs while fetching domain details, output an error message and exit the script
        Write-Host "Error fetching domain details for $domainName"
        exit 1
    }

    # If no domain details are returned, output a message and exit the script
    if (-not $domainDetailsJson) {
        Write-Host "Failed to fetch domain details for $domainName"
        exit 1
    }

    # Parse the JSON response to extract the necessary domain details
    $domainDetails = $domainDetailsJson | ConvertFrom-Json

    # Extract verification record values Domain, SPF and DKIM from the parsed JSON response
    # These values will be used to create DNS records
    $dkimName = $domainDetails.verificationRecords.DKIM.name
    $dkimValue = $domainDetails.verificationRecords.DKIM.value
    $dkim2Name = $domainDetails.verificationRecords.DKIM2.name
    $dkim2Value = $domainDetails.verificationRecords.DKIM2.value
    $spfValue = $domainDetails.verificationRecords.SPF.value
    $domainValue = $domainDetails.verificationRecords.Domain.value
    try {
        # Create the TXT DNS record for the domain's verification value
        az network dns record-set txt create --name $subDomainPrefix --zone-name $dnsZoneName --resource-group $resourceGroup
        
        # Add the domain verification record to the TXT DNS record
        az network dns record-set txt add-record --resource-group $resourceGroup --zone-name $dnsZoneName --record-set-name $subDomainPrefix --value $domainValue
        
        # Add the SPF record value to the TXT DNS record
        az network dns record-set txt add-record --resource-group $resourceGroup --zone-name $dnsZoneName --record-set-name $subDomainPrefix --value "`"$spfValue`""
        
        # Create CNAME DNS records for DKIM verification
        az network dns record-set cname create --resource-group $resourceGroup --zone-name $dnsZoneName --name "$dkimName.$subDomainPrefix"
        
        # Add the DKIM record value to the CNAME DNS record
        az network dns record-set cname set-record --resource-group $resourceGroup --zone-name $dnsZoneName --record-set-name "$dkimName.$subDomainPrefix" --cname $dkimValue
        
        # Create a CNAME record for the second DKIM2 verification
        az network dns record-set cname create --resource-group $resourceGroup --zone-name $dnsZoneName --name "$dkim2Name.$subDomainPrefix"
        
        # Add the DKIM2 record value to the CNAME DNS record
        az network dns record-set cname set-record --resource-group $resourceGroup --zone-name $dnsZoneName --record-set-name "$dkim2Name.$subDomainPrefix" --cname $dkim2Value                
    } catch {
        # If an error occurs while adding DNS records, output an error message and exit the script
        Write-Host "Error while adding DNS records for domain $domainName"
        exit 1
    }

    # Return the domain details as an object for further use if needed
    return $domainDetails
}

Verification of domains

Initiates the domain verification process for the domains, including Domain, SPF, DKIM, and DKIM2 verifications.

# Verify domain function
function Verify-Domain {
    param (
        [string]$domainName
    )

    Write-Host "Initiating domain verification for: $domainName"

    # Define the types of verification that need to be performed
    $verificationTypes = @("Domain", "SPF", "DKIM", "DKIM2")

    # Loop over each verification type and initiate the verification process via Azure CLI
    foreach ($verificationType in $verificationTypes) {
        try {
            # Run the Azure CLI command to initiate the verification process for each verification type
            az communication email domain initiate-verification --domain-name $domainName --email-service-name $emailServiceName --resource-group $resourceGroup --verification-type $verificationType
        } catch {
            Write-Host "Error initiating verification for $domainName"
            exit 1
        }
    }

    # Polling for domain verification
    $attempts = 0 # Track the number of verification attempts
    $maxAttempts = 10 # Set the maximum number of attempts to check domain verification status

    # Loop for polling and checking verification status up to maxAttempts times
    while ($attempts -lt $maxAttempts) {
        try {
            # Run the Azure CLI command to fetch the domain details
            $domainDetailsJson = az communication email domain show --resource-group $resourceGroup --email-service-name $emailServiceName --name $domainName
        } catch {
            # If an error occurs while fetching the domain verification status, output an error message and exit
            Write-Host "Error fetching domain verification status for $domainName"
            exit 1
        }

        # If no domain details are returned, output a message and exit the script
        if (-not $domainDetailsJson) {
            Write-Host "Failed to fetch domain verification details for $domainName"
            exit 1
        }

        # Parse the domain details JSON response
        $domainDetails = $domainDetailsJson | ConvertFrom-Json

        $dkimStatus = $domainDetails.verificationStates.DKIM.status
        $dkim2Status = $domainDetails.verificationStates.DKIM2.status
        $domainStatus = $domainDetails.verificationStates.Domain.status
        $spfStatus = $domainDetails.verificationStates.SPF.status

        # Check if all verification statuses are "Verified"
        if ($dkimStatus -eq 'Verified' -and $dkim2Status -eq 'Verified' -and $domainStatus -eq 'Verified' -and $spfStatus -eq 'Verified') {
            Write-Host "Domain verified successfully."
            return $true
        }

        # If verification is not yet complete, wait before checking again
        $attempts++
        Start-Sleep -Seconds 10
    }

    # If the maximum number of attempts is reached without successful verification, print failure message
    Write-Host "Domain $domainName verification failed or timed out."
    return $false
}

Once the domains are verified and DNS records are configured, you can link the domains to the Azure Communication Service:

Note

The maximum limit for domain linking is 1000 per Azure Communication Service.

# Linking domains to the communication service
if ($linkedDomainIds.Count -gt 0) {
    Write-Host "Linking domains to communication service."
    try {
        # Run the Azure CLI command to link the verified domains to the Communication service
        az communication update --name $commServiceName --resource-group $resourceGroup --linked-domains $linkedDomainIds

        # Output a success message if the domains were successfully linked
        Write-Host "Domains linked successfully."
    } catch {
        # If an error occurs while linking the domains, output an error message and exit the script
        Write-Host "Error linking domains"
        exit 1 # Exit the script with error code 1
    }
} else {
    # If no domains were linked, output a message indicating no domains to link
    Write-Host "No domains were linked."
}

Complete PowerShell script with Azure CLI Commands for Automating End-to-End Resource Creation

# Variables
# Define the name of the Azure resource group where resources will be created
$resourceGroup = "ContosoResourceProvider1"

# Specify the region where the resources will be created. In this case, Europe.
$dataLocation = "europe"

# Define the name of the Azure Communication Service resource
$commServiceName = "ContosoAcsResource1"

# Define the name of the Email Communication Service resource
$emailServiceName = "ContosoEcsResource1"

# Define the DNS zone name where the domains will be managed (replace with actual DNS zone)
$dnsZoneName = "contoso.net"

# Define the list of domains to be created and managed (replace with your own list of domains)
$domains = @(
    "sales.contoso.net",
    "marketing.contoso.net",
    "support.contoso.net",
    "technical.contoso.net",
    "info.contoso.net"
)

# Log in to Azure
# Output message to indicate the authentication process is starting
Write-Host "Logging in to Azure..."
try {
    # Execute the Azure CLI login command
    az login
} catch {
    # If there is an error during authentication, display the error message
    Write-Host "Error during Azure login"

    # Exit the script with an error code (1) if authentication fails
    exit 1
}

# Create Communication resource
# Output message to indicate the creation of the Communication Service resource is starting
Write-Host "Creating Communication resource - $commServiceName"
try {
    # The az communication create cmdlet is used to create a new Communication Service resource
    az communication create --name $commServiceName --resource-group $resourceGroup --location global --data-location $dataLocation
} catch {
    # If there is an error during the creation of the Communication Service resource, display the error message
    Write-Host "Error while creating Communication resource"

    # Exit the script with an error code (1) if the creation of the Communication Service resource fails
    exit 1
}

# Create Email Communication resource
# Output message to indicate the creation of the Email Communication Service resource is starting
Write-Host "Creating Email Communication resource - $emailServiceName"
try {
    # The az communication email create cmdlet is used to create a new Email Communication Service resource
    az communication email create --name $emailServiceName --resource-group $resourceGroup --location global --data-location $dataLocation
} catch {
    # If there is an error during the creation of the Email Communication Service resource, display the error message
    Write-Host "Error while creating Email Communication resource"

    # Exit the script with an error code (1) if the creation of the Email Communication Service resource fails
    exit 1
}

# Function to add DNS records
function Add-RecordSetToDNS {
    param (
        [string]$domainName,
        [string]$subDomainPrefix
    )

    # Output a message indicating that DNS record sets are being added for the specified domain
    Write-Host "Adding DNS record sets for domain: $domainName"
    try {
        # Run the Azure CLI command to fetch domain details for the specified domain name
        $domainDetailsJson = az communication email domain show --resource-group $resourceGroup --email-service-name $emailServiceName --name $domainName
    } catch {
        # If an error occurs while fetching domain details, output an error message and exit the script
        Write-Host "Error fetching domain details for $domainName"
        exit 1
    }

    # If no domain details are returned, output a message and exit the script
    if (-not $domainDetailsJson) {
        Write-Host "Failed to fetch domain details for $domainName"
        exit 1
    }

    # Parse the JSON response to extract the necessary domain details
    $domainDetails = $domainDetailsJson | ConvertFrom-Json

    # Extract verification record values Domain, SPF and DKIM from the parsed JSON response
    # These values will be used to create DNS records
    $dkimName = $domainDetails.verificationRecords.DKIM.name
    $dkimValue = $domainDetails.verificationRecords.DKIM.value
    $dkim2Name = $domainDetails.verificationRecords.DKIM2.name
    $dkim2Value = $domainDetails.verificationRecords.DKIM2.value
    $spfValue = $domainDetails.verificationRecords.SPF.value
    $domainValue = $domainDetails.verificationRecords.Domain.value
    try {
        # Create the TXT DNS record for the domain's verification value
        az network dns record-set txt create --name $subDomainPrefix --zone-name $dnsZoneName --resource-group $resourceGroup
        
        # Add the domain verification record to the TXT DNS record
        az network dns record-set txt add-record --resource-group $resourceGroup --zone-name $dnsZoneName --record-set-name $subDomainPrefix --value $domainValue
        
        # Add the SPF record value to the TXT DNS record
        az network dns record-set txt add-record --resource-group $resourceGroup --zone-name $dnsZoneName --record-set-name $subDomainPrefix --value "`"$spfValue`""
        
        # Create CNAME DNS records for DKIM verification
        az network dns record-set cname create --resource-group $resourceGroup --zone-name $dnsZoneName --name "$dkimName.$subDomainPrefix"
        
        # Add the DKIM record value to the CNAME DNS record
        az network dns record-set cname set-record --resource-group $resourceGroup --zone-name $dnsZoneName --record-set-name "$dkimName.$subDomainPrefix" --cname $dkimValue
        
        # Create a CNAME record for the second DKIM2 verification
        az network dns record-set cname create --resource-group $resourceGroup --zone-name $dnsZoneName --name "$dkim2Name.$subDomainPrefix"
        
        # Add the DKIM2 record value to the CNAME DNS record
        az network dns record-set cname set-record --resource-group $resourceGroup --zone-name $dnsZoneName --record-set-name "$dkim2Name.$subDomainPrefix" --cname $dkim2Value                
    } catch {
        # If an error occurs while adding DNS records, output an error message and exit the script
        Write-Host "Error while adding DNS records for domain $domainName"
        exit 1
    }

    # Return the domain details as an object for further use if needed
    return $domainDetails
}

# Verify domain function
function Verify-Domain {
    param (
        [string]$domainName
    )

    Write-Host "Initiating domain verification for: $domainName"

    # Define the types of verification that need to be performed
    $verificationTypes = @("Domain", "SPF", "DKIM", "DKIM2")

    # Loop over each verification type and initiate the verification process via Azure CLI
    foreach ($verificationType in $verificationTypes) {
        try {
            # Run the Azure CLI command to initiate the verification process for each verification type
            az communication email domain initiate-verification --domain-name $domainName --email-service-name $emailServiceName --resource-group $resourceGroup --verification-type $verificationType
        } catch {
            Write-Host "Error initiating verification for $domainName"
            exit 1
        }
    }

    # Polling for domain verification
    $attempts = 0 # Track the number of verification attempts
    $maxAttempts = 10 # Set the maximum number of attempts to check domain verification status

    # Loop for polling and checking verification status up to maxAttempts times
    while ($attempts -lt $maxAttempts) {
        try {
            # Run the Azure CLI command to fetch the domain details
            $domainDetailsJson = az communication email domain show --resource-group $resourceGroup --email-service-name $emailServiceName --name $domainName
        } catch {
            # If an error occurs while fetching the domain verification status, output an error message and exit
            Write-Host "Error fetching domain verification status for $domainName"
            exit 1
        }

        # If no domain details are returned, output a message and exit the script
        if (-not $domainDetailsJson) {
            Write-Host "Failed to fetch domain verification details for $domainName"
            exit 1
        }

        # Parse the domain details JSON response
        $domainDetails = $domainDetailsJson | ConvertFrom-Json

        $dkimStatus = $domainDetails.verificationStates.DKIM.status
        $dkim2Status = $domainDetails.verificationStates.DKIM2.status
        $domainStatus = $domainDetails.verificationStates.Domain.status
        $spfStatus = $domainDetails.verificationStates.SPF.status

        # Check if all verification statuses are "Verified"
        if ($dkimStatus -eq 'Verified' -and $dkim2Status -eq 'Verified' -and $domainStatus -eq 'Verified' -and $spfStatus -eq 'Verified') {
            Write-Host "Domain verified successfully."
            return $true
        }

        # If verification is not yet complete, wait before checking again
        $attempts++
        Start-Sleep -Seconds 10
    }

    # If the maximum number of attempts is reached without successful verification, print failure message
    Write-Host "Domain $domainName verification failed or timed out."
    return $false
}

# Main loop to create and verify domains
# List to store the IDs of successfully verified domains
$linkedDomainIds = @()

# Loop through each domain in the domains list
foreach ($domainName in $domains) {
    # Extract the subdomain prefix from the fully qualified domain name (e.g., "sales" from "sales.contoso.net")
    $subDomainPrefix = $domainName.Split('.')[0]
    try {
        # Output the domain name that is being created
        Write-Host "Creating domain: $domainName"
        # Create the email domain in Email Communication Service.
        # The command includes the domain name, resource group, email service name, location, and domain management type (CustomerManaged)
        az communication email domain create --name $domainName --resource-group $resourceGroup --email-service-name $emailServiceName --location global --domain-management CustomerManaged
    } catch {
        # If domain creation fails, display an error message and continue with the next domain
        Write-Host "Warning: Failed to create domain $domainName. Error"
        continue
    }

    # Add DNS records
    $domainDetails = Add-RecordSetToDNS $domainName $subDomainPrefix

    # Verify the domain
    if (Verify-Domain $domainName) {
        $linkedDomainIds += $domainDetails.id
    }
}

# Linking domains to the communication service
if ($linkedDomainIds.Count -gt 0) {
    Write-Host "Linking domains to communication service."
    try {
        # Run the Azure CLI command to link the verified domains to the Communication service
        az communication update --name $commServiceName --resource-group $resourceGroup --linked-domains $linkedDomainIds

        # Output a success message if the domains were successfully linked
        Write-Host "Domains linked successfully."
    } catch {
        # If an error occurs while linking the domains, output an error message and exit the script
        Write-Host "Error linking domains"
        exit 1 # Exit the script with error code 1
    }
} else {
    # If no domains were linked, output a message indicating no domains to link
    Write-Host "No domains were linked."
}

Get started with Azure CLI Python to automate the creation of Azure Communication Services (ACS), Email Communication Services (ECS), manage custom domains, configure DNS records, verify domains, and domain linking to communication resource.

In this sample, we cover what the sample does and the prerequisites you need before running it locally on your machine.

This documentation provides a detailed guide on using Azure CLI Python to automate the creation of Azure Communication Services (ACS) and Email Communication Services (ECS). It also covers the process of managing custom domains, configuring DNS records (such as Domain, SPF, DKIM, DKIM2), verifying domains and domain linking to communication resource.

Prerequisites

Prerequisite check

  • In a command prompt, run the python --version command to check whether Python is installed or not.
python --version
  • In a command prompt, run the pip --version command to check whether pip is installed or not.
pip --version
  • In a command prompt, run the az --version command to check whether the Azure CLI is installed or not.
az --version
  • Before you can use the Azure CLI Commands to manage your resources, sign in to Azure CLI. You can sign in running the az login command from the terminal and providing your credentials.
az login

Initialize all the Variables

Before proceeding, define the variables needed for setting up the ACS, ECS, and domains, along with DNS configuration for the domains. Modify these variables based on your environment:

# Define the name of the Azure resource group where resources will be created
resourceGroup = "ContosoResourceProvider1"

# Specify the region where the resources will be created. In this case, Europe.
dataLocation = "europe"

# Define the name of the Azure Communication Service resource
commServiceName = "ContosoAcsResource1"

# Define the name of the Email Communication Service resource
emailServiceName = "ContosoEcsResource1"

# Define the DNS zone name where the domains will be managed (replace with actual DNS zone)
dnsZoneName = "contoso.net"

# Define the list of domains to be created and managed (replace with your own list of domains)
domains = [
    "sales.contoso.net",
    "marketing.contoso.net",
    "support.contoso.net",
    "technical.contoso.net",
    "info.contoso.net"
]

Login to Azure Account

To login to your Azure account through python code, follow the below code:

def loginToAzure():
    try:
        # Print a message indicating that the Azure login process is starting
        print("Logging in to Azure...")

        # Call the runCommand function to execute the Azure CLI login command
        runCommand("az login")

    except Exception as e:
        # If any exception occurs during the Azure login process, handle it using the errorHandler
        errorHandler(f"Azure login failed: {e}")

Create an Azure Communication Service (ACS) resource

This part of the script creates an Azure Communication Service resource:

def createCommunicationResource():
    try:
        # Print a message indicating the creation of the Communication Service resource is starting
        print(f"Creating Communication resource - {commServiceName}")

        # Run the Azure CLI command to create the Communication resource.
        # The 'az communication create' command is used to create a Communication Service in Azure.
        # It requires the name of the service, the resource group, location, and data location.
        runCommand(f"az communication create --name {commServiceName} --resource-group {resourceGroup} --location global --data-location {dataLocation}")
    except Exception as e:
        # If an error occurs during the creation process, the errorHandler function is called
        # The error message is formatted and passed to the errorHandler for proper handling
        errorHandler(f"Failed to create Communication resource: {e}")

Create an Email Communication Service (ECS) resource

This part of the script creates an Email Communication Service resource:

def createEmailCommunicationResource():
    try:
        # Print a message indicating the creation of the Email Communication Service resource is starting
        print(f"Creating Email Communication resource - {emailServiceName}")

        # Run the Azure CLI command to create the Email Communication resource.
        # The 'az communication email create' command is used to create an Email Communication Service.
        # It requires the service name, the resource group, location, and data location.
        runCommand(f"az communication email create --name {emailServiceName} --resource-group {resourceGroup} --location global --data-location {dataLocation}")
    except Exception as e:
        # If an error occurs during the creation process, the errorHandler function is called
        # The error message is formatted and passed to the errorHandler for proper handling
        errorHandler(f"Failed to create Email Communication resource: {e}")

Create domains

Automate the creation of email domains, by following command:

Note

The maximum limit for domain creation is 800 per Email Communication Service.

Note

In our code, we work with five predefined domains, for which DNS records are added and configured.

# Create the email domain in Email Communication Service.
# The command includes the domain name, resource group, email service name, location, and domain management type (CustomerManaged)
runCommand(f"az communication email domain create --name {domainName} --resource-group {resourceGroup} --email-service-name {emailServiceName} --location global --domain-management CustomerManaged")

Add records set to DNS

Add records to DNS record setup (including Domain, SPF, DKIM, DKIM2) for each domain.

# Function to add DNS records
# Add records set to DNS
def addRecordSetToDns(domainName, subDomainPrefix):
    try:
        print(f"Adding DNS record sets for domain: {domainName}")

        # Run the Azure CLI command to fetch domain details for the specified domain name
        domainDetailsJson = runCommand(f"az communication email domain show --resource-group {resourceGroup} --email-service-name {emailServiceName} --name {domainName}")

        # If no details are returned, handle the error by exiting the process
        if not domainDetailsJson:
            errorHandler(f"Failed to fetch domain details for {domainName}")

        # Parse the JSON response to extract the necessary domain details
        domainDetails = json.loads(domainDetailsJson)
        print(f"Domain details are: {domainDetails}")

        # Extract verification record values Domain, SPF and DKIM from the parsed JSON response
        # These values will be used to create DNS records
        domainValue = domainDetails['verificationRecords']['Domain']['value']
        spfValue = domainDetails['verificationRecords']['SPF']['value']
        dkimName = domainDetails['verificationRecords']['DKIM']['name']
        dkimValue = domainDetails['verificationRecords']['DKIM']['value']
        dkim2Name = domainDetails['verificationRecords']['DKIM2']['name']
        dkim2Value = domainDetails['verificationRecords']['DKIM2']['value']
        
        # Create the TXT DNS record for the domain's verification value
        runCommand(f"az network dns record-set txt create --name {subDomainPrefix} --zone-name {dnsZoneName} --resource-group {resourceGroup}")
        
        # Add the domain verification record to the TXT DNS record
        runCommand(f"az network dns record-set txt add-record --resource-group {resourceGroup} --zone-name {dnsZoneName} --record-set-name {subDomainPrefix} --value {domainValue}")
        
        # Add the SPF record value to the TXT DNS record
        runCommand(f"az network dns record-set txt add-record --resource-group {resourceGroup} --zone-name {dnsZoneName} --record-set-name {subDomainPrefix} --value \"{spfValue}\"")
        
        # Create CNAME DNS records for DKIM verification
        runCommand(f"az network dns record-set cname create --resource-group {resourceGroup} --zone-name {dnsZoneName} --name {dkimName}.{subDomainPrefix}")
        
        # Add the DKIM record value to the CNAME DNS record
        runCommand(f"az network dns record-set cname set-record --resource-group {resourceGroup} --zone-name {dnsZoneName} --record-set-name {dkimName}.{subDomainPrefix} --cname {dkimValue}")
        
        # Create a CNAME record for the second DKIM2 verification
        runCommand(f"az network dns record-set cname create --resource-group {resourceGroup} --zone-name {dnsZoneName} --name {dkim2Name}.{subDomainPrefix}")
        
        # Add the DKIM2 record value to the CNAME DNS record
        runCommand(f"az network dns record-set cname set-record --resource-group {resourceGroup} --zone-name {dnsZoneName} --record-set-name {dkim2Name}.{subDomainPrefix} --cname {dkim2Value}")

        # Return the domain details
        return domainDetails

    # Handle JSON decoding errors (in case the response is not valid JSON)
    except json.JSONDecodeError as e:
        errorHandler(f"Failed to parse domain details JSON: {e}")

    # Catch any other exceptions that might occur during the DNS record creation process
    except Exception as e:
        errorHandler(f"Error while adding DNS records for domain {domainName}: {e}")

Verification of domains

Initiates the domain verification process for the domains, including Domain, SPF, DKIM, and DKIM2 verifications.

# Verify domain function
def verifyDomain(domainName):
    try:
        print(f"Initiating domain verification for: {domainName}")

        # Define the types of verification that need to be performed
        verificationTypes = ["Domain", "SPF", "DKIM", "DKIM2"]

        # Loop over each verification type and initiate the verification process via Azure CLI
        for verificationType in verificationTypes:
            # Run the Azure CLI command to initiate the verification process for each verification type
            runCommand(f"az communication email domain initiate-verification --domain-name {domainName} --email-service-name {emailServiceName} --resource-group {resourceGroup} --verification-type {verificationType}")

        # Polling for domain verification
        attempts = 0 # Track the number of verification attempts
        maxAttempts = 10 # Set the maximum number of attempts to check domain verification status

        # Loop for polling and checking verification status up to maxAttempts times
        while attempts < maxAttempts:
            # Run the Azure CLI command to fetch the domain details
            domainDetailsJson = runCommand(f"az communication email domain show --resource-group {resourceGroup} --email-service-name {emailServiceName} --name {domainName}")
            
            # If no details are returned, call the errorHandler to stop the process
            if not domainDetailsJson:
                errorHandler(f"Failed to get domain verification details for {domainName}")

            # Parse the domain details JSON response
            domainDetails = json.loads(domainDetailsJson)

            dkimStatus = domainDetails['verificationStates']['DKIM']['status']
            dkim2Status = domainDetails['verificationStates']['DKIM2']['status']
            domainStatus = domainDetails['verificationStates']['Domain']['status']
            spfStatus = domainDetails['verificationStates']['SPF']['status']
            
            # Check if all verification statuses are "Verified"
            if dkimStatus == 'Verified' and dkim2Status == 'Verified' and domainStatus == 'Verified' and spfStatus == 'Verified':
                print(f"Domain verified successfully.")
                return True

            # If verification is not yet complete, wait before checking again
            attempts += 1
            time.sleep(10)

        # If the maximum number of attempts is reached without successful verification, print failure message
        print(f"Domain {domainName} verification failed or timed out.")
        return False

    # Handle JSON decoding errors that might occur when trying to parse the domain verification response
    except json.JSONDecodeError as e:
        errorHandler(f"Failed to parse domain verification JSON: {e}")

    # Catch any other general exceptions that might occur during the domain verification process
    except Exception as e:
        errorHandler(f"Error while verifying domain {domainName}: {e}")

Once the domains are verified and DNS records are configured, you can link the domains to the Azure Communication Service:

Note

The maximum limit for domain linking is 1000 per Azure Communication Service.

# Link the verified domains to the Communication service
# The command includes the communication service name, resource group, and the list of linked domain IDs
runCommand(f"az communication update --name {commServiceName} --resource-group {resourceGroup} --linked-domains {' '.join(linkedDomainIds)}")

Complete Python Script with Azure CLI Commands for Automating End-to-End Resource Creation

import subprocess
import json
import time

# Variables
# Define the name of the Azure resource group where resources will be created
resourceGroup = "ContosoResourceProvider1"

# Specify the region where the resources will be created. In this case, Europe.
dataLocation = "europe"

# Define the name of the Azure Communication Service resource
commServiceName = "ContosoAcsResource1"

# Define the name of the Email Communication Service resource
emailServiceName = "ContosoEcsResource1"

# Define the DNS zone name where the domains will be managed (replace with actual DNS zone)
dnsZoneName = "contoso.net"

# Define the list of domains to be created and managed (replace with your own list of domains)
domains = [
    "sales.contoso.net",
    "marketing.contoso.net",
    "support.contoso.net",
    "technical.contoso.net",
    "info.contoso.net"
]

# Function to run shell commands and get output
def runCommand(command):
    try:
        # Execute the shell command using subprocess.run
        # 'check=True' will raise an exception if the command exits with a non-zero status
        # 'capture_output=True' captures the command's output and stores it in 'result.stdout'
        # 'text=True' ensures the output is returned as a string (rather than bytes)
        # 'shell=True' allows us to execute the command as if it were in the shell (e.g., using shell features like pipes)
        result = subprocess.run(command, check=True, capture_output=True, text=True, shell=True)

        # Return the standard output (stdout) of the command if successful
        return result.stdout

    except subprocess.CalledProcessError as e:
        # This exception is raised if the command exits with a non-zero status (i.e., an error occurs)
        print(f"Error running command '{command}': {e}") # Print the error message
        print(f"Error details: {e.stderr}") # Print the standard error (stderr) details for more information
        return None # Return None to indicate that the command failed

    except Exception as e:
        # This will catch any other exceptions that may occur (e.g., issues unrelated to the subprocess itself)
        print(f"Unexpected error while executing command '{command}': {e}") # Print any unexpected error messages
        return None # Return None to indicate that an error occurred

# Function to handle errors (catch functionality)
def errorHandler(errorMessage="Error occurred. Exiting."):
    print(errorMessage)
    exit(1)

# Log in to Azure
def loginToAzure():
    try:
        # Print a message indicating that the Azure login process is starting
        print("Logging in to Azure...")

        # Call the runCommand function to execute the Azure CLI login command
        runCommand("az login")

    except Exception as e:
        # If any exception occurs during the Azure login process, handle it using the errorHandler
        errorHandler(f"Azure login failed: {e}")

# Create Communication resource
def createCommunicationResource():
    try:
        # Print a message indicating the creation of the Communication Service resource is starting
        print(f"Creating Communication resource - {commServiceName}")

        # Run the Azure CLI command to create the Communication resource.
        # The 'az communication create' command is used to create a Communication Service in Azure.
        # It requires the name of the service, the resource group, location, and data location.
        runCommand(f"az communication create --name {commServiceName} --resource-group {resourceGroup} --location global --data-location {dataLocation}")
    except Exception as e:
        # If an error occurs during the creation process, the errorHandler function is called
        # The error message is formatted and passed to the errorHandler for proper handling
        errorHandler(f"Failed to create Communication resource: {e}")

# Create Email Communication resource
def createEmailCommunicationResource():
    try:
        # Print a message indicating the creation of the Email Communication Service resource is starting
        print(f"Creating Email Communication resource - {emailServiceName}")

        # Run the Azure CLI command to create the Email Communication resource.
        # The 'az communication email create' command is used to create an Email Communication Service.
        # It requires the service name, the resource group, location, and data location.
        runCommand(f"az communication email create --name {emailServiceName} --resource-group {resourceGroup} --location global --data-location {dataLocation}")
    except Exception as e:
        # If an error occurs during the creation process, the errorHandler function is called
        # The error message is formatted and passed to the errorHandler for proper handling
        errorHandler(f"Failed to create Email Communication resource: {e}")

# Function to add DNS records
# Add records set to DNS
def addRecordSetToDns(domainName, subDomainPrefix):
    try:
        print(f"Adding DNS record sets for domain: {domainName}")

        # Run the Azure CLI command to fetch domain details for the specified domain name
        domainDetailsJson = runCommand(f"az communication email domain show --resource-group {resourceGroup} --email-service-name {emailServiceName} --name {domainName}")

        # If no details are returned, handle the error by exiting the process
        if not domainDetailsJson:
            errorHandler(f"Failed to fetch domain details for {domainName}")

        # Parse the JSON response to extract the necessary domain details
        domainDetails = json.loads(domainDetailsJson)
        print(f"Domain details are: {domainDetails}")

        # Extract verification record values Domain, SPF and DKIM from the parsed JSON response
        # These values will be used to create DNS records
        domainValue = domainDetails['verificationRecords']['Domain']['value']
        spfValue = domainDetails['verificationRecords']['SPF']['value']
        dkimName = domainDetails['verificationRecords']['DKIM']['name']
        dkimValue = domainDetails['verificationRecords']['DKIM']['value']
        dkim2Name = domainDetails['verificationRecords']['DKIM2']['name']
        dkim2Value = domainDetails['verificationRecords']['DKIM2']['value']
        
        # Create the TXT DNS record for the domain's verification value
        runCommand(f"az network dns record-set txt create --name {subDomainPrefix} --zone-name {dnsZoneName} --resource-group {resourceGroup}")
        
        # Add the domain verification record to the TXT DNS record
        runCommand(f"az network dns record-set txt add-record --resource-group {resourceGroup} --zone-name {dnsZoneName} --record-set-name {subDomainPrefix} --value {domainValue}")
        
        # Add the SPF record value to the TXT DNS record
        runCommand(f"az network dns record-set txt add-record --resource-group {resourceGroup} --zone-name {dnsZoneName} --record-set-name {subDomainPrefix} --value \"{spfValue}\"")
        
        # Create CNAME DNS records for DKIM verification
        runCommand(f"az network dns record-set cname create --resource-group {resourceGroup} --zone-name {dnsZoneName} --name {dkimName}.{subDomainPrefix}")
        
        # Add the DKIM record value to the CNAME DNS record
        runCommand(f"az network dns record-set cname set-record --resource-group {resourceGroup} --zone-name {dnsZoneName} --record-set-name {dkimName}.{subDomainPrefix} --cname {dkimValue}")
        
        # Create a CNAME record for the second DKIM2 verification
        runCommand(f"az network dns record-set cname create --resource-group {resourceGroup} --zone-name {dnsZoneName} --name {dkim2Name}.{subDomainPrefix}")
        
        # Add the DKIM2 record value to the CNAME DNS record
        runCommand(f"az network dns record-set cname set-record --resource-group {resourceGroup} --zone-name {dnsZoneName} --record-set-name {dkim2Name}.{subDomainPrefix} --cname {dkim2Value}")

        # Return the domain details
        return domainDetails

    # Handle JSON decoding errors (in case the response is not valid JSON)
    except json.JSONDecodeError as e:
        errorHandler(f"Failed to parse domain details JSON: {e}")

    # Catch any other exceptions that might occur during the DNS record creation process
    except Exception as e:
        errorHandler(f"Error while adding DNS records for domain {domainName}: {e}")

# Verify domain function
def verifyDomain(domainName):
    try:
        print(f"Initiating domain verification for: {domainName}")

        # Define the types of verification that need to be performed
        verificationTypes = ["Domain", "SPF", "DKIM", "DKIM2"]

        # Loop over each verification type and initiate the verification process via Azure CLI
        for verificationType in verificationTypes:
            # Run the Azure CLI command to initiate the verification process for each verification type
            runCommand(f"az communication email domain initiate-verification --domain-name {domainName} --email-service-name {emailServiceName} --resource-group {resourceGroup} --verification-type {verificationType}")

        # Polling for domain verification
        attempts = 0 # Track the number of verification attempts
        maxAttempts = 10 # Set the maximum number of attempts to check domain verification status

        # Loop for polling and checking verification status up to maxAttempts times
        while attempts < maxAttempts:
            # Run the Azure CLI command to fetch the domain details
            domainDetailsJson = runCommand(f"az communication email domain show --resource-group {resourceGroup} --email-service-name {emailServiceName} --name {domainName}")
            
            # If no details are returned, call the errorHandler to stop the process
            if not domainDetailsJson:
                errorHandler(f"Failed to get domain verification details for {domainName}")

            # Parse the domain details JSON response
            domainDetails = json.loads(domainDetailsJson)

            dkimStatus = domainDetails['verificationStates']['DKIM']['status']
            dkim2Status = domainDetails['verificationStates']['DKIM2']['status']
            domainStatus = domainDetails['verificationStates']['Domain']['status']
            spfStatus = domainDetails['verificationStates']['SPF']['status']
            
            # Check if all verification statuses are "Verified"
            if dkimStatus == 'Verified' and dkim2Status == 'Verified' and domainStatus == 'Verified' and spfStatus == 'Verified':
                print(f"Domain verified successfully.")
                return True

            # If verification is not yet complete, wait before checking again
            attempts += 1
            time.sleep(10)

        # If the maximum number of attempts is reached without successful verification, print failure message
        print(f"Domain {domainName} verification failed or timed out.")
        return False

    # Handle JSON decoding errors that might occur when trying to parse the domain verification response
    except json.JSONDecodeError as e:
        errorHandler(f"Failed to parse domain verification JSON: {e}")

    # Catch any other general exceptions that might occur during the domain verification process
    except Exception as e:
        errorHandler(f"Error while verifying domain {domainName}: {e}")

# Main - to create and verify domains
# List to store the IDs of successfully verified domains
linkedDomainIds = []

def main():
    try:
        # Step 1: Call the loginToAzure() function to log in to Azure using the Azure CLI
        loginToAzure()

        # Step 2: Call the createCommunicationResource() function to create Azure Communication Service resource
        createCommunicationResource()

        # Step 3: Call the createEmailCommunicationResource() function to create Azure Communication Service resource.
        createEmailCommunicationResource()

        # Step 4: Loop through each domain in the 'domains' list
        for domainName in domains:

            # Extract the subdomain prefix from the fully qualified domain name (e.g., "sales" from "sales.contoso.net")
            subDomainPrefix = domainName.split('.')[0]
            try:
                print(f"Creating domain: {domainName}")
                # Step 5: Create the email domain in Email Communication Service.
                # The command includes the domain name, resource group, email service name, location, and domain management type (CustomerManaged)
                runCommand(f"az communication email domain create --name {domainName} --resource-group {resourceGroup} --email-service-name {emailServiceName} --location global --domain-management CustomerManaged")
            except Exception as e:
                # If domain creation fails, print a warning and continue with the next domain
                print(f"Warning: Failed to create domain {domainName}. Error: {e}")
                continue

            # Step 6: Add DNS records
            domainDetails = addRecordSetToDns(domainName, subDomainPrefix)

            # Step 7: Verify the domain
            if verifyDomain(domainName):
                # If domain verification is successful, add the domain's ID to the linked domains list
                linkedDomainIds.append(domainDetails['id'])

        # Linking domains to the communication service
        if linkedDomainIds:
            print("Linking domains to communication service.")

            # Run the Azure CLI command to link the verified domains to the Communication service
            runCommand(f"az communication update --name {commServiceName} --resource-group {resourceGroup} --linked-domains {' '.join(linkedDomainIds)}")
            print("Domains linked successfully.")
        else:
            print("No domains were linked.")

    except Exception as e:
        errorHandler(f"An error occurred in the main process: {e}")

# Ensure the main function is called when the script is run directly
if __name__ == "__main__":
    main()

Clean up resources

If you want to clean up and remove a Communication Services subscription, you can delete the resource or resource group. To delete your communication resource, run the following command.

az communication delete --name "ContosoAcsResource1" --resource-group "ContosoResourceProvider1"

If you want to clean up and remove an Email Communication Services, You can delete your Email Communication resource by running the following command:

PS C:\> Remove-AzEmailService -Name ContosoEcsResource1 -ResourceGroupName ContosoResourceProvider1

If you want to clean up and remove a Domain resource, You can delete your Domain resource by running the following command:

PS C:\> Remove-AzEmailServiceDomain -Name contoso.com -EmailServiceName ContosoEcsResource1 -ResourceGroupName ContosoResourceProvider1

Deleting the resource group also deletes any other resources associated with it.

If you have any phone numbers assigned to your resource upon resource deletion, the phone numbers are automatically released from your resource at the same time.

Note

Resource deletion is permanent and no data, including Event Grid filters, phone numbers, or other data tied to your resource, can be recovered if you delete the resource.