This guide will help you to deploy MTA-STS for your domain(s) using a Azure Function App. If you want to deploy MTA-STS using a Azure Static Web App, check out the original deployment guide.
NOTE Please remember, that you can only add 5 custom domains per Azure Static Web App, while you can add 500 custom domains per Azure Function App. So if you want to configure MTA-STS for more than 5 domains, it is highly recommended to use a Azure Function App.
- Prerequisites
- Terminology
- Deployment
- CONGRATULATIONS! You have successfully configured MTA-STS for your domains
- active Azure Subscription, to create the required Azure resources
- Permissions to create resources (Resource Group, Function App, Storage Account)
NOTE Please make sure you have the required permissions to create the Azure resources and that there are no policies in place, which prevent you from using PowerShell to deploy Azure resources. If you are not sure, please contact your Azure Administrator.
- PowerShell modules
NOTE By installing the PS.MTA-STS module you will also install the latest version of the required modules. If you already have some or all of these modules installed make sure you are using the latest version of the modules. You can check the installed versions using
Get-Module -Name Az.Accounts, Az.Functions, Az.Resources, Az.Storage, Az.Websites, ExchangeOnlineManagement -ListAvailable
and update them usingUpdate-Module -Name Az.Accounts, Az.Functions, Az.Resources, Az.Storage, Az.Websites, ExchangeOnlineManagement
.
Use the following command to install the PS.MTA-STS module from the PowerShell Gallery:
Install-Module -Name "PS.MTA-STS"
You can also update the module using Update-Module -Name "PS.MTA-STS"
to get the latest version.
- MTA-STS: Mail Transfer Agent Strict Transport Security (MTA-STS) is a mechanism enabling mail service providers to declare their ability to receive Transport Layer Security (TLS) secure mail and to specify whether sending SMTP servers should refuse to deliver to MX hosts that do not offer TLS with a trusted server certificate. MTA-STS is defined in RFC 8461.
- Azure Subscription: An Azure subscription is a logical container used to provision resources in Microsoft Azure. Learn more
- Resource Group: A resource group is a container that holds related resources for an Azure solution. Learn more
- Azure Location: An Azure location is an area within a region containing one or more datacenters. Learn more
- Azure Function App: Azure Functions is a serverless compute service that enables you to run event-triggered code without having to explicitly provision or manage infrastructure. Learn more
- Azure Managed Certificate: Azure Managed Certificates are free SSL certificates that are managed by Azure. They are automatically renewed and can be used to secure custom domains. Learn more
- CNAME record: A Canonical Name (CNAME) record is a type of DNS record that maps an alias name to a true or canonical domain name. Learn more
- TXT record: A TXT record is a type of DNS record that provides text information to sources outside your domain. Learn more
- MX record: A Mail Exchange (MX) record is a type of DNS record that specifies the mail server responsible for receiving and sending email on behalf of a domain. Learn more
- TLS/SSL: Transport Layer Security (TLS) and its predecessor, Secure Sockets Layer (SSL), are cryptographic protocols designed to provide communications security over a computer network. Learn more
- SNI SSL: Server Name Indication (SNI) is an extension to the TLS computer networking protocol by which a client indicates which hostname it is attempting to connect to at the start of the handshaking process. Learn more
- MTA-STS policy: The MTA-STS policy is a text file that is published on a web server and contains the MTA-STS policy for a domain. Learn more
- Custom domain: A custom domain is a domain that you have purchased and that you want to use for your website, email or in our case MTA-STS configuration. Learn more
Before we start deploying Azure resources, we must prepare a list of domains we want to deploy MTA-STS for. We will use this list later to create the required DNS records and to configure the Azure resources for each domain.
To create the list, we will use the prepared PowerShell function Export-PSMTASTSDomainsFromExo from the PS.MTA-STS module. This function will connect to Exchange Online and read all accepted domains. Then, it will check the MX record for each found domain to validate if it points to Exchange Online. Afterwards, you will be asked to select the domains you want to deploy MTA-STS for from a graphical interface. The selected domains will be exported to a CSV file.
To run the function, open a PowerShell console, install and import the module:
Install-Module -Name PS.MTA-STS
Then, run the function (edit the path to the CSV file as needed):
Export-PSMTASTSDomainsFromExo -CsvPath "C:\temp\acceptedDomains.csv"
Alternatively, check out the comment-based help of the function using Get-Help -Name Export-PSMTASTSDomainsFromExo -Full
for more information.
If you want to create the Azure resources automatically, continue with step 2.1. If you want to create the Azure resources manually, continue with step 2.2.
In both cases, remember the valid characters for the Azure resources:
NOTE: The storage account will be created and named automatically by default, if you create the Azure resources manually as described in step 2.2
- Resource Group Name
- Length: 1-90
- Valid characters: lower- and uppercase letters, numbers, periods, hyphens and underscores (a-z, A-Z, 0-9, ., -, _)
- Function App Name
- Length: 2-60
- Valid characters: lower- and uppercase letters, numbers and hyphens (a-z, A-Z, 0-9, and -)
- must be unique globally!
- Storage Account Name
- Length: 3-24
- Valid characters: lowercase letters and numbers (a-z and 0-9)
- must be unique globally!
Now that you prepared the list of domains you want to deploy MTA-STS for, we can start to create the required Azure resources.
To create all resources automatically use the function "New-PSMTASTSFunctionAppDeployment" and provide your desired Azure Location, Resource Group Name, Function App Name and Storage Account Name. If any of these resources already exist, the function will use the existing resources. If you are not connected to Azure already, you will be prompted to authenticate in your last used browser. After the authentication, the function will create and configure the resources.
The automatic deployment can take a few minutes. Please be patient. Add the
-Verbose
parameter to the function below to see the progress.
# Create resource group and Azure Function App
New-PSMTASTSFunctionAppDeployment -Location "westeurope" -ResourceGroupName "rg-PSMTASTS" -FunctionAppName "func-PSMTASTS" -StorageAccountName "storagepsmtasts" -PolicyMode "Enforce"
NOTE This will publish a MTA-STS policy in enforce mode for the domain. If you want to test the policy without enforcing it, replace 'enforce' with 'testing' or use the "Update-PSMTASTSFunctionAppDeployment" function to update the policy and other function app files to the current version.
This will look like this:
(You can safely ignore the warning about the "Upcoming breaking changes in the cmdlet 'New-AzStorageAccount'")
If your deployment was successful, you can continue with step 3.
If you do not want to create the function app automatically, follow these steps:
First of all, we must create a resource group which will combine all necessary resources. To do so, go to Azure Portal, search for "Resource groups", switch to the service page and select "Create".
Select your subscription, provide a name for your resource group, select your desired region and select "Review + create".
After the validation passed, select "Create".
Now, we can create the Azure Function App. To do so, go to Azure Portal, search for "Function App", switch to the service page and select "Create".
On the new page, enter the following information (as described at step 4 of Enhancing mail flow with MTA-STS):
- Basics
- Subscription: Select your subscription
- Resource group: Select the resource group you created in the previous step
- Function App Name: func-MTA-STS (or any other name you like and complies with the naming rules)
- Runtime stack: PowerShell Core
- Version: 7.4
- Region: Select the same region as you selected for the resource group
- Operating System: Windows
- Hosting options and plans: Consumption (Serverless)
- Storage
- Keep the default settings. This will name and create the needed storage account automatically.
- Networking
- Keep the default settings
- Monitoring
- Keep the default settings
- Deployment
- Keep the default settings
Select "Review + create" and then "Create".
Next, we must replace some file contents of our newly created Azure Function App. To do so, go to Azure Portal, search for "Function App", switch to the service page and select the function app you created in the previous step.
Select "App files" and replace the contents of "host.json", "profile.ps1" and "requirements.psd1" with the following contents:
- host.json
{
"version": "2.0",
"extensions": {
"http": {
"routePrefix": "",
"customHeaders": {
"Permissions-Policy": "geolocation=()",
"X-Frame-Options": "SAMEORIGIN",
"Content-Security-Policy": "default-src 'self'",
"Strict-Transport-Security": "max-age=31536000; includeSubDomains",
"X-Content-Type-Options": "nosniff",
"Referrer-Policy": "no-referrer"
}
}
},
"managedDependency": {
"Enabled": false
},
"extensionBundle": {
"id": "Microsoft.Azure.Functions.ExtensionBundle",
"version": "[3.*, 4.0.0)"
}
}
- profile.ps1
# Azure Functions profile.ps1
#
# This profile.ps1 will get executed every "cold start" of your Function App.
# "cold start" occurs when:
#
# * A Function App starts up for the very first time
# * A Function App starts up after being de-allocated due to inactivity
#
# You can define helper functions, run commands, or specify environment variables
# NOTE: any variables defined that are not environment variables will get reset after the first execution
# Authenticate with Azure PowerShell using MSI.
# Remove this if you are not planning on using MSI or Azure PowerShell.
#if ($env:MSI_SECRET -and (Get-Module -ListAvailable Az.Accounts)) {
# Write-Host "Connecting to Azure"
# Connect-AzAccount -Identity
#}
# Uncomment the next line to enable legacy AzureRm alias in Azure PowerShell.
# Enable-AzureRmAlias
# You can also define functions or aliases that can be referenced in any of your PowerShell functions.
- requirements.psd1
# This file enables modules to be automatically managed by the Functions service.
# See https://aka.ms/functionsmanageddependency for additional information.
#
@{
}
Now, we can create the function, which publishes the MTA-STS policy.
On the Function App service page, select "Functions" and then "Create". As "Development environment" select 'Develop in portal' and as "Template" select 'HTTP trigger'. Provide a name for your function, e.g. 'Publish-MTASTSPolicy' and as authorization select "Anonymous". "Anonymous" is required, as this script will be called by external servers which won't authenticate.
Select "Create" to create the function.
Now, we can add the code to the function. To do so, select "Code + Test" and copy and paste the following code to the run.ps1 and the function.json:
- run.ps1
The run.ps1 file is responsible for returning the MTA-STS policy. The following code will return a MTA-STS policy in enforce mode for the domain. If you want to test the policy without enforcing it, replace 'enforce' with 'testing' in the $mtaStsPolicy variable.
NOTE If you want to include additional MX records, add additional mx lines like shown below. This can be useful if you have multiple MX records for your domains, because you want to take advantage of SMTP DANE with DNSSEC
version: STSv1
mode: enforce
mx: *.mail.protection.outlook.com
mx: *.abcd-v1.mx.microsoft
mx: smtp.contoso.com
max_age: 604800
param ($Request, $TriggerMetadata)
# Write to the Azure Functions log stream.
Write-Host "Trigger: MTA-STS policy has been requested."
# Prepare the response body
## Replace 'enforce' with 'testing' to test the policy without enforcing it
## Or use the "Update-PSMTASTSFunctionAppDeployment" function to update the policy and other function app files to the current version
$mtaStsPolicy = @"
version: STSv1
mode: enforce
mx: *.mail.protection.outlook.com
max_age: 604800
"@
<#
## If you want to include additional MX records, add additional mx lines like this:
## This can be useful if you have multiple MX records for your domains, because you want to take advantage of SMTP DANE with DNSSEC, but you don't want to create separate MTA-STS policies for each MX record
$mtaStsPolicy = @"
version: STSv1
mode: enforce
mx: *.mail.protection.outlook.com
mx: *.abcd-v1.mx.microsoft
mx: smtp.contoso.com
max_age: 604800
"@
#>
# Return the response
try {
Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{
StatusCode = [System.Net.HttpStatusCode]::OK
Headers = @{
"Content-type" = "text/plain"
}
Body = $mtaStsPolicy
})
}
catch {
# Return error, if something went wrong
Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{
StatusCode = [System.Net.HttpStatusCode]::InternalServerError
Headers = @{
"Content-type" = "text/plain"
}
Body = $_.Exception.Message
})
}
- function.json
The function.json file is responsible for the configuration of the function. The following code will configure the function to be triggered by a HTTP request and to return a HTTP response.
{
"bindings": [
{
"route": ".well-known/mta-sts.txt",
"authLevel": "anonymous",
"methods": [
"get"
],
"type": "httpTrigger",
"direction": "in",
"name": "Request"
},
{
"type": "http",
"direction": "out",
"name": "Response"
}
]
}
Additionally, we will publish one more function, which returns a simple text, if the root of the function app is called. To do so, repeat the steps above to create a new function and provide the following code:
- run.ps1
# Input bindings are passed in via param block.
param($Request, $TriggerMetadata)
# Write to the Azure Functions log stream.
Write-Host "Trigger: Static website index has been requested."
# Create HTML
$staticWebsiteIndex = @"
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>MTA-STS</title>
</head>
<body>
<hl>MTA-STS Static Website Index</hl>
<p>Looking for the <a href="./.well-known/mta-sts.txt">mta-sts.txt</a>?</p>
</body>
</html>
"@
# Return the response
try {
Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{
StatusCode = [System.Net.HttpStatusCode]::OK
Headers = @{
"content-type" = "text/html"
}
Body = $staticWebsiteIndex
})
}
catch {
# Return error, if something went wrong
Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{
StatusCode = [System.Net.HttpStatusCode]::InternalServerError
Headers = @{
"Content-type" = "text/plain"
}
Body = $_.Exception.Message
})
}
- function.json
{
"bindings": [
{
"route": "/",
"authLevel": "ANONYMOUS",
"methods": [
"get"
],
"type": "httpTrigger",
"direction": "in",
"name": "Request"
},
{
"type": "http",
"direction": "out",
"name": "Response"
}
]
}
That's it. Your Function App is now prepared to publish the MTA-STS policy. Custom Domains will be added in step 4.
Before adding your custom domains, you have to prepare CNAME records for the validation process. To do so, go to your public DNS provider and create the two following records per domain:
Name | Type | Value |
---|---|---|
mta-sts.<your-custom-domain> | CNAME | <your-functionapp-name>.azurewebsites.net. |
asuid.mta-sts.<your-custom-domain> | TXT | <your-functionapp-custom-domain-verification-ID> |
Your can find the <your-functionapp-custom-domain-verification-ID> in the Azure Portal. Go to your Function App service page, select "Custom domains". You will find the ID in the "Custom domain verification ID" field on top of the page.
If you want to add custom domains to your Azure Function App automatically, continue with step 4.1. If you want to add custom domains to your Azure Function App manually, continue with step 4.2.
Now that you have prepared the CNAME records, you can add your custom domains to the Azure Function App. To do so, you can use the Add-PSMTASTSCustomDomain function. The function will validate the CNAME records and add the custom domain to the Azure Function App.
This function will do the following by default for each domain in the CSV file:
- Validate the TXT record
- Add the custom domain to the Azure Function App
- Create a Azure managed certificate for the custom domain. The display name of the certificate object in Azure will be "mtasts-cert-<your-custom-domain>"
- Bind the Azure managed certificate for the custom domain to the Function App with SNI SSL. SNI SSL is a newer protocol that allows multiple SSL certificates to share the same IP address. It is the most common way to secure a custom domain with a certificate. (RECOMMENDED)
Simply run the function and edit the parameters as required:
Add-PSMTASTSCustomDomain -CsvPath "C:\temp\acceptedDomains.csv" -ResourceGroupName "rg-PSMTASTS" -FunctionAppName "func-PSMTASTS"
Alternatively, check out the comment-based help of the function using Get-Help -Name Add-PSMTASTSCustomDomain -Full
for more information, for example how to add a single custom domain or how to add a custom domain without TLS/SSL certificate.
If you do not want to add the custom domains automatically, follow these steps:
Go to Azure Portal, search for "Function App", switch to the service page and select the function app you created in step 2.
Select "Custom domains" and click "Add custom domain". The flyout will open and you can enter the following information depending on your preferences:
- Domain Provider
- Select "App Service Domain", if you have an App Service Domain and want to use it
- Select "All other domain services", if you want to use a custom domain
- TLS/SSL certificate
- Select "App Service Managed Certificate" to use a certificate managed by Azure. This is the easiest way to secure your custom domain with a certificate. Azure will automatically renew the certificate for you and you don't have to worry about it. (RECOMMENDED)
- Select "Add certificate later" to add your own certificate later
- TLS/SSL type (if you selected "App Service Managed Certificate" as recommended)
- Select "SNI SSL" to use SNI SSL. SNI SSL is a newer protocol that allows multiple SSL certificates to share the same IP address. It is the most common way to secure a custom domain with a certificate. (RECOMMENDED)
- Select "IP SSL" to use IP SSL. IP SSL is an older protocol that requires a dedicated IP address for each SSL certificate. It is less common and comes with additional costs.
- Domain
- Enter your custom domain. This must be the same domain you created the CNAME and TXT records for in step 3. The domain must be validated before it can be added.
Click on "Validate" and after the validation passed, click on "Add" to add the custom domain.
Repeat these steps for each custom domain you want to add.
Lastly, you have to create a TXT record in your public DNS to enable MTA-STS for your domain. To do so, go to your public DNS provider and create the following record per domain:
Name | Type | Value |
---|---|---|
_mta-sts.<your-custom-domain> | TXT | v=STSv1; id=<your own unique id, e.g. the current date as 20230712120000>Z; |
To verify that your MTA-STS policy is working, you can use the Test-PSMTASTSConfiguration function. The function will test the MTA-STS policy for your domain and return the result. If you want to export the result to a CSV file, you can use the -ExportResult parameter as shown in the second example below.
# Reads list of accepted domains from "C:\temp\acceptedDomains.csv" and checks if MTA-STS is configured correctly for each domain in Function App "MTA-STS-FunctionApp".
Test-PSMTASTSConfiguration -CsvPath "C:\temp\acceptedDomains.csv" -FunctionAppName "func-PSMTASTS"
# Reads list of accepted domains from "C:\temp\acceptedDomains.csv" and checks if MTA-STS is configured correctly for each domain in Function App "MTA-STS-FunctionApp". It also exports result to "C:\temp\mta-sts-result.csv".
Test-PSMTASTSConfiguration -CsvPath "C:\temp\acceptedDomains.csv" -FunctionAppName "func-PSMTASTS" -ExportResult -ResultPath "C:\temp\mta-sts-result.csv"
CONGRATULATIONS! You have successfully configured MTA-STS for your domains.
You made a huge step towards a more secure email communication. Now, you can sit back and relax. Your MTA-STS policy will be published automatically.
It is recommended to monitor your Azure Function App, so you can react quickly in case of an error. To learn how to create a alert rule for your function app check out the PS.MTA-STS Alert rule recommendation or follow the instructions in the official Microsoft documentation Create or edit an alert rule
Additionally, we recommend to configure a TLSRPT policy to receive reports about your MTA-STS policy. This will help you to identify issues and to improve your policy.
Defined in rfc8460
This DNS Record allows the Sender MTA to send Reports (similar to DMARC) to a defined Emailadress or a HTML Site for reporting purposes. While Microsoft does not offer a Service to aggregate these Reports, there are plenty of TLSRPT Data providers that can do this Job.
_smtp._tls.example.com. IN TXT "v=TLSRPTv1;rua=mailto:reports@example.com"
_smtp._tls.example.com. IN TXT "v=TLSRPTv1; rua=https://reporting.example.com/v1/tlsrpt"
We are looking forward to your feedback. If you have any questions or suggestions, please feel free to open an issue in the PS.MTA-STS GitHub repository.