Setting Up DMARC for AWS SES: Developer Guide

Technical guide for developers: Configure SPF, DKIM, and DMARC for Amazon SES. Includes CLI commands, code examples, and best practices.

December 15, 2025
8 min read
Share:
Setting Up DMARC for AWS SES: Developer Guide

Introduction

Amazon Simple Email Service (SES) is a powerful, cost-effective email platform for developers. However, proper authentication configuration is critical for deliverability and avoiding the SES spam folder.

This technical guide covers complete email authentication setup for AWS SES, including CLI commands and infrastructure-as-code examples.

What you'll learn:

  • Configure SES domain identity with SPF and DKIM
  • Implement DMARC policy
  • Handle multiple sending domains
  • Monitor and troubleshoot authentication
  • Best practices for production deployments

Prerequisites:

  • AWS account with SES access
  • Domain with DNS management
  • AWS CLI installed and configured
  • Basic understanding of DNS and email authentication

Step 1: Verify Domain in SES

1.1: Verify Domain Identity (Console)

  1. Open SES Console

  2. Create Domain Identity

    • Click "Identities" → "Create identity"
    • Select "Domain"
    • Enter your domain: yourdomain.com
    • Check "Assign a default configuration set" (optional)
    • Click "Create identity"
  3. Copy DNS Records

    • SES provides DNS records to add:
      • Domain verification (TXT or CNAME)
      • DKIM records (3 CNAME records)
      • (Optionally) Custom MAIL FROM

1.2: Verify Domain Identity (CLI)

# Verify domain identity
aws ses verify-domain-identity --domain yourdomain.com --region us-east-1

# Get DKIM tokens
aws ses verify-domain-dkim --domain yourdomain.com --region us-east-1

Output:

{
    "DkimTokens": [
        "token1abc123",
        "token2def456",
        "token3ghi789"
    ]
}

1.3: Verify Domain via CloudFormation

AWSTemplateFormatVersion: '2010-09-09'
Resources:
  SESIdentity:
    Type: AWS::SES::EmailIdentity
    Properties:
      EmailIdentity: yourdomain.com
      DkimSigningAttributes:
        NextSigningKeyLength: RSA_2048_BIT

1.4: Verify Domain via Terraform

resource "aws_ses_domain_identity" "main" {
  domain = "yourdomain.com"
}

resource "aws_ses_domain_dkim" "main" {
  domain = aws_ses_domain_identity.main.domain
}

Step 2: Configure DNS Records

2.1: Add Domain Verification Record

SES provides a verification token. Add TXT record:

  • Name: _amazonses.yourdomain.com
  • Type: TXT
  • Value: Verification token from SES

Example:

_amazonses.yourdomain.com TXT "abc123def456ghi789..."

2.2: Add DKIM Records

SES provides three DKIM tokens. Create three CNAME records:

token1abc123._domainkey.yourdomain.com CNAME token1abc123.dkim.amazonses.com
token2def456._domainkey.yourdomain.com CNAME token2def456.dkim.amazonses.com
token3ghi789._domainkey.yourdomain.com CNAME token3ghi789.dkim.amazonses.com

Using Route 53 CLI:

# Get hosted zone ID
ZONE_ID=$(aws route53 list-hosted-zones-by-name --dns-name yourdomain.com \
  --query "HostedZones[0].Id" --output text)

# Add DKIM CNAME records
aws route53 change-resource-record-sets --hosted-zone-id $ZONE_ID --change-batch '{
  "Changes": [
    {
      "Action": "CREATE",
      "ResourceRecordSet": {
        "Name": "token1abc123._domainkey.yourdomain.com",
        "Type": "CNAME",
        "TTL": 1800,
        "ResourceRecords": [{"Value": "token1abc123.dkim.amazonses.com"}]
      }
    },
    {
      "Action": "CREATE",
      "ResourceRecordSet": {
        "Name": "token2def456._domainkey.yourdomain.com",
        "Type": "CNAME",
        "TTL": 1800,
        "ResourceRecords": [{"Value": "token2def456.dkim.amazonses.com"}]
      }
    },
    {
      "Action": "CREATE",
      "ResourceRecordSet": {
        "Name": "token3ghi789._domainkey.yourdomain.com",
        "Type": "CNAME",
        "TTL": 1800,
        "ResourceRecords": [{"Value": "token3ghi789.dkim.amazonses.com"}]
      }
    }
  ]
}'

2.3: Verify DKIM Status

Console: Check identity status (should show "Verified" with DKIM enabled)

CLI:

aws ses get-identity-dkim-attributes --identities yourdomain.com --region us-east-1

Expected output:

{
    "DkimAttributes": {
        "yourdomain.com": {
            "DkimEnabled": true,
            "DkimVerificationStatus": "Success",
            "DkimTokens": ["token1abc123", "token2def456", "token3ghi789"]
        }
    }
}

Step 3: Configure SPF for SES

3.1: Basic SPF Record

Add TXT record:

  • Name: @ or yourdomain.com
  • Type: TXT
  • Value: v=spf1 include:amazonses.com ~all

3.2: SPF with Multiple Services

If using SES + other services:

v=spf1 include:amazonses.com include:_spf.google.com ~all

3.3: Regional SPF Considerations

Important: SES sends from regional endpoints. The include amazonses.com covers all regions.

Alternative (region-specific):

  • US East (N. Virginia): include:ses-email.us-east-1.amazonses.com
  • US West (Oregon): include:ses-email.us-west-2.amazonses.com
  • EU (Ireland): include:ses-email.eu-west-1.amazonses.com

Using include:amazonses.com is recommended (covers all regions).

3.4: Add SPF via Route 53 CLI

ZONE_ID=$(aws route53 list-hosted-zones-by-name --dns-name yourdomain.com \
  --query "HostedZones[0].Id" --output text)

aws route53 change-resource-record-sets --hosted-zone-id $ZONE_ID --change-batch '{
  "Changes": [{
    "Action": "CREATE",
    "ResourceRecordSet": {
      "Name": "yourdomain.com",
      "Type": "TXT",
      "TTL": 300,
      "ResourceRecords": [{"Value": "\"v=spf1 include:amazonses.com ~all\""}]
    }
  }]
}'

3.5: Verify SPF

dig yourdomain.com TXT

Or use: SPF Checker →

Step 4: Configure Custom MAIL FROM (Optional but Recommended)

4.1: Why Use Custom MAIL FROM?

By default, SES uses amazonses.com as the MAIL FROM domain, which causes SPF alignment issues with DMARC.

Custom MAIL FROM benefits:

  • Proper SPF alignment
  • Better DMARC compliance
  • Professional appearance

4.2: Set Custom MAIL FROM Domain

Choose subdomain: bounce.yourdomain.com or mail.yourdomain.com

Via Console:

  1. Go to SES Identity
  2. Click "Custom MAIL FROM domain"
  3. Enter subdomain: bounce.yourdomain.com
  4. Choose "Use a default value" or "Reject email"
  5. Save

Via CLI:

aws ses set-identity-mail-from-domain \
  --identity yourdomain.com \
  --mail-from-domain bounce.yourdomain.com \
  --behavior-on-mx-failure UseDefaultValue \
  --region us-east-1

4.3: Add DNS Records for Custom MAIL FROM

SES requires two records:

MX record:

  • Name: bounce.yourdomain.com
  • Type: MX
  • Value: 10 feedback-smtp.us-east-1.amazonses.com
  • Priority: 10

SPF record:

  • Name: bounce.yourdomain.com
  • Type: TXT
  • Value: v=spf1 include:amazonses.com ~all

Route 53 CLI:

aws route53 change-resource-record-sets --hosted-zone-id $ZONE_ID --change-batch '{
  "Changes": [
    {
      "Action": "CREATE",
      "ResourceRecordSet": {
        "Name": "bounce.yourdomain.com",
        "Type": "MX",
        "TTL": 300,
        "ResourceRecords": [{"Value": "10 feedback-smtp.us-east-1.amazonses.com"}]
      }
    },
    {
      "Action": "CREATE",
      "ResourceRecordSet": {
        "Name": "bounce.yourdomain.com",
        "Type": "TXT",
        "TTL": 300,
        "ResourceRecords": [{"Value": "\"v=spf1 include:amazonses.com ~all\""}]
      }
    }
  ]
}'

4.4: Verify Custom MAIL FROM

aws ses get-identity-mail-from-domain-attributes \
  --identities yourdomain.com \
  --region us-east-1

Step 5: Implement DMARC

5.1: Create DMARC Record

Add TXT record:

  • Name: _dmarc.yourdomain.com
  • Type: TXT
  • Value:
    v=DMARC1; p=none; rua=mailto:dmarc@yourdomain.com; fo=1

5.2: Route 53 CLI

aws route53 change-resource-record-sets --hosted-zone-id $ZONE_ID --change-batch '{
  "Changes": [{
    "Action": "CREATE",
    "ResourceRecordSet": {
      "Name": "_dmarc.yourdomain.com",
      "Type": "TXT",
      "TTL": 300,
      "ResourceRecords": [{"Value": "\"v=DMARC1; p=none; rua=mailto:dmarc@yourdomain.com; fo=1\""}]
    }
  }]
}'

5.3: Progressive DMARC Policy

Monitoring (week 1-4):

v=DMARC1; p=none; rua=mailto:dmarc@yourdomain.com

Quarantine (week 5-8):

v=DMARC1; p=quarantine; pct=100; rua=mailto:dmarc@yourdomain.com; adkim=r; aspf=r

Reject (week 9+):

v=DMARC1; p=reject; pct=100; rua=mailto:dmarc@yourdomain.com; adkim=r; aspf=r

5.4: Verify DMARC

dig _dmarc.yourdomain.com TXT

Or use: DMARC Checker →

Step 6: Send Test Email

6.1: Send Test via AWS SDK (Python)

import boto3

ses = boto3.client('ses', region_name='us-east-1')

response = ses.send_email(
    Source='test@yourdomain.com',
    Destination={'ToAddresses': ['recipient@example.com']},
    Message={
        'Subject': {'Data': 'Test Email - DMARC Verification'},
        'Body': {'Text': {'Data': 'This is a test email to verify authentication.'}}
    }
)

print(f"Email sent. Message ID: {response['MessageId']}")

6.2: Send Test via CLI

aws ses send-email \
  --from test@yourdomain.com \
  --to recipient@example.com \
  --subject "Test Email" \
  --text "Test authentication" \
  --region us-east-1

6.3: Verify Email Headers

  1. Send to Gmail/Outlook
  2. View original message
  3. Check Authentication-Results header

Expected:

Authentication-Results: mx.google.com;
       dkim=pass header.i=@yourdomain.com;
       spf=pass smtp.mailfrom=yourdomain.com;
       dmarc=pass

Step 7: Production Best Practices

7.1: Use Configuration Sets

Track sends, opens, clicks, bounces:

aws ses create-configuration-set \
  --configuration-set Name=production-emails \
  --region us-east-1

7.2: Set Up Event Publishing

Monitor bounces and complaints:

aws ses put-configuration-set-event-destination \
  --configuration-set-name production-emails \
  --event-destination '{
    "Name": "CloudWatch",
    "Enabled": true,
    "MatchingEventTypes": ["send","bounce","complaint"],
    "CloudWatchDestination": {
      "DimensionConfigurations": [{
        "DimensionName": "ses:configuration-set",
        "DimensionValueSource": "messageTag",
        "DefaultDimensionValue": "production-emails"
      }]
    }
  }'

7.3: Monitor Sending Reputation

aws ses get-account-sending-enabled --region us-east-1

aws ses get-send-quota --region us-east-1

7.4: Handle Bounces and Complaints

Set up SNS topic for notifications:

aws ses set-identity-notification-topic \
  --identity yourdomain.com \
  --notification-type Bounce \
  --sns-topic arn:aws:sns:us-east-1:123456789012:ses-bounces

Troubleshooting

Issue: DKIM verification fails

Symptoms: DKIM status shows "Pending" or "Failed"

Solution:

  • Verify all three CNAME records are added correctly
  • Check for typos in DKIM tokens
  • Wait up to 72 hours for DNS propagation
  • Use dig to verify CNAME records

Issue: SPF alignment fails with DMARC

Symptoms: SPF passes but DMARC fails alignment

Solution:

  • Configure custom MAIL FROM domain
  • Ensure custom MAIL FROM has proper MX and SPF records
  • Use relaxed alignment in DMARC: aspf=r

Issue: Emails in sandbox mode

Symptoms: Can only send to verified addresses

Solution: Request production access:

# Submit request via AWS Support Console
# Or use CLI to check status:
aws ses get-account-sending-enabled

Infrastructure as Code Examples

Complete Terraform Example

resource "aws_ses_domain_identity" "main" {
  domain = "yourdomain.com"
}

resource "aws_ses_domain_dkim" "main" {
  domain = aws_ses_domain_identity.main.domain
}

resource "aws_ses_domain_mail_from" "main" {
  domain           = aws_ses_domain_identity.main.domain
  mail_from_domain = "bounce.${aws_ses_domain_identity.main.domain}"
}

resource "aws_route53_record" "dkim" {
  count   = 3
  zone_id = aws_route53_zone.main.zone_id
  name    = "${element(aws_ses_domain_dkim.main.dkim_tokens, count.index)}._domainkey"
  type    = "CNAME"
  ttl     = 300
  records = ["${element(aws_ses_domain_dkim.main.dkim_tokens, count.index)}.dkim.amazonses.com"]
}

resource "aws_route53_record" "spf" {
  zone_id = aws_route53_zone.main.zone_id
  name    = ""
  type    = "TXT"
  ttl     = 300
  records = ["v=spf1 include:amazonses.com ~all"]
}

resource "aws_route53_record" "dmarc" {
  zone_id = aws_route53_zone.main.zone_id
  name    = "_dmarc"
  type    = "TXT"
  ttl     = 300
  records = ["v=DMARC1; p=none; rua=mailto:dmarc@yourdomain.com"]
}

The Bottom Line

AWS SES authentication setup involves:

  1. Domain verification: Verify domain identity in SES
  2. DKIM: Add three CNAME records (auto-configured)
  3. SPF: Add include:amazonses.com
  4. Custom MAIL FROM: Configure for proper SPF alignment
  5. DMARC: Progressive policy enforcement

Timeline:

  • Setup: 30-45 minutes
  • DNS propagation: 1-24 hours
  • Monitoring: 2-4 weeks
  • Full enforcement: 6-8 weeks

Next Steps

Verify your AWS SES configuration:

  1. Check SPF →
  2. Verify DKIM →
  3. Test DMARC →
  4. Complete audit →

Need automated DMARC monitoring? Start free trial →


Related Articles:

Tags:aws-sesamazon-sesdmarc-setupdevelopers

Ready to improve your email deliverability?

Start monitoring your DMARC reports and get insights into your email authentication setup.

Start Free Trial