Amazon SES, DKIM and DMARC: Email Authentication for Every Smarter Environment
Smarter sends its email through Amazon SES, and Terraform gives every environment its own SES domain identity, DKIM records and a DMARC policy, along with an SMTP user that can do one thing: send. This post walks through that setup in smarter-infrastructure, including the two places where it stops short of what you might expect, no SPF record and a long-lived SMTP key, and why those are tradeoffs rather than oversights.
Smarter doesn’t send much email, but what it sends matters. Password reset links, new-account notices, the result of a custom domain’s verification, deployment notices and the logs of infrastructure jobs all go out by SMTP. A password reset email is exactly the kind of message that phishing imitates, and a platform’s domain is exactly the kind of name that gets spoofed. Email authentication is how a receiving mail server tells the real message from the imitation.
Three records and a policy
Each environment’s platform domain (for example alpha.platform.example.com, or platform.example.com in production) becomes an SES domain identity, with Easy DKIM, in ses.tf.
resource "aws_ses_domain_identity" "environment_platform_domain" {
domain = local.environment_platform_domain
}
resource "aws_ses_domain_dkim" "environment_platform_domain" {
domain = aws_ses_domain_identity.environment_platform_domain.domain
}
The DNS side goes into the environment’s own Route 53 zone, in route53.tf. It holds a TXT record that proves to SES that the identity’s creator controls the domain, three DKIM CNAMEs, and a DMARC policy.
resource "aws_route53_record" "environment_platform_domain_amazonses_dkim_record" {
count = 3
zone_id = aws_route53_zone.environment_platform_domain.zone_id
name = "${aws_ses_domain_dkim.environment_platform_domain.dkim_tokens[count.index]}._domainkey"
type = "CNAME"
ttl = "600"
records = ["${aws_ses_domain_dkim.environment_platform_domain.dkim_tokens[count.index]}.dkim.amazonses.com"]
}
resource "aws_route53_record" "environment_platform_domain_amazonses_dmarc_record" {
zone_id = aws_route53_zone.environment_platform_domain.zone_id
name = "_dmarc.${local.environment_platform_domain}"
type = "TXT"
ttl = "600"
records = ["v=DMARC1;p=quarantine;rua=mailto:my_dmarc_report@${var.root_domain}"]
}
Here is what each piece does.
- The verification TXT record lets SES confirm that whoever created the identity controls the domain. Until it does, SES won’t send as that domain.
- DKIM signs every outgoing message with a private key that SES holds. The CNAMEs point at public keys that AWS publishes, so a receiver can check the signature, and there is no key file for anyone to manage or leak.
- DMARC tells receivers what to do with mail that claims to be from the domain but can’t prove it.
p=quarantinemeans “put it in spam”.ruaasks receivers to send aggregate reports to a mailbox.

Because all of it is in Terraform, a new environment gets the same records as every other one, and destroying an environment removes them.
Why DKIM carries the weight
DMARC passes when a message passes either SPF or DKIM, and the domain that passed is aligned with the domain in the visible From header. That second part is the one people forget. SPF checks the envelope sender (the bounce address), not the From header that the reader sees.
SES, by default, uses its own bounce address, a subdomain of amazonses.com. So SPF on a message from Smarter passes, for amazonses.com, and is not aligned with the platform’s domain. DKIM is different. Easy DKIM signs with the identity’s own domain, so the signature is aligned. For DMARC, Smarter’s mail is authenticated by DKIM, and only by DKIM.

What no SPF record costs
Terraform creates no SPF record for the platform domain, and no custom MAIL FROM domain in SES. Given the default bounce address, an SPF record on the platform domain would not be checked for Smarter’s own mail, so leaving it out costs nothing on the normal path.
It does cost something at the edges.
- DMARC has only one way to pass. If a message’s DKIM signature breaks, for example when a mailing list or a forwarding rule rewrites the body, there is no aligned SPF result to fall back on, and the message is quarantined.
- Some receivers and spam filters score a domain without SPF lower, whatever DMARC says.
- An explicit SPF record that says “nothing sends as this domain directly” is a cheap, clear statement, and the domain doesn’t make it.
None of these weakens the protection against spoofing. DMARC with p=quarantine already tells receivers to distrust unauthenticated mail claiming the domain, with or without SPF. The gap is deliverability and resilience, not impersonation. If you want both paths, SES supports a custom MAIL FROM subdomain. You add an MX record and an SPF record for that subdomain, and SPF becomes aligned too. It’s a handful of Terraform resources. Smarter’s mail volume is small and almost all of it is sent directly to the person it’s for, so I’ve left it as a known gap rather than added more DNS to every environment.
One action, one long-lived key
The SMTP user can do one thing, in iam.tf.
data "aws_iam_policy_document" "ses_sender" {
statement {
actions = ["ses:SendRawEmail"]
resources = ["*"]
}
}
resource "aws_iam_user" "smtp_user" {
name = "${local.environment_namespace}_smtp_user"
tags = local.tags
}
resource "aws_iam_access_key" "smtp_user" {
user = aws_iam_user.smtp_user.name
}
ses:SendRawEmail is what the SES SMTP interface needs, and nothing else. The credentials can’t read mail, change identities, touch DNS or call any other AWS service. There’s one such user per environment, so each environment’s credentials are separate.
Terraform derives the SMTP password from the access key and writes both into a Kubernetes Secret in the environment’s namespace, in kubernetes-ses.tf.
data = {
SMTP_HOST = "email-smtp.${var.aws_region}.amazonaws.com"
SMTP_PORT = 587
SMTP_USE_SSL = false
SMTP_USE_TLS = true
SMTP_USERNAME = aws_iam_access_key.smtp_user.id
SMTP_PASSWORD = aws_iam_access_key.smtp_user.ses_smtp_password_v4
}
The deploy workflow reads that Secret and passes the values to the Helm chart, so no human ever copies the password. The application connects with STARTTLS on port 587.
The tradeoff is that this is a static IAM access key, and it lives until someone replaces it. Nothing rotates it. That follows from SMTP itself, because the SES SMTP endpoint authenticates with a username and password, not with an IAM role, so a pod can’t use IRSA to send mail over SMTP the way cert-manager uses it to write DNS records. The alternatives are to send through the SES API with IRSA instead of SMTP, or to keep SMTP and rotate the key on a schedule.
So the honest summary is that the key has a small blast radius and a long life. What someone holding it could do is send mail through SES in that account, which is why the policy has one action and why the key lives only in a Kubernetes Secret. Two refinements would shrink it further. The policy’s resources = ["*"] lets the key send as any verified identity in the account, and could name the environment’s identity instead. And because Terraform creates the key, the key is also in Terraform’s state, which therefore needs the same protection as the Secret. Treat state as a secret store whenever Terraform creates credentials.
A note on the From address
DKIM alignment depends on the From header’s domain. Smarter’s application sends from SMTP_FROM_EMAIL if it’s set, and otherwise from no-reply@ the platform’s domain, in settings.py.
if not is_missing_value(settings_defaults.SMTP_FROM_EMAIL):
return str(settings_defaults.SMTP_FROM_EMAIL)
return f"no-reply@{self.platform_subdomain}.{self.root_domain}"
The default is the production platform domain. For each environment’s mail to be signed by that environment’s own identity, set SMTP_FROM_EMAIL to an address in the environment’s domain. It’s one environment variable, and it’s the difference between “every environment has an identity” and “every environment uses its own”.
How much does it matter?
I’d rate this a 5 out of 10 for Smarter. Spoofed email from the platform’s domain is a real threat to its users, and DMARC with DKIM is the standard answer. But the attack surface is narrow, Smarter’s own mail volume is small, and the controls are commodity, which every serious sender should have. The score would be higher for a platform whose emails carry more authority, such as invoices or approvals.
Cost, burden, UX and stability
SES charges per message sent, about ten cents per thousand, so Smarter’s email costs pennies a month. The DNS records add nothing measurable to the cost of the zones they live in.
The ongoing burden is small but real.
- DMARC aggregate reports are only useful if the
ruamailbox exists and someone reads them, or feeds them to a report tool. Without that, ap=quarantinepolicy works, but nobody notices when legitimate mail starts failing. - A new AWS account starts in the SES sandbox, which only delivers to verified addresses. Moving to production access is a request to AWS, made once per account and region, and it is not in Terraform.
- The SMTP key should be rotated by hand, or replaced with the SES API and IRSA, as above.
For users, the effect is invisible when it works. Password resets land in the inbox, not in spam, and a spoofed one is more likely to be quarantined. For stability, the only moving part is SES itself, and the application is built so that email never breaks the request that sends it. If SES refuses a message, the email service logs the error, sends an email_failed signal and returns False, rather than raising into the page the user is waiting on.
Should you do the same?
Yes, and it’s one of the cheaper things on any list of security practices. SES identity, Easy DKIM and a DMARC policy are a few Terraform resources per environment, and they move your mail from “probably fine” to “verifiably from you”. Start with p=none if you’re not sure all your senders are signed, read the reports for a few weeks, then move to quarantine or reject.
Decide on SPF and the SMTP key deliberately rather than by default. If deliverability through forwarders matters, add a custom MAIL FROM domain and its SPF record. If you can send through an API instead of SMTP, use your cloud’s workload identity and skip the long-lived key altogether.
Takeaways
- Give every environment its own SES identity, DKIM records and DMARC policy, created by the same Terraform that creates the environment.
- Know which mechanism aligns. With SES’s default bounce address, DMARC passes on DKIM alone.
- Add a custom MAIL FROM domain with SPF if you want a second path through DMARC.
- Give the SMTP user one action,
ses:SendRawEmail, and scope it to the identity it sends as. - Treat a Terraform-created access key as long-lived, so keep it in a Secret, protect the state, and rotate it, or use the SES API with workload identity instead.
- Make sure the DMARC report mailbox exists, and read what arrives there.
Closing thoughts
Email authentication is unglamorous plumbing, and that’s its appeal, because a few DNS records, a signing key nobody has to hold and a one-line policy make a platform’s mail much harder to fake. Smarter has the core of it in every environment. The two gaps, SPF and the key’s lifetime, are known, small and written down here.
