Terraform module to configure various email related DNS records on Cloudflare.
Module that configures various email related DNS records on Cloudflare, including serving a MTA-STS policy text file via Cloudflare Workers.
- Configure MX records.
- Configure SPF record.
- Configure DMARC record.
- Configure SMTP TLS reporting record.
- Configure MTA-STS record, generate
mta-sts.txt
policy file and serve it with a Cloudflare Worker onhttps://mta-sts.<your-domain>/.well-known/mta-sts.txt
. - Configure domain key records (
<selector>._domainkey.<your-domain>
).
Examples assume that you have the following variables setup:
cloudflare_account_id
— Your Account ID.cloudflare_zone_id
— ID of the Zone (domain name).cloudflare_zone_name
— Domain name, e.g.foobar.com
.
Adjust examples as needed to fit your setup.
Below example is based on the DNS Basics support article. When going through the domain setup wizard within the Google Workspace Admin, you are likely to be given a slightly different list of MX records, and obviously
Also make sure you generate your own domain key from under Apps > Google Workspace > Gmail > Authenticate Email.
main.tf
module "email" {
source = "jimeh/email/cloudflare"
version = "0.0.2"
account_id = var.cloudflare_account_id
zone_id = var.cloudflare_zone_id
mx = {
"aspmx.l.google.com" = 1
"alt1.aspmx.l.google.com" = 5
"alt2.aspmx.l.google.com" = 5
"aspmx2.googlemail.com" = 10
"aspmx3.googlemail.com" = 10
}
spf_terms = [
"include:_spf.google.com",
"~all",
]
mta_sts_mode = "enforce"
mta_sts_max_age = 86400
mta_sts_mx = [
"*.aspmx.l.google.com",
"*.googlemail.com",
"aspmx.l.google.com",
]
tlsrpt_rua = [
"mailto:tls-report@${var.cloudflare_zone_name}",
]
dmarc_policy = "reject"
dmarc_rua = [
"mailto:dmarc-report@${var.cloudflare_zone_name}",
]
domainkeys = {
"google" = {
type = "TXT"
value = join("", [
# TODO: Replace this example key with a real one.
"v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApAVNwJ9",
"+6ArXN23ZaR8SFSYxVEEbbHRZplZqHVt6uEpcirY+jxHOqV2bvqAY3BHZQs/KoHnFSWUf",
"6zv6ajZgUxvU65UhCbrQ7CwrJCjU8sQFDk+CpbvmXyJIe9G470HuGEs4NmQDoddJZr09V",
"7d3anX8n7ePSCsIxwGi53DMhwijQXqHYMFALml+QIMZ/03ydL6/B3EwDNDFSBSEqzt2QS",
"N43EYb3FlUiGu5NGHl3gibEsbywTmGtN3kmkp/rxqaJPLv16NVpTe+0lAqPiq/pgJT4pp",
"ACz2ENh6BD0H+hDiCKBiw+gyAeDbOn1c5yslENSEyDxqpn17tnxo+O/ZFmwIDAQAB"
])
}
}
}
resource "cloudflare_record" "cname" {
for_each = {
"mail" = { value = "ghs.googlehosted.com", proxied = false }
}
name = lookup(each.value, "name", each.key)
proxied = lookup(each.value, "proxied", false)
ttl = lookup(each.value, "ttl", 1)
type = "CNAME"
value = each.value.value
zone_id = var.cloudflare_zone_id
}
resource "cloudflare_record" "txt" {
for_each = {
"google" = {
value = (
"google-site-verification=__REPLACE_ME_WITH_A_REAL_VALUE__"
)
}
}
name = lookup(each.value, "name", local.zone_name)
proxied = lookup(each.value, "proxied", false)
ttl = lookup(each.value, "ttl", 1)
type = "TXT"
value = each.value.value
zone_id = var.cloudflare_zone_id
}
The below example is based on Fastmail's Manual DNS configuration help article.
main.tf
module "email" {
source = "jimeh/email/cloudflare"
version = "0.0.2"
account_id = var.cloudflare_account_id
zone_id = var.cloudflare_zone_id
mx = {
"in1-smtp.messagingengine.com" = 10
"in2-smtp.messagingengine.com" = 20
}
mx_subdomains = ["*"]
spf_terms = [
"include:spf.messagingengine.com",
"?all"
]
mta_sts_mode = "enforce"
mta_sts_max_age = 86400
mta_sts_mx = [
"in1-smtp.messagingengine.com",
"in2-smtp.messagingengine.com",
]
tlsrpt_rua = [
"mailto:tls-report@${var.cloudflare_zone_name}",
]
dmarc_policy = "reject"
dmarc_rua = [
"mailto:dmarc-report@${var.cloudflare_zone_name}",
]
domainkeys = {
"fm1" = {
type = "CNAME"
value = "fm1.${var.cloudflare_zone_name}.dkim.fmhosted.com"
}
"fm2" = {
type = "CNAME"
value = "fm2.${var.cloudflare_zone_name}.dkim.fmhosted.com"
}
"fm3" = {
type = "CNAME"
value = "fm3.${var.cloudflare_zone_name}.dkim.fmhosted.com"
}
"mesmtp" = {
type = "CNAME"
value = "mesmtp.${var.cloudflare_zone_name}.dkim.fmhosted.com"
}
}
}
resource "cloudflare_record" "srv" {
for_each = {
"_caldav._tcp" = {}
"_caldavs._tcp" = {
port = 433
target = "caldav.fastmail.com"
weight = 1
}
"_carddav._tcp" = {}
"_carddavs._tcp" = {
port = 443
target = "carddav.fastmail.com"
weight = 1
}
"_imap._tcp" = {}
"_imaps._tcp" = {
port = 993
target = "imap.fastmail.com"
weight = 1
}
"_jmap._tcp" = {
port = 443
target = "jmap.fastmail.com"
weight = 1
}
"_pop3._tcp" = {}
"_pop3s._tcp" = {
port = 995
priority = 10
target = "pop.fastmail.com"
weight = 1
}
"_submission._tcp" = {
port = 587
target = "smtp.fastmail.com"
weight = 1
}
}
name = lookup(each.value, "name", each.key)
proxied = lookup(each.value, "proxied", false)
ttl = lookup(each.value, "ttl", 1)
type = "SRV"
zone_id = var.cloudflare_zone_id
data {
name = var.cloudflare_zone_name
port = lookup(each.value, "port", 0)
priority = lookup(each.value, "priority", 0)
proto = split(".", each.key)[1]
service = split(".", each.key)[0]
target = lookup(each.value, "target", ".")
weight = lookup(each.value, "weight", 0)
}
}
Name | Version |
---|---|
cloudflare | >= 3.0, < 5.0 |
Name | Version |
---|---|
cloudflare | >= 3.0, < 5.0 |
No modules.
Name | Type |
---|---|
cloudflare_record.dmarc | resource |
cloudflare_record.domainkeys | resource |
cloudflare_record.mta-sts-a | resource |
cloudflare_record.mta-sts-aaaa | resource |
cloudflare_record.mta_sts | resource |
cloudflare_record.mx | resource |
cloudflare_record.smtp_tls | resource |
cloudflare_record.spf | resource |
cloudflare_worker_route.mta_sts_route | resource |
cloudflare_worker_script.mta_sts | resource |
cloudflare_workers_kv.mta_sts | resource |
cloudflare_workers_kv_namespace.mta_sts | resource |
cloudflare_zone.zone | data source |
Name | Description | Type | Default | Required |
---|---|---|---|---|
account_id | Cloudflare Account ID | string |
n/a | yes |
dmarc_dkim_mode | The DMARC DKIM mode for alignment (options: relaxed , strict ). |
string |
"relaxed" |
no |
dmarc_fo | Failure reporting options for DMARC (characters: 0 , 1 , d , s , separated by : ). |
string |
"1:d:s" |
no |
dmarc_percent | Percentage of messages to apply the DMARC policy to (0-100). | number |
100 |
no |
dmarc_policy | The DMARC policy to apply (options: none , quarantine , reject ). |
string |
"none" |
no |
dmarc_rua | Where aggregate DMARC reports about policy violations should be sent. | list(string) |
n/a | yes |
dmarc_ruf | Where failure/forensic DMARC reports about policy violations should be sent. | list(string) |
[] |
no |
dmarc_spf_mode | The DMARC SPF mode for alignment (options: relaxed , strict ). |
string |
"relaxed" |
no |
dmarc_ttl | TTL for _dmarc DNS record. 1 is auto. Default is 1 . |
number |
1 |
no |
domainkeys | Map of domain keys with name, record type (TXT or CNAME ), and value. |
map(object({ |
{} |
no |
mta_sts_max_age | Maximum lifetime of the policy in seconds, up to 31557600, defaults to 604800 (1 week) | number |
604800 |
no |
mta_sts_mode | MTA policy mode, https://tools.ietf.org/html/rfc8461#section-5 | string |
"testing" |
no |
mta_sts_mx | Additional permitted MX hosts for the MTA STS policy. | list(string) |
[] |
no |
mx | A map representing the MX records. Key is the mail server hostname and value is the priority. | map(number) |
n/a | yes |
mx_subdomains | List of sub-domains to also apply MX records to. | list(string) |
[] |
no |
record_ttl | TTL for DNS records. 1 is auto. Default is 1 . |
number |
1 |
no |
spf_terms | List of SPF terms that should be included in the SPF TXT record. | list(string) |
[ |
no |
tlsrpt_rua | Locations to which aggregate TLS SMTP reports about policy violations should be sent, either mailto: or https: schema. |
list(string) |
n/a | yes |
zone_id | Cloudflare Zone ID | string |
n/a | yes |
Name | Description |
---|---|
mta_sts_policy_url | URL to the MTA-STS policy file. |