AWS SES Email Configuration Guide

This guide explains how to configure Amazon Simple Email Service (SES) for sending emails in production.

Overview

The application supports two email backends: - AWS SES (recommended for production) - Reliable, scalable email service - SMTP (fallback) - Traditional SMTP server configuration

AWS SES can be used with: - IAM Role (recommended for EC2/ECS/Lambda) - No credentials needed ⭐ - IAM User Credentials (for non-AWS servers) - Access keys required

Prerequisites

  1. An AWS account

  2. AWS CLI installed (optional, for testing)

  3. Verified email addresses or domain in AWS SES

Step 1: Set Up AWS SES

1.1 Create an AWS Account

If you don’t have one, sign up at https://aws.amazon.com

1.2 Verify Your Email Address or Domain

Option A: Verify Individual Email Addresses (Quick start) 1. Go to AWS SES Console 2. Navigate to Verified identitiesCreate identity 3. Select Email address 4. Enter your email address (e.g., aclark@aclark.net) 5. Click Create identity 6. Check your email and click the verification link 7. Repeat for any email addresses you want to send FROM

Option B: Verify Your Domain (Recommended for production) 1. Go to AWS SES Console 2. Navigate to Verified identitiesCreate identity 3. Select Domain 4. Enter your domain (e.g., aclark.net) 5. Follow the instructions to add DNS records (DKIM, SPF, DMARC) 6. Wait for verification (can take up to 72 hours)

1.3 Request Production Access

By default, AWS SES starts in sandbox mode, which only allows sending to verified addresses.

To send to any email address: 1. In the SES Console, go to Account dashboard 2. Click Request production access 3. Fill out the form explaining your use case 4. AWS typically approves within 24 hours

1.4 Choose Your AWS Region

Select a region close to your server for better performance: - us-east-1 (N. Virginia) - Default, most features - us-west-2 (Oregon) - eu-west-1 (Ireland) - See AWS SES Regions for full list

Step 2: Set Up IAM Permissions

You have two options for authentication:

Option B: IAM User Credentials (For Non-AWS Servers)

Best for: Applications running outside AWS (on-premises, other cloud providers)

2B.1 Create IAM User

  1. Go to IAM Console

  2. Navigate to UsersCreate user

  3. Enter username (e.g., aclarknet-ses-user)

  4. Click Next

2B.2 Attach SES Permissions

  1. Select Attach policies directly

  2. Search for and select AmazonSESFullAccess (or create a custom policy - see below)

  3. Click NextCreate user

2B.3 Create Access Keys

  1. Click on the newly created user

  2. Go to Security credentials tab

  3. Click Create access key

  4. Select Application running outside AWS

  5. Click NextCreate access key

  6. IMPORTANT: Save the Access Key ID and Secret Access Key securely

  7. You won’t be able to see the secret key again!

Step 3: Configure Environment Variables

If Using IAM User Credentials (Option B)

Edit your production .env file:

# Enable AWS SES
USE_SES=True

# AWS Credentials (from Step 2B.3)
AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY

# AWS SES Region (use the region where you verified your email/domain)
AWS_SES_REGION_NAME=us-east-1

# Optional: Configuration set for tracking (leave empty if not using)
AWS_SES_CONFIGURATION_SET=

# Use SES v2 API (recommended)
USE_SES_V2=True

Security Note: Never commit these credentials to version control!

Step 4: Test Your Configuration

Test from Django Shell

cd /srv/aclarknet
sudo -u nginx /srv/aclarknet/.venv/bin/python manage.py shell --settings=aclarknet.settings.production
from django.core.mail import send_mail

send_mail(
    'Test Email from AWS SES',
    'This is a test email sent via AWS SES with IAM role authentication.',
    'aclark@aclark.net',  # Must be verified in SES
    ['recipient@example.com'],  # Must be verified if in sandbox mode
    fail_silently=False,
)

If successful, you should see no errors and receive the email.

Test with AWS CLI (Optional)

aws ses send-email \
  --from aclark@aclark.net \
  --destination ToAddresses=recipient@example.com \
  --message Subject={Data="Test Email",Charset=utf8},Body={Text={Data="Test message",Charset=utf8}} \
  --region us-east-1

Verify IAM Role is Working (EC2 Only)

SSH into your EC2 instance and check:

# Check if instance has an IAM role attached
curl http://169.254.169.254/latest/meta-data/iam/security-credentials/

# Should output the role name, e.g., "aclarknet-ses-role"

# Get temporary credentials (these auto-rotate)
curl http://169.254.169.254/latest/meta-data/iam/security-credentials/aclarknet-ses-role

# Should output JSON with AccessKeyId, SecretAccessKey, Token, and Expiration

Step 5: Monitoring and Troubleshooting

Monitor Email Sending

  1. SES Console Dashboard

    • Go to SES Console

    • View sending statistics, bounces, and complaints

  2. CloudWatch Metrics

    • Monitor delivery rates, bounces, complaints

    • Set up alarms for high bounce rates

  3. Django Logs

    • Check /srv/aclarknet/logs/django.log for email errors

Common Issues

Issue: “Unable to locate credentials”

Solution: - If using IAM role: Verify the role is attached to your EC2 instance bash   curl http://169.254.169.254/latest/meta-data/iam/security-credentials/ - If using IAM user: Check that AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY are set in .env

Issue: “Email address is not verified”

Solution: Verify the sender email address in SES Console, or request production access to send to any address.

Issue: “Access Denied” or “InvalidClientTokenId”

Solution: - If using IAM role: Verify the role has SES permissions (check IAM policy) - If using IAM user: Check that AWS credentials are correct and not expired

Issue: Emails going to spam

Solution: - Verify your domain (not just email address) - Set up SPF, DKIM, and DMARC records - Use a verified domain for the FROM address - Avoid spam trigger words in subject/body

Issue: “Daily sending quota exceeded”

Solution: - Check your sending limits in SES Console - Request a limit increase if needed - Implement rate limiting in your application

Sending Limits

Sandbox Mode: - 200 emails per 24 hours - 1 email per second - Can only send to verified addresses

Production Mode: - Starts at 50,000 emails per 24 hours (can be increased) - 14 emails per second (can be increased) - Can send to any address

Step 6: Advanced Configuration

Configuration Sets (Optional)

Configuration sets allow you to track email events (opens, clicks, bounces):

  1. Create a configuration set in SES Console

  2. Set up event destinations (SNS, CloudWatch, Kinesis)

  3. Add to your .env:

    AWS_SES_CONFIGURATION_SET=my-config-set
    

Custom Email Headers

You can add custom headers in your Django email code:

from django.core.mail import EmailMessage

email = EmailMessage(
    'Subject',
    'Body',
    'from@example.com',
    ['to@example.com'],
    headers={'X-SES-CONFIGURATION-SET': 'my-config-set'},
)
email.send()

Bounce and Complaint Handling

Set up SNS notifications for bounces and complaints:

  1. Create SNS topics for bounces and complaints

  2. Configure SES to publish to these topics

  3. Subscribe your application endpoint to handle notifications

  4. Automatically remove bounced/complained addresses from your mailing list

Switching Between SES and SMTP

To switch back to SMTP (e.g., for testing):

# In your .env file
USE_SES=False

# Configure SMTP settings
EMAIL_HOST=smtp.gmail.com
EMAIL_PORT=587
EMAIL_USE_TLS=True
EMAIL_HOST_USER=your-email@gmail.com
EMAIL_HOST_PASSWORD=your-app-password

Cost Estimation

AWS SES Pricing (as of 2026): - First 62,000 emails per month: FREE (when sent from EC2) - After that: $0.10 per 1,000 emails - Attachments: $0.12 per GB

Example: Sending 100,000 emails/month = ~$3.80/month

See AWS SES Pricing for current rates.

Security Best Practices

For IAM User Credentials

  1. ⚠️ Never commit credentials to version control

  2. ⚠️ Rotate access keys regularly (every 90 days)

  3. ⚠️ Use least privilege IAM policies

  4. ⚠️ Store in secrets manager (not .env file) for production

  5. ⚠️ Enable MFA on your AWS account

  6. ⚠️ Monitor CloudTrail logs for suspicious activity

  7. ⚠️ Set up billing alerts to detect unusual usage

Credential Rotation (IAM User Only)

If using IAM user credentials, rotate them regularly:

# 1. Create new access key in IAM Console
# 2. Update .env with new credentials
# 3. Restart application
sudo systemctl restart aclarknet.service
# 4. Test email sending
# 5. Delete old access key in IAM Console

Note: With IAM roles, credentials auto-rotate every hour - no manual rotation needed!

Comparison: IAM Role vs IAM User

Feature

IAM Role (EC2/ECS/Lambda)

IAM User (Non-AWS)

Credentials in .env

❌ No

✅ Yes

Auto-rotation

✅ Every hour

❌ Manual

Security

⭐⭐⭐⭐⭐

⭐⭐⭐

Setup Complexity

Easy

Easy

Credential Leaks

✅ Impossible

⚠️ Possible

Works on AWS

✅ Yes

✅ Yes

Works off AWS

❌ No

✅ Yes

Recommended

✅ Yes (on AWS)

✅ Yes (off AWS)

Additional Resources

Support

For issues specific to this application, check: - Application logs: /srv/aclarknet/logs/django.log - Django settings: aclarknet/settings/production.py - Environment variables: /srv/aclarknet/.env

For AWS SES issues: - AWS Support - AWS SES Forum

Quick Reference

Minimal .env for IAM User (Non-AWS servers)

USE_SES=True
AWS_ACCESS_KEY_ID=your-key-id
AWS_SECRET_ACCESS_KEY=your-secret-key
AWS_SES_REGION_NAME=us-east-1

Test Email Sending

from django.core.mail import send_mail
send_mail('Test', 'Message', 'from@example.com', ['to@example.com'])

Check IAM Role (EC2 only)

curl http://169.254.169.254/latest/meta-data/iam/security-credentials/