Get Started

AWS SES Setup

Connect your AWS account and verify a sending domain.

#AWS SES Setup

This platform sends mail through your AWS SES account — you provide the credentials, Amazon does the sending, and you pay Amazon directly (about $0.10 per 1,000 emails).

There are two ways to wire up SES:

  1. Dashboard setup (recommended) — paste your AWS keys at Settings → SES, click one button, the app creates the SES configuration set, SNS topics, and event destinations for you.
  2. Manual setup — run the AWS CLI commands yourself if you want fine-grained control or you already have SES infrastructure you want to reuse.

Either way, you'll end up with the same thing:

  • An SES configuration set named emailforsaas-tracking
  • Three SNS topics — emailforsaas-bounces, emailforsaas-complaints, emailforsaas-deliveries
  • Event destinations pointed at those topics
  • An HTTPS SNS subscription pointed at /api/webhooks/ses on your deployment

#Prerequisites

  • An AWS account
  • Billing enabled (SES is pay-as-you-go, roughly $0.10 / 1,000 emails)
  • A domain you can add DNS records for

#1. Create an IAM user

Create an IAM user (e.g. emailforsaas-ses) with an access key and the following inline policy. The app reads these credentials to send mail and manage SES infrastructure on your behalf.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "SES",
      "Effect": "Allow",
      "Action": [
        "ses:SendEmail",
        "ses:SendRawEmail",
        "ses:GetAccount",
        "ses:CreateEmailIdentity",
        "ses:DeleteEmailIdentity",
        "ses:GetEmailIdentity",
        "ses:PutEmailIdentityDkimSigningAttributes",
        "ses:CreateConfigurationSet",
        "ses:CreateConfigurationSetEventDestination",
        "ses:GetConfigurationSet"
      ],
      "Resource": "*"
    },
    {
      "Sid": "SNS",
      "Effect": "Allow",
      "Action": [
        "sns:CreateTopic",
        "sns:Subscribe",
        "sns:GetTopicAttributes",
        "sns:SetTopicAttributes",
        "sns:Publish"
      ],
      "Resource": "arn:aws:sns:*:*:emailforsaas-*"
    }
  ]
}

Save the Access Key ID and Secret Access Key somewhere safe — you'll paste them into the dashboard next.

  1. Sign in to your deployment and open Settings → SES.
  2. Paste the Access Key ID, Secret Access Key, and region (default us-east-1).
  3. Click Set up SES.

The app runs an authenticated Convex action (api.sesSetup.setupSes) that:

  • Validates the credentials and reports whether you're still in SES sandbox mode.
  • Creates the emailforsaas-tracking configuration set if it doesn't exist.
  • Creates the three SNS topics.
  • Subscribes https://<your-deployment>.convex.site/api/webhooks/ses to each topic (HTTPS).
  • Wires each SES event type to the matching SNS topic as an event destination.

Credentials are encrypted with AES-256-GCM using the ENCRYPTION_KEY env var before being stored, and only decrypted at send time.

Already have an SES configuration set you want to reuse? The setup action is idempotent — it will skip anything that already exists.

#3. Manual setup (optional)

If you'd rather do the wiring yourself, run these commands. Replace <account-id> and <deployment> with real values.

# 1. Configuration set
aws sesv2 create-configuration-set \
  --configuration-set-name emailforsaas-tracking \
  --region us-east-1

# 2. SNS topics
aws sns create-topic --name emailforsaas-bounces     --region us-east-1
aws sns create-topic --name emailforsaas-complaints  --region us-east-1
aws sns create-topic --name emailforsaas-deliveries  --region us-east-1

# 3. Subscribe the webhook (auto-confirmed by our endpoint)
for topic in bounces complaints deliveries; do
  aws sns subscribe \
    --topic-arn "arn:aws:sns:us-east-1:<account-id>:emailforsaas-${topic}" \
    --protocol https \
    --notification-endpoint "https://<deployment>.convex.site/api/webhooks/ses"
done

# 4. Event destinations
aws sesv2 create-configuration-set-event-destination \
  --configuration-set-name emailforsaas-tracking \
  --event-destination-name bounces \
  --event-destination '{"Enabled":true,"MatchingEventTypes":["BOUNCE"],"SnsDestination":{"TopicArn":"arn:aws:sns:us-east-1:<account-id>:emailforsaas-bounces"}}'

aws sesv2 create-configuration-set-event-destination \
  --configuration-set-name emailforsaas-tracking \
  --event-destination-name complaints \
  --event-destination '{"Enabled":true,"MatchingEventTypes":["COMPLAINT"],"SnsDestination":{"TopicArn":"arn:aws:sns:us-east-1:<account-id>:emailforsaas-complaints"}}'

aws sesv2 create-configuration-set-event-destination \
  --configuration-set-name emailforsaas-tracking \
  --event-destination-name deliveries \
  --event-destination '{"Enabled":true,"MatchingEventTypes":["DELIVERY","SEND","OPEN","CLICK"],"SnsDestination":{"TopicArn":"arn:aws:sns:us-east-1:<account-id>:emailforsaas-deliveries"}}'

Then set the deployment environment variables:

npx convex env set AWS_ACCESS_KEY_ID     "..."
npx convex env set AWS_SECRET_ACCESS_KEY "..."
npx convex env set AWS_SES_REGION        "us-east-1"
npx convex env set SES_CONFIGURATION_SET "emailforsaas-tracking"

#4. Verify a sending domain

  1. Open Domains → Add domain in the dashboard.
  2. Enter the domain you want to send from (e.g. mail.yourcompany.com) and region.
  3. Copy the three DKIM CNAME records into your DNS provider.
  4. DNS propagation is usually instant on Cloudflare, minutes on most registrars, up to 72 hours worst case.

The domain flips to verified automatically once SES sees the records. Check with:

dig CNAME <token>._domainkey.yourdomain.com

While you're there, add an SPF record (v=spf1 include:amazonses.com ~all) and a DMARC record (v=DMARC1; p=none; rua=mailto:postmaster@yourdomain.com) to hit strong inbox placement.

#5. Leave the SES sandbox

Every new SES account is in sandbox mode — you can only send to verified recipient addresses, and you're capped at a small daily volume.

  1. AWS Console → SES → Account dashboardRequest production access.
  2. Fill in:
    • Mail type: Transactional (or Transactional + Marketing if you'll run campaigns)
    • Website URL
    • Use case description
    • Expected daily volume
  3. Approvals are usually returned within 24 hours.

Until you're approved, the dashboard displays a sandbox badge and restricts sending to verified addresses.

#Verifying the setup

From Settings → SES, click Validate setup. The platform checks:

  • Credentials are present and still valid
  • Configuration set exists
  • All three SNS topics exist and have an active HTTPS subscription
  • Event destinations are wired to the right topics

Any red flag shows up as a specific step failure so you know exactly what to fix.

#Troubleshooting

#Webhooks aren't firing

  • Confirm each SNS subscription is in Confirmed state:
    aws sns list-subscriptions-by-topic --topic-arn arn:aws:sns:us-east-1:<id>:emailforsaas-bounces
    
  • Convex HTTP logs (npx convex logs) will show incoming POSTs to /api/webhooks/ses. The first request from SNS after subscription is a SubscriptionConfirmation — the endpoint auto-confirms it.

#Emails show failed status

  • Check Convex logs for the actual SES error.
  • Verify you're out of the sandbox, or that you're only sending to verified addresses.
  • Confirm the from address uses a verified domain.

#DKIM verification stuck on pending

  • DNS propagation. Run dig against your domain's three DKIM CNAMEs and the provider you pasted the records into.
  • Some registrars silently append the root domain — abc._domainkey.yourdomain.com becomes abc._domainkey.yourdomain.com.yourdomain.com. Fix by entering just abc._domainkey as the host.