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¶
An AWS account
AWS CLI installed (optional, for testing)
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 identities → Create 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 identities → Create 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 A: IAM Role (Recommended for EC2/ECS/Lambda) ⭐¶
Best for: Applications running on AWS infrastructure
Benefits: - ✅ No credentials in your code or .env file - ✅ Automatic credential rotation (every hour) - ✅ Most secure option - ✅ No manual key management - ✅ Can’t accidentally leak credentials
2A.1 Create IAM Role¶
Go to IAM Console
Navigate to Roles → Create role
Select AWS service → EC2 (or ECS/Lambda depending on your setup)
Click Next
2A.2 Attach SES Permissions¶
Search for and select AmazonSESFullAccess (or create a custom policy - see below)
Click Next
Enter role name (e.g.,
aclarknet-ses-role)Click Create role
2A.3 Attach Role to EC2 Instance¶
Go to EC2 Console
Select your instance
Actions → Security → Modify IAM role
Select the role you created (
aclarknet-ses-role)Click Update IAM role
That’s it! No credentials needed in your .env file. boto3 will
automatically use the instance role.
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¶
Go to IAM Console
Navigate to Users → Create user
Enter username (e.g.,
aclarknet-ses-user)Click Next
2B.2 Attach SES Permissions¶
Select Attach policies directly
Search for and select AmazonSESFullAccess (or create a custom policy - see below)
Click Next → Create user
2B.3 Create Access Keys¶
Click on the newly created user
Go to Security credentials tab
Click Create access key
Select Application running outside AWS
Click Next → Create access key
IMPORTANT: Save the Access Key ID and Secret Access Key securely
You won’t be able to see the secret key again!
Minimal IAM Policy (Recommended for Both Options)¶
Instead of AmazonSESFullAccess, use this minimal policy for better
security:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ses:SendEmail",
"ses:SendRawEmail"
],
"Resource": "*"
}
]
}
To create a custom policy: 1. In IAM Console, go to Policies →
Create policy 2. Click JSON tab and paste the policy above 3.
Click Next → Enter name (e.g., SESMinimalSendPolicy) 4. Click
Create policy 5. Attach this policy to your role or user instead of
AmazonSESFullAccess
Step 3: Configure Environment Variables¶
If Using IAM Role (Option A) - Recommended ⭐¶
Edit your production .env file (e.g., /srv/aclarknet/.env):
# Enable AWS SES
USE_SES=True
# AWS SES Region (required)
AWS_SES_REGION_NAME=us-east-1
# NO credentials needed! boto3 will use the EC2 instance role
# Leave AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY commented out or empty
# Optional: Configuration set for tracking (leave empty if not using)
AWS_SES_CONFIGURATION_SET=
# Use SES v2 API (recommended)
USE_SES_V2=True
That’s it! The application will automatically use the IAM role attached to your EC2 instance.
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¶
SES Console Dashboard
Go to SES Console
View sending statistics, bounces, and complaints
CloudWatch Metrics
Monitor delivery rates, bounces, complaints
Set up alarms for high bounce rates
Django Logs
Check
/srv/aclarknet/logs/django.logfor 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):
Create a configuration set in SES Console
Set up event destinations (SNS, CloudWatch, Kinesis)
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:
Create SNS topics for bounces and complaints
Configure SES to publish to these topics
Subscribe your application endpoint to handle notifications
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 Roles (Recommended)¶
✅ Use IAM roles when running on AWS infrastructure
✅ Apply least privilege - use minimal IAM policy (see Step 2)
✅ No credentials to manage - automatic rotation
✅ Enable MFA on your AWS account
✅ Monitor CloudTrail logs for suspicious activity
✅ Set up billing alerts to detect unusual usage
For IAM User Credentials¶
⚠️ Never commit credentials to version control
⚠️ Rotate access keys regularly (every 90 days)
⚠️ Use least privilege IAM policies
⚠️ Store in secrets manager (not .env file) for production
⚠️ Enable MFA on your AWS account
⚠️ Monitor CloudTrail logs for suspicious activity
⚠️ 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 Role (Recommended on EC2)¶
USE_SES=True
AWS_SES_REGION_NAME=us-east-1
# No credentials needed!
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/