This post will show you how to request & renew free LetsEncrypt certificate using PowerShell in automated way.
As an addition, I will show you how you can use it for MailEnable SMTP server.
We will use:
MailEnable as a free SMTP server, but the certificate can be created the same way for other SMTP/Web servers too
PowerShell script to request/renew LetsEncrypt certificate using Posh-ACME module run through Scheduled task
LetsEncrypt DNS-01 challenge type for issuing the certificate
AWS Route53 DNS service, because it has API that allows you to scope permissions to modify just one specific DNS record
The result will be set & forget solution for managing LetsEncrypt certificates 👍.
SMTP certificate requirements
The default LetsEncrypt certificate is just fine
It has to have a private key and an SMTP server service account (SYSTEM in the case of MailEnable) has to be able to read it
In the case of MailEnable SMTP server, it has to be placed in computer's personal certificate store
Creating AWS IAM user with permission to modify just LetsEncrypt DNS verification TXT record
Because we will use DNS challenge to request/renew the LetsEncrypt certificate, we need to use DNS provider that supports API access and set up some automation around it.
I've used this great post, which you can check to get more details.
In general, we need to create in the AWS console:
Create AWS IAM user
Create an ordinary IAM user with default settings

Create a user Access key
Open newly created user Settings \ Security credentials \ Create access key

Select Third-party service type

Make a note of the newly created Access key and its Secret! We will need this later when setting up PowerShell automation.

Create AWS IAM policy
Now we will create an IAM policy that will allow our user to set and modify only selected TXT record (LetsEncrypt TXT record for satisfying the challenge).
Create a new policy

When on Specify permissions tab, switch setting to JSON.

Copy the following JSON and use it to replace the default one.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "route53:GetChange",
"Resource": "arn:aws:route53:::change/*"
},
{
"Effect": "Allow",
"Action": "route53:ListHostedZonesByName",
"Resource": "*"
},
{
"Effect": "Allow",
"Action": ["route53:ListResourceRecordSets"],
"Resource": ["arn:aws:route53:::hostedzone/Z11111112222222333333"]
},
{
"Effect": "Allow",
"Action": ["route53:ChangeResourceRecordSets"],
"Resource": ["arn:aws:route53:::hostedzone/Z11111112222222333333"],
"Condition": {
"ForAllValues:StringEquals": {
"route53:ChangeResourceRecordSetsNormalizedRecordNames": [
"_acme-challenge.example.com"
],
"route53:ChangeResourceRecordSetsRecordTypes": ["TXT"]
}
}
}
]
}
Now we need to modify it a little bit!
Replace all Z11111112222222333333 with your hosted zone ID and example.com with a domain name for which you will create the certificate (for example smtp.contoso.com).
Zone ID can be found in Route 53 \ Hosted zones \ <the zone that will host the TXT record> \ Hosted zone details

Save the policy.
For more details check https://paulgalow.com/aws-route-53-iam-policy-letsencrypt-dns/
Attach IAM policy to the IAM user
Switch to Policies menu and filter your custom policies (Customer managed).

Pick the one you've created, select Entities attached and use Attach button to attach it to our IAM user.

The user now has the minimum possible permission to set the LetsEncrypt TXT challenge record.
Set up PowerShell script for managing certificate creation & renewal
💡
The following steps need to be done on the system where the certificate will be requested!
As stated earlier, the PSH script uses Posh-ACME module hence, we need to install it.
Install-Module "Posh-ACME"
Export Access key credential for making unattended AWS authentication
To be able to request and renew a LetsEncrypt certificate in the future, we must securely store AWS secret generated earlier.
Because DPAPI is used to store these credentials, the same account that will be used to run PSH script that requests and renews a LetsEncrypt certificate must be used to run the following code!
And it has to be a SYSTEM account because we need to install the requested certificate to the Computer Personal Certificate Store (because of MailEnable) and at the same time MailEnable server service account (which is a SYSTEM) must be able to read its private key (if you customize your MailEnable to run under different account, you can use any account with admin privileges to run this script).
Save following code to ps1 file and run it using Scheduled Task under SYSTEM account.
💡
Don't forget to modify $accessKey, $accessKeySecret, $xmlPath variables! $accessKey and $accessKeySecret corresponds to the AWS user access secret. $xmlPath is a path to XML, where this secret will be securely stored.
$accessKey = "<accessKey>"
$accessKeySecret = "<accessKeySecret>"
$xmlPath = "folderWhereAWSCredentialsAreStored\aws_api_credential.xml"
$securedAccessKeySecret = ConvertTo-SecureString $accessKeySecret -AsPlainText -Force
$credential = New-Object System.Management.Automation.PSCredential $accessKey, $securedAccessKeySecret
Export-Clixml -InputObject $credential -Path $xmlPath -Encoding UTF8 -Force -ea Stop
As a result you will get aws_api_credential.xml file with securely stored AWS credentials.
Create 'letsencrypt_cert.ps1' PSH script
Copy the following code and save it as letsencrypt_cert.ps1 file.
💡
Don't forget to modify $certDomainName, $expireContact, $cred variables to match your environment!
$ErrorActionPreference = "Stop"
$certDomainName = "smtp.contoso.com"
$expireContact = "postmaster@contoso.com"
$cred = Import-Clixml "folderWhereAWSCredentialsAreStored\aws_api_credential.xml"
Start-Transcript (Join-Path $PSScriptRoot ((Split-Path $PSCommandPath -Leaf) + ".log"))
Import-Module -Name "Posh-ACME"
Set-PAServer "LE_PROD"
if (!(Get-PAAccount -List)) {
"No account exists, creating"
New-PAAccount -Contact $expireContact -AcceptTOS
}
$pArgs = @{
R53AccessKey = $cred.UserName
R53SecretKey = $cred.Password
}
"Getting existing orders"
$PAOrder = Get-PAOrder -Name $certDomainName
if ($PAOrder) {
"Trying to renew"
$renewed = Submit-Renewal -Name $certDomainName -PluginArgs $pArgs -Verbose
if ($renewed) {
"Renewal was successful"
"Applying new certificate to MailEnable server"
"Stopping MailEnable services"
Stop-Service -DisplayName "MailEnable*"
"Removing old $certDomainName certificate from Personal Store"
Get-ChildItem Cert:\LocalMachine\My | Where-Object { $_.FriendlyName -eq $certDomainName } | Remove-Item
"Installing certificate to Personal store"
Get-PACertificate -Name $certDomainName | Install-PACertificate -StoreLocation LocalMachine -StoreName My
"Starting MailEnable services"
Start-Service -DisplayName "MailEnable*"
} else {
return "No renewal needed"
}
} else {
"Creating new certificate request"
New-PACertificate -Name $certDomainName -Domain $certDomainName -AcceptTOS -Contact $expireContact -Plugin Route53 -PluginArgs $pArgs -PfxPass ($cred.GetNetworkCredential().password.substring(0, 15)) -Force -Verbose
"Installing certificate to Personal store"
Get-PACertificate -Name $certDomainName | Install-PACertificate -StoreLocation LocalMachine -StoreName My
throw "Certificate was created, but now you need to select it in the MailEnable configuration manually to finish the setup"
}
The code is based on the following official tutorial https://poshac.me/docs/v4/Tutorial/.
Create scheduled task to run 'letsencrypt_cert.ps1'
Now when we have ready aws_api_credential.xml file with AWS credentials and letsencrypt_cert.ps1 with an actual script, we need to automate the invocation of it.
Open Scheduled Task and create a new Scheduled Task as follows.


The random delay is to minimize the chance of LetsEncrypt service throttling.

For the first time, run the scheduled task manually, to immediately get the requested certificate.
Invocation result will be logged in directory where letsencrypt_cert.ps1 script is located, named as letsencrypt_cert.ps1.log.
When the script finishes, you should see new certificate in the computer's personal certificate store, which means, everything is working as expected ❤.
Set up MailEnable server to use our LetsEncrypt certificate
To be able to use the certificate with MailEnable SMTP server:
The certificate has to be placed in computer's personal certificate store
- because renewal process creates a new one with each run, make sure you delete the expired one (my PSH script manages that)
The certificate has to contain a private key
MailEnable service account (SYSTEM by default) has to have the right to read our certificate private key
Now when the LetsEncrypt certificate exists, you have to open the MailEnable console and select it there

💡
It seems like even if you select NONE from the drop-down list, but the correct certificate exists, it will be automatically used anyway!
Restart the server, send a test email, and check MailEnable SMTP\Logs\Debug log. You should see something like this

Which mean that everything is working as it should!