Sending email with SES gives you a reliable, cost-effective way to handle transactional emails at scale. Whether you're confirming user signups, resetting passwords, or notifying customers of order status changes, AWS Simple Email Service (SES) delivers millions of emails for just pennies per thousand. After 90 days of building on AWS, it's time to add a critical piece to your platform: the ability to send emails reliably without breaking the bank.
Why SES matters for production platforms
I've integrated SES into e-commerce, CRM, and SaaS platforms, and the pattern is always the same: you need email working before launch, but you can't afford SendGrid or Mailgun at startup costs. SES starts free (50 emails per day in sandbox, up to 50,000 per month with production access) and scales to millions. More importantly, it integrates deeply with SNS and Lambda, letting you build event-driven email workflows without external dependencies.
The challenge isn't the service itself—it's the operational work around it. Domains need verification, bounce handling must be automated, and templates keep your emails consistent. Get these right, and email becomes a boring utility. Get them wrong, and you'll chase deliverability issues for months.
Verify domains — Verify the sending domain and set up DKIM
Before you send a single email, AWS needs proof you own the domain you're sending from. This isn't optional, and it's not just a formality—domain verification protects the email ecosystem from spoofing.
In the SES console, you'll add a verified identity. For production, always verify a domain (not individual email addresses). AWS generates DNS records you add to your domain registrar:
- MX record (optional, for receiving mail)
- DKIM records (critical for deliverability)
- SPF record (part of your DNS, tells receivers SES can send for your domain)
DKIM is non-negotiable. It cryptographically signs every email with your domain's private key, proving to Gmail, Outlook, and others that the message actually came from you. Without it, delivery rates tank and you'll land in spam folders consistently.
Here's what the verification flow looks like in the AWS CLI:
aws ses verify-domain-identity --domain myapp.com --region us-east-1
# Output includes the tokens you need to add as DNS records
# Then check status:
aws ses get-identity-verification-attributes --identities myapp.comOnce verified, enable DKIM signing automatically. AWS SES does this for you, but you can also enable Easy DKIM in the console. This generates three CNAME records—add all three to your DNS. Wait 24 hours for propagation (sometimes faster), then verify they're live:
aws ses verify-domain-dkim --domain myapp.comReal scenario: I launched a travel booking platform and forgot to add the SPF record. Emails went out, but 40% landed in spam. Support tickets flooded in. The fix took ten minutes, but the reputation damage took two weeks to recover. Don't skip the DNS setup—it's the foundation.
Leave the sandbox — Request production access before launch
Every AWS account starts in the SES sandbox. This is a sandbox for a reason: unlimited sending to verified addresses, but you can't email real users. You'll verify test email addresses in the console, send a few transactional emails, and confirm the integration works. Then you request production access.
The production request goes to AWS Support. You'll describe your use case, confirm you have bounce handling in place (we'll cover this next), and certify you won't send unsolicited bulk email. AWS usually approves within 24 hours, sometimes immediately.
Here's what your request should say:
We're launching a SaaS CRM platform and will send transactional emails only: password resets, invitation links, account notifications, and digest emails. All emails are triggered by user action or user settings. We will monitor bounce and complaint rates using SNS notifications and automatically suppress suppressed addresses. Estimated volume: 5,000 emails per month initially.
Once approved, your sending limit jumps to 50,000 emails per day (you can request higher). But this doesn't mean you should blast emails without discipline. Set up monitoring, establish a sending cadence, and always test with a small cohort first.
Common gotcha: many developers request production access the day before launch, then panic when it takes 24 hours. Request it the week before. Also, production access is per region—if you're sending from multiple regions, request access for each.
Handle bounces — Process bounce and complaint events to protect reputation
This is the unglamorous but critical part: when an email bounces or someone marks you as spam, you need to know immediately and stop sending to that address. ISPs track your bounce and complaint rates. Go above 5% bounces or 0.1% complaints, and your sending reputation collapses. Emails to valid addresses start bouncing. You're now blocklisted.
Set up SNS notifications for bounce and complaint events. Create an SQS queue that subscribes to the SNS topic, then process messages in a Lambda or background job:
aws ses set-identity-notification-topic \
--identity myapp.com \
--notification-type Bounce \
--sns-topic-arn arn:aws:sns:us-east-1:123456789:ses-bouncesThe bounce event payload looks like this:
{
"messageType": "Bounce",
"bounce": {
"bounceType": "Permanent",
"bounceSubType": "General",
"bouncedRecipients": [
{
"emailAddress": "invalid@example.com",
"status": "5.1.1",
"diagnosticCode": "smtp; 550 5.1.1 user unknown"
}
],
"timestamp": "2024-01-15T10:30:00.000Z",
"feedbackId": "..."
}
}
In your Lambda handler, distinguish between permanent and transient bounces. Permanent bounces (bad email address) require suppression—never send to that address again. Transient bounces (mailbox full, server down) can be retried after a delay.
exports.handleBounce = async (event) => {
for (const record of event.Records) {
const message = JSON.parse(record.Sns.Message);
const { bounce } = message;
if (bounce.bounceType === 'Permanent') {
for (const recipient of bounce.bouncedRecipients) {
await suppressAddress(recipient.emailAddress, 'bounce');
}
}
}
};
Complaints are similar but more serious—they mean a user hit "mark as spam." Suppress the address immediately and investigate why they received the email. One or two complaints can tank your reputation.
I built a CRM platform where we didn't suppress complaint addresses properly. Over two months, complaint rate drifted to 0.2%. One day, our sending hit a wall—even emails to engaged users bounced. The cause was obvious in retrospect: we'd been emailing users who complained. We rebuilt the suppression logic that week, re-verified with AWS, and regained full deliverability. The lesson: automate suppression or lose your reputation.
Templates — Use templates for consistent transactional email
Writing HTML email is tedious and error-prone. You'll inevitably have a typo in one version, a different font in another, and links that work in Gmail but break in Outlook. SES templates solve this. Define your email once in SES, pass variables at send time, and every email is consistent.
Create a template in the SES console or via CLI:
aws ses create-template --template Name=WelcomeEmail,SubjectPart="Welcome to MyApp",TextPart="Hi {{name}}, thanks for signing up.",HtmlPart="<h1>Welcome!</h1><p>Hi {{name}}, thanks for signing up.</p>"Then send using the template:
const ses = new AWS.SES();
await ses.sendTemplatedEmail({
Source: 'noreply@myapp.com',
Destination: { ToAddresses: ['user@example.com'] },
Template: 'WelcomeEmail',
TemplateData: JSON.stringify({ name: 'Alice' })
}).promise();
Real-world setup: I maintain templates for password resets, invitation links, order confirmations, and billing alerts. Each template lives in version control (as JSON), is tested before deployment, and includes both HTML and plain text versions (critical for accessibility and spam filter evasion).
Template variables use Handlebars syntax. You can do basic logic:
{{#if orderTotal}}Your order total is ${{orderTotal}}.{{/if}}Store template definitions in your repo, deploy them as part of CI/CD, and never email-template-swap by hand. Consistency saves debugging time and improves user experience.
Common mistakes
Forgetting DKIM verification. You'll send email, it won't reach spam, and you'll panic. DKIM is free and takes 20 minutes. Do it first.
Not suppressing bounces and complaints. Your reputation degrades silently. By the time you notice, you're already damaged. Automate this on day one.
Requesting production access too late. Ask AWS a week before launch, not the day before. It's rarely a problem, but when it is, you're stuck.
Sending to test addresses in production code. One deployed email to a test email address in your code, and you've sent to an invalid address. Suppress test addresses automatically in your send function.
Mixing transactional and marketing email. Use separate sending addresses and domains for transactional (password resets, order confirmations) and marketing (newsletters, promotions). ISPs track reputation per sender. Protect your transactional reputation like your life depends on it.
Key takeaways
- Verify your domain with SES and enable DKIM signing before sending any production email. This is the foundation of deliverability.
- Request production access a week before launch, not the day before. Document your use case and bounce handling strategy in the request.
- Set up SNS notifications for bounces and complaints, then suppress addresses automatically. Your sending reputation is fragile—protect it.
- Use SES templates for all transactional email. Version control them, test before deployment, and include both HTML and plain text versions.
- Monitor bounce and complaint rates weekly. If either exceeds thresholds, investigate and fix immediately. Reputation recovery takes time.
- Keep transactional email separate from marketing. Use different sending addresses and domains to isolate reputation.
After 90 days of building on AWS, email should feel like a solved problem. It is, if you set it up right. Spend the time on verification, suppression, and templates now, and email fades into the background. Skip these steps, and you'll be chasing deliverability issues for months. The choice is yours.






Comments
Loading comments…