This tool automates the complete setup of Amazon SES (Simple Email Service) for a domain, including domain verification, DKIM configuration, DNS record creation in CloudFlare, and IAM user provisioning for secure email sending.
This was mostly developed by an AI beause I'm lazy. Use at your own risk.
I developed this because I wanted an easy way to deploy WordPress sites using SES as a transactional email service. The outputs provide a copy/pasta sample for updated the WordPress config for FluentSMTP.
The provision_ses_domain.py script performs the following automated tasks:
- SES Domain Verification: Verifies your domain with Amazon SES
- DKIM Configuration: Sets up DomainKeys Identified Mail for email authentication
- DNS Record Creation: Automatically creates all required DNS records in CloudFlare:
- SES verification TXT record
- DKIM CNAME records
- MX record for MAIL FROM domain (optional for outgoing-only email)
- SPF record for email authentication
- DMARC record for email policy
- IAM User Creation: Creates a dedicated IAM user with minimal permissions for sending emails
- SMTP Credentials: Generates secure SMTP credentials for your applications
- Feedback Email Configuration: Optionally configures SES feedback forwarding for bounces and complaints
- Comprehensive Logging: Creates detailed logs of all operations
Before running this tool, you need:
- A domain registered and managed through CloudFlare
- An AWS account with appropriate permissions
- Python 3.6+ installed
- Required Python packages (see Installation section)
- Clone or download this repository
- Install Python dependencies:
pip install -r requirements.txt
You need an AWS user with permissions to manage SES, IAM, and STS. This policy is for the user/role executing the script, not the one being created. It needs broad permissions.
Go to the IAM console in AWS -> Policies -> Create Policy. Use the JSON editor and paste the following policy.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "SESManagement",
"Effect": "Allow",
"Action": [
"ses:VerifyDomainIdentity",
"ses:VerifyDomainDkim",
"ses:SetIdentityMailFromDomain",
"ses:GetIdentityVerificationAttributes"
],
"Resource": "*"
},
{
"Sid": "IAMUserAndPolicyManagementForSES",
"Effect": "Allow",
"Action": [
"iam:CreateUser",
"iam:CreatePolicy",
"iam:AttachUserPolicy",
"iam:CreateAccessKey",
"iam:GetUser",
"iam:GetPolicy",
"iam:DetachUserPolicy",
"iam:ListAccessKeys",
"iam:DeleteAccessKey",
"iam:DeleteUser",
"iam:ListPolicyVersions",
"iam:DeletePolicyVersion",
"iam:DeletePolicy"
],
"Resource": "*"
},
{
"Sid": "STSPermissions",
"Effect": "Allow",
"Action": "sts:GetCallerIdentity",
"Resource": "*"
}
]
}Attach this policy to the IAM user or role that you will use to run the script. Generate and configure the Access Key and Secret for this script-running user.
Note: The IAM permissions include both creation and deletion operations. If you only need to provision domains (not remove them), you can remove the deletion-related permissions:
iam:DetachUserPolicyiam:ListAccessKeysiam:DeleteAccessKeyiam:DeleteUseriam:ListPolicyVersionsiam:DeletePolicyVersioniam:DeletePolicy
However, if you want to use the --remove-aws-resources flag, all permissions listed above are required. The --regenerate-keys flag needs iam:GetUser, iam:ListAccessKeys, iam:CreateAccessKey and iam:DeleteAccessKey.
- Log into CloudFlare Dashboard
- Navigate to API Tokens: My Profile → API Tokens
- Create Custom Token with the following permissions:
- Zone Permissions: Zone → Zone:Read, DNS:Edit
- Zone Resources: Include → All zones
- Account Resources: Include → All accounts
- Account Permissions: Account → Account:Read
- Set token name:
ses-provisioning-token(or your preferred name) - Create token and save the token value
Create a config.ini file in the same directory as the script (There is a sample available config.bak):
[AWS]
access_key_id = YOUR_AWS_ACCESS_KEY_ID
secret_access_key = YOUR_AWS_SECRET_ACCESS_KEY
region = us-east-1
[Cloudflare]
api_token = YOUR_CLOUDFLARE_API_TOKENImportant: Replace the placeholder values with your actual credentials.
Note: If you use the --no-cloudflare flag, you can omit the [Cloudflare] section from your config file.
python provision_ses_domain.py yourdomain.com# Use custom configuration file
python provision_ses_domain.py yourdomain.com --config my-config.ini
# Use custom MAIL FROM subdomain
python provision_ses_domain.py yourdomain.com --mail-from-subdomain email
# Skip CloudFlare integration (manual DNS setup required)
python provision_ses_domain.py yourdomain.com --no-cloudflare
# Enable SES feedback email forwarding for bounces and complaints
python provision_ses_domain.py yourdomain.com --enable-feedback
# Remove all SES-related DNS records from CloudFlare
python provision_ses_domain.py yourdomain.com --remove-cloudflare-records
# Remove IAM user and policy for this domain
python provision_ses_domain.py yourdomain.com --remove-aws-resources
# Replace the access key for an already-provisioned domain
python provision_ses_domain.py yourdomain.com --regenerate-keysIf you don't use CloudFlare for DNS management, you can use the --no-cloudflare flag to skip CloudFlare integration:
python provision_ses_domain.py yourdomain.com --no-cloudflareWhen using this option:
- The script will still configure SES and create the IAM user
- You'll need to manually create the DNS records in your DNS provider
- The script will output the exact DNS records you need to create
- No CloudFlare API token is required in your config file
By default, SES feedback email forwarding is disabled. This means you won't receive notifications for bounces and complaints.
To enable feedback email forwarding, use the --enable-feedback flag:
python provision_ses_domain.py yourdomain.com --enable-feedbackWhat feedback emails include:
- Bounce notifications: When emails cannot be delivered
- Complaint notifications: When recipients mark emails as spam
- Delivery notifications: When emails are successfully delivered
Important considerations:
- Feedback emails are sent to the address specified in the
Return-Pathheader of your emails - Ensure you monitor the feedback email address to handle bounces and complaints
- High bounce and complaint rates can affect your sending reputation
- You can combine this with other flags:
--enable-feedback --no-cloudflare
To remove all SES-related DNS records from CloudFlare for a domain, use the --remove-cloudflare-records flag:
python provision_ses_domain.py yourdomain.com --remove-cloudflare-recordsWhat gets removed:
- SES domain verification TXT record (
_amazonses.{domain}) - All DKIM CNAME records (
{token}._domainkey.{domain}) - MAIL FROM MX record
- MAIL FROM SPF TXT record
- DMARC TXT record (
_dmarc.{domain})
Important notes:
- This operation only removes DNS records from CloudFlare
- It does NOT remove the SES identity or IAM user from AWS
- You must have CloudFlare configured (cannot use with
--no-cloudflare) - This is useful when reprovisioning a domain or cleaning up after decommissioning
To remove the IAM user and policy created for a domain, use the --remove-aws-resources flag:
python provision_ses_domain.py yourdomain.com --remove-aws-resourcesWhat gets removed:
- IAM user (
ses-sender-{domain}) - All access keys associated with the user
- IAM policy (
ses-send-only-{domain}) - Policy attachments from the user
Important notes:
- Required Permissions: Your AWS user must have the following IAM permissions to use this feature:
iam:DetachUserPolicyiam:ListAccessKeysiam:DeleteAccessKeyiam:DeleteUseriam:ListPolicyVersionsiam:DeletePolicyVersioniam:DeletePolicy
- This operation only removes IAM resources (user and policy)
- It does NOT remove the SES identity or DNS records
- The policy will only be deleted if it's not attached to other entities
- Access keys are deleted before the user is removed
- This is useful when decommissioning a domain or cleaning up resources
If you've lost the credentials for a domain, or want to rotate them, use the --regenerate-keys flag:
python provision_ses_domain.py yourdomain.com --regenerate-keysWhat it does:
- Checks that the IAM user
ses-sender-{domain}exists (provision the domain first if it doesn't) - Creates a new access key and prints it in the same format as a normal run (AWS keys, SMTP details, FluentMail
define()lines) - Deletes the user's old access key(s). IAM allows two keys per user, so if two already exist, the oldest is deleted before the new one is created
Important notes:
- The old key stops working immediately, so update your site's config straight away
- SES and Cloudflare are not touched, and the
[Cloudflare]config section is not needed - Cannot be combined with
--remove-aws-resourcesor--remove-cloudflare-records - Required Permissions:
iam:GetUser,iam:ListAccessKeys,iam:CreateAccessKey,iam:DeleteAccessKey
Note: You can combine cleanup flags:
# Remove both CloudFlare DNS records and AWS IAM resources
python provision_ses_domain.py yourdomain.com --remove-cloudflare-records --remove-aws-resourcespython provision_ses_domain.py example.comThis will:
- Verify
example.comwith SES - Create MAIL FROM domain
mail.example.com - Set up all DNS records in CloudFlare
- Create IAM user
ses-sender-example-com - Generate SMTP credentials
The script will:
- Create a detailed log file with timestamp:
ses-provision-{domain}-{timestamp}.log - Display credentials for your applications:
- AWS Access Keys for programmatic access
- SMTP credentials for email sending
- WordPress FluentMail configuration
--- ✅ Provisioning Complete! ✅ ---
AWS ACCESS KEYS (for programmatic access):
AWS_ACCESS_KEY_ID: AKIAIOSFODNN7EXAMPLE
AWS_SECRET_ACCESS_KEY: wJalrXUtnFEMIK7MDENGbPxRfiCYEXAMPLEKEY
AWS_REGION: us-east-1
SES SMTP CREDENTIALS (for email sending):
SES SMTP Endpoint: smtp.mail.us-east-1.amazonaws.com
SMTP Username: AKIAIOSFODNN7EXAMPLE
SMTP Password: wJalrXUtnFEMIK7MDENGbPxRfiCYEXAMPLEKEY
Fluentmail credentials (paste into WordPress Config):
define('FLUENTMAIL_AWS_ACCESS_KEY_ID', 'AKIAIOSFODNN7EXAMPLE');
define('FLUENTMAIL_AWS_SECRET_ACCESS_KEY', 'wJalrXUtnFEMIK7MDENGbPxRfiCYEXAMPLEKEY');
The script creates the following DNS records in CloudFlare:
-
SES Verification (TXT) - Required
- Name:
_amazonses.{domain} - Purpose: Verifies domain ownership with AWS SES
- Name:
-
DKIM Signing (CNAME) - Required (3 records)
- Name:
{token}._domainkey.{domain} - Purpose: Enables DKIM email authentication
- Name:
-
MAIL FROM (MX) - Optional
- Name:
{mail-from-subdomain}.{domain}(default:mail.{domain}) - Purpose: Handles bounce/complaint notifications
- Can be skipped with
--skip-mxfor outgoing-only transactional email
- Name:
-
SPF (TXT) - Required
- Name:
{mail-from-subdomain}.{domain} - Purpose: Authorizes AWS SES to send email on your behalf
- Name:
-
DMARC (TXT) - Recommended
- Name:
_dmarc.{domain} - Purpose: Email authentication policy (defaults to 'none' policy)
- Name:
- DNS records typically propagate within 5-30 minutes
- You can check propagation using tools like
digor online DNS checkers
- AWS will automatically detect the DNS records
- Check the SES Console → Identities to see verification status
- Domain should show as "Verified" once DNS propagates
- Your domain will be in "Sandbox Mode" initially
- To send to any email address, request production access in SES Console
- In sandbox mode, you can only send to verified email addresses
The tool creates a dedicated IAM user with minimal permissions:
- Can only send emails via SES for the specific domain
- Cannot access other AWS services
- Cannot modify SES configuration
The script creates an IAM policy with the following permissions for the new user:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ses:SendRawEmail",
"ses:SendEmail"
],
"Resource": "arn:aws:ses:{region}:{account-id}:identity/{domain}"
},
{
"Effect": "Allow",
"Action": [
"ses:ListIdentities",
"ses:GetIdentityVerificationAttributes",
"ses:GetIdentityDkimAttributes",
"ses:SetIdentityFeedbackForwardingEnabled"
],
"Resource": "*"
}
]
}What these permissions allow:
ses:SendEmailandses:SendRawEmail: Send emails through SES for the specific domainses:ListIdentities: List SES identities (needed for some email libraries)ses:GetIdentityVerificationAttributes: Check verification status of identitiesses:GetIdentityDkimAttributes: Retrieve DKIM configuration detailsses:SetIdentityFeedbackForwardingEnabled: Configure feedback email forwarding for bounces and complaints
Security features:
- Permissions are scoped to the specific domain only
- No administrative access to SES or other AWS services
- Cannot modify SES configuration or create new identities
- Cannot access other AWS resources outside of SES
- The API token has minimal required permissions
- Only can read zones and edit DNS records
- Cannot access other CloudFlare features
- Store credentials securely
- Never commit
config.inito version control - Consider using AWS Secrets Manager for production
-
"Domain not found in CloudFlare"
- Ensure the domain is added to your CloudFlare account
- Verify the domain name is spelled correctly
-
"Access Denied" errors
- Check that your AWS user has the required permissions
- Verify your CloudFlare API token has DNS edit permissions
-
DNS records not appearing
- Check CloudFlare API token permissions
- Verify the domain is active in CloudFlare
-
SES verification fails
- Wait for DNS propagation (5-30 minutes)
- Check that all DNS records were created correctly
-
MX record not needed
- For outgoing-only transactional email, use
--skip-mxto skip MX record creation - The MX record is only needed for receiving bounce/complaint notifications at the MAIL FROM subdomain
- For outgoing-only transactional email, use
- Check the generated log file for detailed error information
- Log files are named:
ses-provision-{domain}-{timestamp}.log
- Log file:
ses-provision-{domain}-{timestamp}.log - IAM User:
ses-sender-{domain} - IAM Policy:
ses-send-only-{domain} - DNS Records: Various records in CloudFlare
For issues or questions:
- Check the log file for detailed error messages
- Verify all prerequisites are met
- Ensure credentials have correct permissions
Contributions are welcome! However, please note that I have limited time available, so I may not be able to respond to issues or pull requests promptly.
- Fork the repository
- Create a feature branch:
git checkout -b feature/amazing-feature - Make your changes and test thoroughly
- Commit your changes:
git commit -m 'Add some amazing feature' - Push to the branch:
git push origin feature/amazing-feature - Open a Pull Request
- Please test your changes before submitting
- Update documentation if needed
- Keep changes focused and well-documented
- Follow existing code style and patterns
While I appreciate bug reports and feature requests, I may not have time to respond to all issues. Feel free to:
- Fork the project and fix issues yourself
- Submit pull requests with fixes
- Discuss solutions in the issues
This project is licensed under the MIT License - see the LICENSE file for details.
The MIT License is one of the most permissive open source licenses, allowing you to:
- Use the code for any purpose (commercial or non-commercial)
- Modify the code as you see fit
- Distribute the code
- Use it privately
- Sublicense it
The only requirement is that you include the original copyright notice and license text when redistributing.
