Check the sandbox first, because it decides whether this works at all. A new SES account starts in the sandbox in every Region, and while it's there SES delivers only to verified addresses, verified domains and its own mailbox simulator. On top of that come 200 messages per 24 hours and one per second.
Verifying an Email Spam Tester address won't get you around it. SES verifies a recipient by sending it an email with a link, and since our addresses accept exactly one message before closing, the verification email would be the one that uses it up. So in practice you need production access in the Region you send from. AWS promises an initial response to that request within 96 hours.
Because sandbox status is per Region, an account that's in production in eu-west-1 can still be sandboxed in us-east-1, and identities are per Region as well, each with its own DKIM records.
Get an address, send one message
Open email-spam-tester.com and copy the address it shows, which needs no account or API key and looks like test- plus twenty characters at t.email-spam-tester.com. You have an hour and one message, so build the send command before you fetch the address.
The smallest send is the CLI, adapted from AWS's own example on the SES tenants page with the tenant and configuration-set flags dropped:
aws sesv2 send-email \
--from-email-address "sender@yourdomain.com" \
--destination "ToAddresses=test-xxxxxxxxxxxxxxxxxxxx@t.email-spam-tester.com" \
--content "Simple={Subject={Data='Test Subject',Charset=utf-8},Body={Text={Data='Test email body',Charset=utf-8}}}"
The From has to be a verified identity, even in production. A text-only body like this is enough to test authentication and the IP but tells you almost nothing about content. For a real verdict, send the message your application really sends, with its HTML part and its headers. Use the same code path too, whether that's boto3's send_email, an SMTP library or something else. If production sends use a configuration set, add --configuration-set-name here as well, so the test runs through the same settings.
The inbox test plugs into the same command. Its provider inboxes go into ToAddresses beside the test address and its marker into the Subject data. One call then fills two reports, the second listing the folder the message landed in at Gmail, Outlook, Yahoo and other providers. Those inboxes aren't verified either, so this too needs production access.
There's also a console route. Open Configuration > Identities, click the identity and choose Send test email, picking Raw if you want to paste a full MIME message with your own headers. AWS describes the console as a tool you typically only use for test emails, and the docs describe no test prefix or other change to the message. In the sandbox, the Custom recipient field needs a verified address, which brings you back to the problem above.
What a fresh SES identity looks like in the report
From the report · Authentication
- SPFpassnot alignedThe MAIL FROM is a subdomain of amazonses.com, not of your domain.
- DKIMpassalignedEasy DKIM signs every message with a 2048-bit key on your domain.
- DMARCpassPasses on the aligned DKIM signature, once you publish a DMARC record.
A default SES identity: SPF passes, but for amazonses.com. A custom MAIL FROM is what lines it up.
DKIM. Easy DKIM is the default for domain identities and signs every message with a 2048-bit key. The setup is three CNAME records named token._domainkey.yourdomain.com that point to token.dkim.<region>.amazonses.com. If the DKIM check fails, look at two things first: whether DNS has caught up (AWS allows up to 72 hours) and whether your DNS host appended the domain to the name and created token._domainkey.yourdomain.com.yourdomain.com. With BYODKIM you publish one TXT record at your own selector instead. Mid-switch between the two, SES may send without a signature at all, which the report flags as missing DKIM. The DKIM checker takes the token as the selector.
SPF and the return path. By default SES uses a subdomain of amazonses.com as the MAIL FROM. SPF passes, but for amazonses.com, so the report shows SPF as unaligned with your From. When DKIM is aligned this costs little (2 points on the 100-point scale instead of 5), and DMARC still passes on DKIM. To align SPF as well, set up a custom MAIL FROM subdomain with exactly one MX record, 10 feedback-smtp.<region>.amazonses.com, and a TXT record v=spf1 include:amazonses.com ~all. Don't pick a subdomain that receives mail, and keep aspf=s out of your DMARC record, because AWS says SPF alignment with SES doesn't work under a strict SPF policy.
DMARC. SES doesn't publish one for you. AWS's sample is v=DMARC1;p=quarantine;rua=mailto:my_dmarc_report@example.com. If nobody reads your DMARC reports yet, I'd start at p=none and tighten later. The DMARC checker shows what you have, and DMARC fail covers the common breakages.
Example values. Copy the real ones from your account.
| Name | Type | Value | What it is for | Copy |
|---|---|---|---|---|
| token. | CNAME | token. | Easy DKIM. Three of these, one per token. | |
| mail. | MX | 10 feedback-smtp. | Custom MAIL FROM. Exactly one MX record, on a subdomain that receives no mail. | |
| mail. | TXT | v= | SPF for the custom MAIL FROM, so that SPF aligns with your From. | |
| _dmarc. | TXT | v= | DMARC, AWS's sample. Start at p=none if nobody reads the reports yet, and leave out aspf=s. |
List-Unsubscribe. SES adds List-Unsubscribe and List-Unsubscribe-Post only when you send with ListManagementOptions pointing at a contact list and topic. Even then, only for single-recipient messages, only with Easy DKIM, and SES overwrites any unsubscribe headers you set yourself. Everything else goes out with no unsubscribe headers at all, which for a newsletter means a failed List-Unsubscribe check and a failed one-click check unless you add them yourself. Since March 2024 the v2 API accepts custom headers without raw MIME. Adapted from AWS's one-click blog post, the two headers look like this:
"Headers": [
{ "Name": "List-Unsubscribe",
"Value": "<https://yourdomain.com/unsub?address=x&topic=x>, <mailto:unsubscribe@yourdomain.com>" },
{ "Name": "List-Unsubscribe-Post",
"Value": "List-Unsubscribe=One-Click" }
]
Your endpoint then has to handle the POST. Password resets and receipts without list headers are a different case, and for them the report marks the bulk-sender group as skipped. More on the header in our List-Unsubscribe page.
IPs and blocklists
New accounts send from IPs shared with other SES users, and AWS recommends staying on them unless your volume is large and regular. The report queries 21 blocklists for the sending IP, and on shared addresses a hit belongs to the pool. Dedicated IPs come as Standard, which you warm up by hand, or Managed, where SES handles the warm-up, and there a listing is tied to your own sending. Any IP you find in the Headers tab can go straight into the blocklist checker.
AWS watches your account too. Its enforcement FAQ says to test against real addresses that can receive mail, or the simulator, so made-up recipients don't drive up bounces, and it puts an account under review at a 5% bounce rate or a 0.1% complaint rate.
The fix plan at the bottom lists up to eight changes, each with the DNS record to copy where one applies, and the points beside each fix are the current penalties of the checks it touches. The model picks and explains fixes. It doesn't price them. If a check name means nothing to you, the docs explain each of the 42.
AWS references
Sandbox and production accessdocs.aws.amazon.com ↗Easy DKIMdocs.aws.amazon.com ↗Custom MAIL FROMdocs.aws.amazon.com ↗DMARC with SESdocs.aws.amazon.com ↗Subscription managementdocs.aws.amazon.com ↗sesv2 send-emaildocs.aws.amazon.com ↗Questions
Can I test from the SES sandbox?
Not with our address. In the sandbox SES only delivers to verified recipients, and verifying our address would use up the one message it accepts. Request production access for the Region, and meanwhile check your records with the free DKIM, SPF and DMARC checkers.
Does the SES mailbox simulator give the same information?
No. It tells you how SES handles a bounce, a complaint or a delivery, and it's billed like normal sending. It doesn't analyse your headers, authentication or content.
Should I put SES in my root domain's SPF record?
Receivers check SPF against the MAIL FROM domain, which is a subdomain of amazonses.com by default or your custom MAIL FROM subdomain. The SPF record for SES belongs on that subdomain, and as far as the MAIL FROM page goes, nothing is required on the root.