Amazon SES (Simple Email Service)¶
1. What is Amazon SES?¶
Amazon SES is a cloud-based email sending and receiving service — it lets your applications send transactional emails, marketing emails, and bulk campaigns at scale, and optionally receive inbound email.
Email categories SES handles:
Transactional: order confirmations, password resets, receipts, alerts
Marketing: newsletters, promotions, announcements
Bulk: large-scale campaigns to opt-in mailing lists
Notification: system alerts, CloudWatch alarms via email
SES does NOT:
Provide a GUI email client (that's an MUA — use Gmail, Outlook)
Replace an Exchange server (that's Microsoft 365 / WorkMail)
Send SMS (that's SNS)
Why SES Instead of SMTP Server¶
Self-managed SMTP server:
Maintain server 24/7 → patches, uptime, scaling
Build/maintain IP reputation from scratch
Handle bounces, complaints, blacklists manually
Complex DKIM/SPF/DMARC setup
Amazon SES:
Fully managed — no servers to maintain
AWS-managed IP reputation (warm IPs with established reputation)
Built-in bounce/complaint handling + suppression lists
Easy DKIM with automatic key rotation every 90 days [captaindns](https://www.captaindns.com/en/blog/amazon-ses-transactional-email-technical-guide)
Cost: $0.10 per 1,000 emails
2. Identities (Verification) ⭐¶
Before sending, you must verify the sending identity — SES confirms you own the domain or email address:
Email Address Identity¶
Verify a single email address:
SES → Verified Identities → Create → Email address
→ SES sends verification link to that email
→ Click link → verified ✅
Limitation: can only send FROM this exact email address
Use case: quick testing, single-sender applications
Domain Identity (Recommended for Production)¶
Verify an entire domain:
SES → Verified Identities → Create → Domain
→ SES provides DNS records to add to your domain (Route 53 or other)
→ DNS propagates → domain verified ✅
Benefit: send from ANY address at that domain
noreply@ibtisam-iq.com ✅
support@ibtisam-iq.com ✅
orders@ibtisam-iq.com ✅
DNS Records Added During Domain Verification¶
1. DKIM records (3 CNAME records for Easy DKIM)
Automatically used for both domain verification AND DKIM signing
2. Optional MAIL FROM (custom MX record)
Required for SPF DMARC alignment (see Section 4)
3. DMARC TXT record (you add manually)
Not generated by SES — you write it based on your policy
3. Sandbox vs Production ⭐¶
All new SES accounts start in sandbox mode — a restricted environment to prevent abuse:
Sandbox Restrictions¶
Can only send TO: verified email addresses or domains (you own both sides)
Sending limit: 200 emails/day
Send rate: 1 email/second
Cannot send to: random email addresses (would get AccessDenied error)
Why Sandbox Exists¶
New AWS accounts are untrusted — SES cannot know if you will spam.
Sandbox forces you to prove:
→ You own the sending domain (verified identity)
→ You know who you're sending to (verified recipients)
→ You have basic bounce/complaint handling
Moving to Production¶
Submit a request via AWS Support → Service Limit Increase → SES Sending Limits. AWS reviews manually. Your request must demonstrate:
Required in your request:
1. Website / use case description
→ "We send order confirmation emails to paying customers"
2. Email type
→ Transactional / Marketing / Notification
3. Opt-in mechanism
→ "Users check a box at registration confirming they want emails"
→ Double opt-in preferred (confirm email address via link)
4. Bounce handling
→ "We have SNS + Lambda processing bounces in real-time
and suppressing bounced addresses from future sends"
5. Complaint handling
→ "We process SES complaint notifications and unsubscribe
complainants immediately"
6. Sending volume estimate
→ Daily and peak volumes
7. Unsubscribe mechanism (for marketing)
→ One-click unsubscribe link in every email
Best practices to guarantee approval:
→ Verify domain (not just email address)
→ Configure bounce + complaint notifications (SNS)
→ Enable account-level suppression list [docs.aws.amazon](https://docs.aws.amazon.com/ses/latest/APIReference-V2/API_PutConfigurationSetSuppressionOptions.html)
→ Set up DKIM (Easy DKIM)
→ Have a real application/website to show
4. Email Authentication: SPF, DKIM, DMARC ⭐¶
These three protocols work together to prove your emails are legitimate:
SPF (Sender Policy Framework)¶
What: DNS TXT record listing which mail servers may send on behalf of your domain
How: Receiving mail server checks MAIL FROM domain → looks up SPF record
→ is the sending IP in the allowed list? YES → SPF pass
DNS TXT record:
Name: mail.ibtisam-iq.com ← your custom MAIL FROM subdomain
Value: "v=spf1 include:amazonses.com ~all"
v=spf1 → SPF version
include:amazonses.com → SES IP ranges are authorized
~all → soft fail (mark suspicious, don't reject)
-all → hard fail (reject outright — use after testing)
Critical: SPF checks MAIL FROM domain (Return-Path / envelope sender)
NOT the From: address you see in your email client
→ Must use custom MAIL FROM for SPF DMARC alignment [easydmarc](https://easydmarc.com/blog/amazon-ses-spf-and-dkim-configuration/)
DKIM (DomainKeys Identified Mail)¶
What: cryptographic signature on outgoing emails proving content wasn't modified
How:
SES (sender): signs email with private key
Receiving server: retrieves public key from DNS → verifies signature
→ Signature valid? Content intact? → DKIM pass
Easy DKIM (recommended): [docs.aws.amazon](https://docs.aws.amazon.com/ses/latest/dg/send-email-authentication-dkim.html)
SES generates 2048-bit RSA key pair automatically
Provides 3 CNAME records to add to your DNS:
abc123._domainkey.ibtisam-iq.com → CNAME → abc123.dkim.amazonses.com
def456._domainkey.ibtisam-iq.com → CNAME → def456.dkim.amazonses.com
ghi789._domainkey.ibtisam-iq.com → CNAME → ghi789.dkim.amazonses.com
→ AWS rotates keys automatically every 90 days ← no manual maintenance [captaindns](https://www.captaindns.com/en/blog/amazon-ses-transactional-email-technical-guide)
→ These 3 CNAMEs also serve as domain verification records
BYODKIM (Bring Your Own DKIM):
You generate 1024–2048 bit RSA key pair
Add TXT record with public key to DNS
Upload private key to SES
You are responsible for rotation
DMARC (Domain-based Message Authentication, Reporting & Conformance)¶
What: policy that tells receiving servers what to do when SPF or DKIM fail
AND ensures the From: address domain matches the authenticated domain
DMARC requires ALIGNMENT:
SPF alignment: MAIL FROM domain must match From: domain
DKIM alignment: d= in DKIM signature must match From: domain
At least one must align and pass → DMARC pass
DMARC DNS TXT record:
Name: _dmarc.ibtisam-iq.com
Value: "v=DMARC1; p=quarantine; rua=mailto:dmarc@ibtisam-iq.com; pct=100"
v=DMARC1 → DMARC version
p=none → policy: monitor only (start here — no rejection)
p=quarantine → send to spam folder if DMARC fails
p=reject → reject email entirely if DMARC fails (most strict)
rua=mailto:... → aggregate reports sent here (daily digest)
ruf=mailto:... → forensic reports (individual failures)
pct=100 → apply policy to 100% of email
Rollout strategy:
Phase 1: p=none → collect reports, find legitimate sources
Phase 2: p=quarantine → test with pct=10, increase gradually
Phase 3: p=reject → full protection against spoofing
The Three Work Together¶
Receiving server checks email from ibtisam@ibtisam-iq.com:
SPF check:
Looks at MAIL FROM (Return-Path): mail.ibtisam-iq.com
→ DNS TXT lookup: mail.ibtisam-iq.com SPF record
→ SES IP in allowed list? → SPF PASS ✅
→ mail.ibtisam-iq.com matches ibtisam-iq.com? → SPF ALIGNMENT ✅
DKIM check:
Finds DKIM-Signature header in email
→ d=ibtisam-iq.com in signature
→ DNS CNAME lookup: public key
→ Verifies signature → DKIM PASS ✅
→ d=ibtisam-iq.com matches From: ibtisam-iq.com? → DKIM ALIGNMENT ✅
DMARC check:
Looks up _dmarc.ibtisam-iq.com
→ SPF aligned + passed OR DKIM aligned + passed → DMARC PASS ✅
→ p=reject but DMARC passed → deliver normally ✅
If DMARC FAILS (someone spoofing your domain):
p=none: deliver, send report to rua address
p=quarantine: move to spam, send report
p=reject: refuse delivery, send report
5. Custom MAIL FROM Domain ⭐¶
By default, SES uses amazonses.com as the MAIL FROM (Return-Path) domain. This breaks SPF DMARC alignment because amazonses.com ≠ ibtisam-iq.com.
Default (no custom MAIL FROM):
From: ibtisam@ibtisam-iq.com ← what user sees
Return-Path: bounce@amazonses.com ← SPF checks THIS domain
SPF passes for amazonses.com but fails alignment with ibtisam-iq.com
→ DMARC FAILS for SPF (only DKIM can save it)
With custom MAIL FROM (mail.ibtisam-iq.com):
From: ibtisam@ibtisam-iq.com
Return-Path: bounce@mail.ibtisam-iq.com ← SPF checks THIS domain
→ mail.ibtisam-iq.com is subdomain of ibtisam-iq.com → ALIGNMENT ✅
→ DMARC passes via SPF ✅
DNS records to add:
MX: mail.ibtisam-iq.com 10 feedback-smtp.us-east-1.amazonses.com
TXT: mail.ibtisam-iq.com "v=spf1 include:amazonses.com ~all"
Custom MAIL FROM is strongly recommended — it enables both SPF and DKIM to contribute to DMARC alignment, giving you redundant protection.
6. Sending Email — Methods¶
SMTP Interface¶
Use any application that supports SMTP:
Host: email-smtp.us-east-1.amazonaws.com
Port: 587 (STARTTLS) or 465 (SSL) or 25
Auth: SMTP credentials (separate from IAM — generate in SES console)
Username: SMTP username (starts with AKIA...)
Password: SMTP password (different from access key secret)
Use case:
Existing apps that use SMTP (WordPress, Jira, Jenkins notifications)
CMS systems with built-in SMTP configuration
SES API v2 (Recommended for Applications)¶
import boto3
ses = boto3.client('sesv2', region_name='us-east-1')
response = ses.send_email(
FromEmailAddress='noreply@ibtisam-iq.com',
Destination={
'ToAddresses': ['customer@example.com'],
'CcAddresses': [],
'BccAddresses': []
},
Content={
'Simple': {
'Subject': {'Data': 'Your order has shipped', 'Charset': 'UTF-8'},
'Body': {
'Text': {'Data': 'Your order #1234 has shipped.', 'Charset': 'UTF-8'},
'Html': {
'Data': '<h1>Order Shipped</h1><p>Your order #1234 is on its way.</p>',
'Charset': 'UTF-8'
}
}
}
},
ConfigurationSetName='production-tracking' # ← always use a configuration set
)
Sending from Lambda (Common Pattern)¶
# Lambda → SES → customer email
# IAM role must have: ses:SendEmail or ses:SendRawEmail permission
import boto3, os
def handler(event, context):
ses = boto3.client('sesv2')
ses.send_email(
FromEmailAddress=os.environ['FROM_EMAIL'],
Destination={'ToAddresses': [event['customerEmail']]},
Content={
'Simple': {
'Subject': {'Data': f"Order #{event['orderId']} confirmed"},
'Body': {'Html': {'Data': build_html_email(event)}}
}
},
ConfigurationSetName='prod-config-set'
)
7. Configuration Sets ⭐¶
A configuration set is a named group of rules applied to emails sent using that configuration set. Always attach one to every email send.
Configuration Set: "production-tracking"
Components:
Event destinations → where to send email events (see below)
Reputation options → track bounce/complaint rates
Sending options → enable/disable sending for this config set
Suppression options → override account-level suppression (see Section 8)
TLS policy → REQUIRE TLS or OPTIONAL
Virtual Deliverability Manager → ML deliverability insights
Event Destinations¶
Track what happens after you send:
| Event Type | Meaning |
|---|---|
SEND | SES accepted the email for delivery |
RENDERING_FAILURE | Template rendering failed |
REJECT | SES rejected before sending (virus, policy) |
DELIVERY | Email delivered to recipient's mail server |
BOUNCE | Mail server returned email undeliverable |
COMPLAINT | Recipient marked email as spam |
OPEN | Recipient opened the email (pixel tracking) |
CLICK | Recipient clicked a link |
SUBSCRIPTION | Subscription management event |
Route events to:
SNS → real-time processing (Lambda → update DB, suppress address)
Kinesis Firehose → stream to S3/analytics
CloudWatch → metrics and alarms on delivery rates
EventBridge → route to any downstream service
Essential configuration: [oneuptime](https://oneuptime.com/blog/post/2026-02-12-move-amazon-ses-out-of-sandbox/view)
aws sesv2 create-configuration-set-event-destination \
--configuration-set-name "production-tracking" \
--event-destination-name "bounces-and-complaints" \
--event-destination '{
"Enabled": true,
"MatchingEventTypes": ["BOUNCE", "COMPLAINT"],
"SnsDestination": {
"TopicArn": "arn:aws:sns:us-east-1:123456789012:ses-bounces"
}
}'
8. Bounces and Complaints ⭐¶
Email deliverability and sender reputation depend critically on keeping bounce and complaint rates low.
Bounce Types¶
Hard bounce (permanent failure):
Address does not exist: user@nonexistentdomain.com
Address permanently rejected: mailbox closed, blocked sender
→ NEVER send to this address again
→ Sending repeatedly → reputation destroyed → SES may suspend account
Soft bounce (temporary failure):
Mailbox full, server temporarily unavailable
→ SES retries automatically for up to 72 hours
→ After exhausting retries → treated like hard bounce
Complaint¶
Recipient clicks "Mark as Spam" in Gmail/Outlook
→ ISP sends complaint notification to SES via Feedback Loop
→ SES triggers COMPLAINT event
→ You must stop sending to this person immediately
→ Continuing to send to complainants = major reputation damage
Reputation Thresholds¶
Bounce rate:
< 2% → Healthy
2–5% → Warning — AWS may pause sending
> 5% → Account sending suspended
Complaint rate:
< 0.08% → Healthy (Gmail threshold: below 0.1%)
0.08–0.1% → Warning
> 0.1% → Risk of suspension, deliverability severely impacted
These metrics in CloudWatch: AWS/SES namespace
Reputation.BounceRate
Reputation.ComplaintRate
→ Set alarms on both at warning thresholds
Suppression List ⭐¶
Account-level suppression list:
Automatically adds addresses that result in hard bounce or complaint
SES will not attempt delivery to suppressed addresses
Suppressed addresses NOT counted toward bounce/complaint rate metrics
Enable:
aws sesv2 put-account-suppression-attributes \
--suppressed-reasons BOUNCE COMPLAINT
Configuration-set-level suppression override: [docs.aws.amazon](https://docs.aws.amazon.com/ses/latest/dg/sending-email-suppression-list-config-level.html)
You can override account-level suppression per configuration set
Example:
Account: suppress BOUNCE + COMPLAINT
Config set "transactional": suppress BOUNCE only
→ Transactional emails ignore complaint suppression
(transactional emails like password resets are expected)
Manually add/remove addresses:
Add: aws sesv2 put-suppressed-destination --email-address bad@example.com --reason BOUNCE
Remove: aws sesv2 delete-suppressed-destination --email-address recover@example.com
List: aws sesv2 list-suppressed-destinations
Global AWS suppression list:
AWS maintains a list of addresses that bounced across ALL SES customers
Addresses on this global list → SES suppresses even if not on your list
Cannot override global suppression
9. Receiving Email¶
SES can receive inbound email for your verified domain:
Setup:
1. Add MX record: ibtisam-iq.com MX 10 inbound-smtp.us-east-1.amazonaws.com
2. Create receipt rules defining what to do with incoming email
Receipt rule actions:
S3: save raw email to S3 bucket
Lambda: invoke function with email content (process, parse, respond)
SNS: publish notification
SES: forward to another email address (bounce if unverified)
WorkMail: deliver to Amazon WorkMail
Stop: stop processing further rules
Receipt rule example:
Rule: emails to support@ibtisam-iq.com
→ S3: save to my-email-bucket/support/
→ Lambda: parse email → create Jira ticket
→ SNS: notify on-call engineer
Use cases:
Auto-reply system
Email-to-ticket integration
Email-based workflow triggers
Catch-all inbox with Lambda processing
10. Dedicated IP Addresses¶
By default SES uses shared IPs — IPs shared with other SES customers:
Shared IPs:
AWS manages reputation
Good for low-to-medium volume
Cheaper (no extra cost)
Reputation affected by other customers' behavior (rare but possible)
Dedicated IPs:
IP assigned exclusively to your account
You own the reputation → send consistently to build it
Required for: high volume, strict deliverability requirements
Cost: ~$24.95/month per dedicated IP (plus usage)
Warm-up: must gradually increase volume over weeks to build reputation
Dedicated IP Pools:
Group dedicated IPs → use different pools for different email types
Pool "transactional": high-reputation IPs for password resets
Pool "marketing": separate IPs for campaigns (isolate reputation)
11. Virtual Deliverability Manager (VDM)¶
ML-powered dashboard showing deliverability health:
Open rates, click rates per ISP (Gmail, Outlook, Yahoo)
Inbox placement rate
Deliverability recommendations
Optimal time-to-send suggestions
Enable per configuration set or account-level
Additional cost per processed email
Use for: large-scale senders needing per-ISP deliverability insights
12. SES Pricing¶
Sending:
$0.10 per 1,000 emails
Free tier: 3,000 emails/month free when sent from EC2 or Lambda
Attachments: $0.12 per GB of attachments sent
Receiving: $0.10 per 1,000 received emails
First 1,000 received/month: FREE
Dedicated IPs: ~$24.95/month per IP
Virtual Deliverability Manager: $0.0015 per message processed
No charge for:
Bounces/complaints to your SNS topics
SES console usage
Sandbox emails within limits
13. SES vs SNS for Email¶
| Amazon SES | Amazon SNS (email) | |
|---|---|---|
| Purpose | Rich email sending (HTML, attachments) | Simple notification delivery |
| HTML support | ✅ Full HTML, CSS inline | ❌ Plain text only |
| Attachments | ✅ | ❌ |
| Bounce/complaint handling | ✅ Built-in | ❌ Manual |
| Unsubscribe management | ✅ | ❌ |
| Deliverability tracking | ✅ Opens, clicks, delivery | ❌ |
| Use case | Transactional, marketing email | Alert notifications, ops emails |
| Authentication | DKIM/SPF/DMARC | ❌ None |
| Cost | $0.10/1,000 | $2.00/100,000 notifications |
Use SES for any email where formatting, deliverability, and tracking matter. Use SNS email only for quick internal alerts where you control all recipient addresses.
14. Common Mistakes¶
| ❌ Wrong | ✅ Correct |
|---|---|
| SPF alone is enough for email authentication | SPF + DKIM + DMARC all needed — DMARC requires alignment which SPF alone can't guarantee |
| Easy DKIM uses one DNS TXT record | Easy DKIM uses 3 CNAME records — all three must be added |
| Default MAIL FROM is fine for production | Default (amazonses.com) breaks SPF DMARC alignment — always set custom MAIL FROM |
Start DMARC with p=reject | Always start with p=none → collect reports → gradually move to reject |
| Sandbox restrictions are just rate limits | Sandbox restricts recipients to verified addresses only — not just rate |
| Retry sending to hard bounced addresses | Hard bounces = permanent — never retry, use suppression list |
| Complaint rate < 1% is fine | Gmail threshold is < 0.1% — 1% will destroy deliverability |
| Suppressed addresses count toward bounce rate | Suppressed addresses are NOT counted toward Reputation.BounceRate |
| SMTP credentials = IAM access keys | SES SMTP credentials are separate from IAM — generated specifically in SES console |
| Configuration sets are optional | Always use configuration sets — without them you cannot track bounces/complaints |
15. Interview Questions Checklist¶
- What is the difference between transactional and marketing email?
- What are the two identity types in SES? Which is recommended for production?
- What is sandbox mode? What are its restrictions?
- What must you demonstrate to get production access?
- Explain SPF — what does it verify? What DNS record does it use?
- Explain DKIM — what does it verify? What is Easy DKIM?
- Explain DMARC — what is alignment? What are the three policy values?
- Why does custom MAIL FROM matter for DMARC alignment?
- What is the difference between a hard bounce and a soft bounce?
- What bounce rate triggers SES account suspension? (> 5%)
- What complaint rate is dangerous for Gmail deliverability? (> 0.1%)
- What is the account-level suppression list?
- Can you override account-level suppression per configuration set?
- What is a configuration set? What are event destinations?
- Name all eight SES event types
- Shared IPs vs dedicated IPs — when to use dedicated?
- SES vs SNS for sending email — when to use which?
- How does SES receive inbound email? What is an MX record change needed?
- DMARC rollout strategy — three phases?
- SMTP credentials in SES — how are they different from IAM credentials?