AWS SES Integration
Mailer supports Amazon Simple Email Service (SES) as an email provider. SES offers enterprise-grade email delivery at a fraction of the cost of other providers.
Why Use AWS SES?
- High deliverability - AWS's infrastructure ensures reliable delivery
- Scalable - Handle millions of emails without infrastructure changes
- Full control - Manage your own sending reputation and domains
- Pay as you go - No monthly minimums or commitments
Prerequisites
Before setting up SES, you'll need:
- An AWS account with billing enabled
- A domain you own for sending emails
- Access to your domain's DNS settings
- A Redis instance for the email queue (Upstash recommended for Vercel)
Quick Setup
1. Create AWS Credentials
- Go to AWS Console → IAM → Users
- Create a new user named
mailer-ses-user - Attach the
AmazonSESFullAccesspolicy - Create an access key and save the credentials
For production, use a custom policy with minimal permissions. See the full setup guide for details.
2. Set Environment Variables
Add these to your environment:
AWS_SES_ACCESS_KEY_ID=your-access-key
AWS_SES_SECRET_ACCESS_KEY=your-secret-key
AWS_DEFAULT_REGION=us-east-1
# Redis for email queue
REDIS_URL=redis://your-redis-url:6379
# Your app URL (for webhooks)
PUBLIC_APP_URL=https://your-app.vercel.app3. Add Your Domain
- Go to Organization Settings → Domains
- Click Add Domain
- Enter your domain name
- Select your AWS region
- Click Add Domain
4. Configure DNS Records
After adding a domain, you'll see required DNS records:
| Type | Purpose |
|---|---|
| MX | Bounce handling |
| TXT (SPF) | Email authentication |
| TXT (DKIM) | Email signing |
| TXT (DMARC) | Domain policy |
Add these records to your DNS provider (Cloudflare, Route53, etc.).
5. Verify Domain
- Wait for DNS propagation (up to 48 hours)
- Click Check Status to refresh verification
- Once verified, click Set Active
Event Tracking
SES automatically tracks email events through webhooks:
| Event | Description |
|---|---|
| Sent | Email accepted by SES |
| Delivered | Email delivered to recipient's server |
| Opened | Recipient opened the email |
| Clicked | Recipient clicked a link |
| Bounced | Email couldn't be delivered |
| Complained | Recipient marked as spam |
These events appear in your email analytics and update in real-time.
Suppression List
SES requires maintaining a suppression list to protect your sender reputation. Mailer automatically:
- Adds hard bounces to the suppression list
- Adds spam complaints to the suppression list
- Prevents sending to suppressed addresses
Manage your suppression list at People → Suppression List.
SES Sandbox Mode
New AWS accounts start in "sandbox mode" with limitations:
- Can only send to verified email addresses
- 200 emails per day limit
- 1 email per second rate
Request Production Access
- Go to AWS Console → SES → Account Dashboard
- Click Request production access
- Describe your email use case
- Wait for approval (usually 24-48 hours)
Switching from Resend
If you're currently using Resend and want to switch to SES:
- Set up AWS credentials and environment variables
- Add and verify your domain in SES
- Update the domain's provider setting to "ses"
- Test with a small batch before full migration
Both providers can coexist - you can use different providers for different domains.
Troubleshooting
Scheduled Emails with QStash
Scheduled SES emails are stored in Redis and only send when the queue worker is woken up. In this app, the worker wake-up endpoint is:
POST /api/cron/email-queue
To make scheduled sends work reliably, create a QStash schedule that calls this endpoint every minute.
QStash Dashboard Setup
- Open your QStash project in Upstash.
- Go to Schedules.
- Click Create Schedule.
- Set the destination URL to your live cron endpoint, for example
https://your-app-domain.com/api/cron/email-queue. - Set the HTTP method to
POST. - If you set
CRON_SECRET, send it in theAuthorizationheader asBearer <your CRON_SECRET>. - Set the cron expression to
* * * * *. - Save the schedule.
QStash evaluates cron expressions in UTC by default. That is fine for this endpoint because it should run every minute regardless of your campaign timezone.
It can take up to about a minute for a newly created schedule to start firing.
If CRON_SECRET is not set, the endpoint still works without auth. That avoids breaking setups that have not configured a shared secret yet, but header-based auth is recommended for any public deployment.
Domain Not Verifying
- Check DNS records are correctly configured
- Wait up to 48 hours for propagation
- Use MXToolbox to verify records
Emails Not Sending
- Verify AWS credentials are correct
- Check if account is still in sandbox mode
- Look for errors in the application logs
Events Not Tracking
- Ensure
PUBLIC_APP_URLis set correctly - Check SNS subscription in AWS Console
- Verify webhook endpoint is accessible
Rate Limiting
SES has sending limits based on account reputation. The email queue automatically respects these limits with retry logic.
Resources
- Full SES Setup Guide - Detailed technical setup
- AWS SES Documentation
- AWS SES Pricing