Email with Amazon SES
What it is
Amazon SES (Simple Email Service) sends the product's emails: one-time sign-in codes, receipts, notices. To make mail arrive in inboxes rather than spam, the sending domain proves it is allowed to send, using three DNS records: SPF (which servers may send), DKIM (a signature on each email) and DMARC (what receivers should do with mail that fails the other two).
Why we do it
- New SES accounts start in a sandbox: they can only send to addresses you have verified, and only about 200 emails a day. Production access must be requested.
- Since 2024, Gmail and Yahoo reject or spam-folder bulk mail without SPF, DKIM and DMARC.
- If too many emails bounce or are marked as spam, AWS pauses sending for the whole account, including sign-in codes.
Before you start
- The domain zone in Route 53 (step 5).
- The sending address agreed with the client, for example
no-reply@example.com. - The main region; SES is configured per region.
How it fits together
Steps
1. Verify the domain with DKIM
SES, Configuration, Identities, Create identity, Domain, example.com.
Keep Easy DKIM with 2048-bit keys and tick Publish DNS records to Route 53. SES
adds three CNAME records. The identity turns Verified within minutes.
2. Set a custom MAIL FROM domain
In the identity, Custom MAIL FROM domain, use mail.example.com. Publish the records
it shows:
| Name | Type | Value |
|---|---|---|
mail.example.com | MX | 10 feedback-smtp.<region>.amazonses.com |
mail.example.com | TXT | "v=spf1 include:amazonses.com ~all" |
3. Publish DMARC
| Name | Type | Value |
|---|---|---|
_dmarc.example.com | TXT | "v=DMARC1; p=none; rua=mailto:dmarc@example.com" |
Start with p=none and read the reports for two weeks. Then move to p=quarantine. If the
client already sends mail from this domain (Google Workspace, Microsoft 365), check their
existing SPF and DMARC records first and merge, do not overwrite.
4. Create a configuration set and handle bounces
- Configuration sets, Create set,
acme-prod. - Add an event destination for Bounce, Complaint and Delivery, to an SNS topic or EventBridge.
- The API subscribes and marks the address as undeliverable, so we never retry it.
- Keep the account-level suppression list on for bounces and complaints.
5. Request production access
SES, Account dashboard, Request production access. Describe: what emails you send (transactional: sign-in codes, receipts), how people sign up, how you handle bounces and complaints (step 4), and expected daily volume. AWS usually answers within a day.
Do this in prod only. Dev and staging stay in the sandbox and send only to verified team addresses, so test mail can never reach a real user.
6. Send from the API
import { SESv2Client, SendEmailCommand } from '@aws-sdk/client-sesv2';
const ses = new SESv2Client({ region: process.env.AWS_REGION });
await ses.send(new SendEmailCommand({
FromEmailAddress: 'Acme <no-reply@example.com>',
Destination: { ToAddresses: [to] },
ConfigurationSetName: process.env.SES_CONFIGURATION_SET,
Content: { Simple: { Subject: { Data: subject }, Body: { Html: { Data: html }, Text: { Data: text } } } },
}));
The task role needs ses:SendEmail on the identity and the configuration set.
Check it worked
- Send a test from dev to your own verified address.
- In the received email, view the original headers:
spf=pass,dkim=pass,dmarc=pass. - Send to
bounce@simulator.amazonses.comand check the bounce event reached the API.
If it goes wrong
| You see | It means |
|---|---|
| "Email address is not verified" | The account is still in the sandbox, or the recipient is not verified in dev |
| Mail arrives in spam | SPF, DKIM or DMARC fails. Check the headers |
| Sending paused by AWS | Bounce rate near 5% or complaints near 0.1%. Clean the list, handle bounces |
| Client's own company mail started failing | Their SPF or DMARC record was overwritten. Merge the records |
Rules
Sign-in codes and receipts are transactional email. Marketing email needs consent, an unsubscribe link and, ideally, a separate configuration set or subdomain, so a marketing complaint spike cannot stop sign-in codes. See Marketing and email.
Never send real email from dev or staging to anyone outside the team.
Done when
- Domain verified with DKIM; custom MAIL FROM with SPF; DMARC published.
- Configuration set with bounce and complaint handling.
- Production access granted in prod; dev and staging stay in the sandbox.
- A test email passes SPF, DKIM and DMARC.