# AWS SES Setup Simplified: From Hours to Minutes

Source: https://wraps.dev/blog/aws-ses-simplified

Engineering•10 min read•Wraps Team

# AWS SES Setup Simplified:From Hours to Minutes

If you've ever tried to set up AWS SES from scratch, you know the pain. What should be a simple “send email from my app” turns into a multi-day odyssey through IAM policies, sandbox approvals, DKIM configuration, and prayers to the DNS propagation gods.

We built Wraps because this experience is broken—and because you shouldn't have to choose between AWS economics and developer sanity.

Minutes to deploy

$0.10 per 1K emails

Your AWS, your data

## The Problem: SES Setup is a Nightmare

Developer forums are filled with horror stories about AWS SES setup. The process involves navigating the AWS console, writing IAM policies by hand, configuring [DKIM records](https://wraps.dev/blog/your-dmarc-policy-is-useless), setting up event tracking pipelines, and managing credentials—all before you can send a single email.

“

> I have been denied production access about 3 times now... It's frustrating as the responses we get from the AWS Trust & Safety Team are very point blank and appear to be canned messages for rejecting.

— AWS re:Post

“

> After a whole day of configurations in the AWS console I still couldn't send one single email. When going back to Resend, I managed to send my first email in literally 1 minute.SES: 15 hours = $1500; Resend: 15mn = $25

— Nino Filiu, Dev.to

The time investment is brutal. When you factor in engineer time, the cost of manual SES setup can dwarf the savings you get from AWS's low per-email pricing. Developers routinely spend full days just getting to the point where they can send a single test email.

But here's the thing— **once SES is set up, it's incredibly cheap** ($0.10 per 1,000 emails) and you own the infrastructure. The problem isn't SES itself. It's the setup.

## The Solution: Infrastructure as Code, Zero Console

Wraps deploys production-ready email infrastructure to your AWS account using Pulumi under the hood. One command. No AWS console spelunking. No sandbox approval essays.

terminal

```
▋
```

That's it. In about 2 minutes, you have:

-   SES configured with your domain
-   DKIM, SPF, and DMARC ready for DNS
-   Event tracking via EventBridge
-   Email history in DynamoDB
-   Lambda functions processing delivery events
-   Proper IAM roles with least-privilege access
-   OIDC authentication (no stored credentials)

~2min

Setup Time

vs 4-8 hours

0

AWS Resources

auto-configured

Zero

Credentials

stored secrets

$0.0

Cost

per 1K emails

## See It In Action

Terminal

Run Demo

$npx @wraps.dev/cli email init

◐ Validating AWS credentials...

## Option 1: The CLI Path (Fastest)

### Installation

Install the CLI globally or run it directly with npx:

terminal

```
npm install -g @wraps.dev/cli
# or just use npx
npx @wraps.dev/cli email init
```

#### Requirements

-   Node.js 20+
-   AWS CLI configured with valid credentials
-   An AWS account with appropriate permissions

### Deploying Infrastructure

Run the init command and the CLI walks you through the entire process:

terminal

```
wraps email init
```

The CLI handles five steps automatically:

1.  **Credential validation** – Confirms your AWS access
2.  **Preset selection** – Choose Starter, Production, or Enterprise
3.  **Cost estimation** – Shows estimated monthly AWS costs
4.  **Deployment** – Pulumi provisions everything
5.  **OIDC setup** – Configures credential-free access for Vercel

#### Command Options

terminal

```
wraps email init -p vercel -r us-east-1 -y
```

| Flag | Description |
| --- | --- |
| \-p, --provider | Hosting provider (vercel, aws, railway, other) |
| \-r, --region | AWS region |
| \-y, --yes | Skip confirmation prompts |

### Adding Your Domain

Once infrastructure is deployed, add your sending domain. This creates the SES identity and returns DKIM tokens for DNS configuration.

terminal

```
wraps email domains add -d yourdomain.com
```

### Verifying DNS Configuration

After adding DNS records, verify that everything is configured correctly. The CLI checks DKIM CNAME records, [SPF TXT record](https://wraps.dev/blog/spf-guide), [DMARC TXT record](https://wraps.dev/blog/your-dmarc-policy-is-useless), and MAIL FROM MX records (if configured).

terminal

```
wraps email domains verify -d yourdomain.com
```

### Managing Your Deployment

The CLI provides commands for every lifecycle stage of your email infrastructure:

terminal

```
# Check status of your infrastructure
wraps email status

# List all domains
wraps email domains list

# Upgrade to a higher tier
wraps email upgrade

# Remove everything (careful!)
wraps email destroy
```

## Manual vs Wraps

| Task | Manual SES | With Wraps |
| --- | --- | --- |
| Time to first email | 4-8 hours | 
~2 minutes

 |
| DNS configuration | Manual lookup | 

Auto-generated

 |
| IAM policies | Write from scratch | 

Least-privilege included

 |
| Event tracking | Build pipeline | 

One command

 |
| Credential management | Store secrets | 

OIDC (zero secrets)

 |

## Option 2: Understanding the IaC (What Gets Deployed)

Under the hood, Wraps uses Pulumi with `@pulumi/aws` to provision infrastructure. All resources are namespaced with `wraps-email-*` for easy identification. Here's the complete picture.

### IAM Resources

| Resource | Purpose |
| --- | --- |
| wraps-email-role | Main IAM role for SDK access |
| wraps-email-policy | Least-privilege policy for email operations |
| OIDC Provider | Enables Vercel to assume the role without stored credentials |

### SES Resources

| Resource | Purpose |
| --- | --- |
| wraps-email-config-set | Configuration set for event tracking |
| Email Identities | Your verified domains |

### Event Tracking Architecture

Every email event flows through a reliable, serverless pipeline. SES emits events to EventBridge, which routes them to SQS for buffered processing. A Lambda function reads from the queue and writes structured records to DynamoDB. Failed events land in a Dead Letter Queue for debugging.

#### Event Processing Architecture

SES

EventBridge

SQS

Lambda

DynamoDB

Email sent through SES triggers delivery events

| Resource | Purpose |
| --- | --- |
| EventBridge Rule | Captures SES events (send, delivery, open, click, bounce, complaint) |
| SQS Queue | Buffers events for reliable processing |
| SQS Dead Letter Queue | Stores failed events for debugging |
| Lambda Function | Processes events and writes to DynamoDB |

### Storage

| Resource | Purpose |
| --- | --- |
| wraps-email-history | DynamoDB table for email history |
| wraps-email-events | DynamoDB table for delivery events |

### Event Types Tracked

SENDDELIVERYOPENCLICKBOUNCECOMPLAINTREJECTRENDERING\_FAILUREDELIVERY\_DELAYSUBSCRIPTION

### Resource Naming Convention

All Wraps resources follow the pattern `wraps-email-{resource-type}`. Every resource is tagged with `ManagedBy: 'wraps-cli'` so you can easily identify and audit Wraps-managed infrastructure in your AWS account.

## Configuration Presets

Choose your deployment complexity based on your needs. Upgrade anytime with `wraps email upgrade`.

#### Starter

~$0.05/mo

MVPs & side projects

-   Open & click tracking
-   Bounce suppression
-   Basic metrics

#### Production

~$2-5/mo

Most production apps

-   Real-time event tracking
-   90-day email history
-   All event types
-   Reputation metrics

#### Enterprise

~$50-100/mo

High-volume & compliance

-   Dedicated IP address
-   1-year retention
-   All 10 SES event types
-   Priority support

## Sending Emails with the SDK

Once infrastructure is deployed, install the TypeScript SDK and start sending. The SDK is fully typed and handles credential resolution automatically.

terminal

```
npm install @wraps.dev/email
```

On Vercel, credentials are automatic via OIDC—no environment variables, no stored secrets. The SDK detects the runtime environment and acquires temporary AWS credentials transparently.

Send Email

Batch SendTrack Events

```
import { WrapsEmail } from '@wraps.dev/email';

const email = new WrapsEmail();

await email.send({
  from: 'hello@yourdomain.com',
  to: 'user@example.com',
  subject: 'Welcome!',
  html: '<h1>Hello from Wraps!</h1>',
});
```

## Vercel OIDC Integration

One of the nastier parts of AWS integrations is credential management. Wraps eliminates this entirely using OIDC federation.

### How It Works

OIDC Flow

```
Vercel → OIDC Token → AWS STS → Temporary Credentials → SES
```

1.  Your Vercel deployment requests an OIDC token
2.  AWS STS validates the token and issues temporary credentials
3.  The SDK uses those credentials to call SES
4.  Credentials rotate automatically—nothing stored

### Setup

During `wraps email init`, you'll be asked for your Vercel team slug and project name. These configure the OIDC trust relationship so your deployments can assume the IAM role without any stored secrets. See our [Next.js + Vercel + SES guide](https://wraps.dev/blog/nextjs-vercel-ses-guide) for a complete walkthrough.

## Connecting Existing SES

Already have SES configured? Wraps can layer on top without touching your existing setup:

terminal

```
wraps email connect
```

This command scans your existing AWS resources (SES domains, config sets), prompts for which features you want to add, and deploys Wraps resources non-destructively. It never modifies your existing configuration—all new resources are created with the `wraps-email-` prefix.

## Troubleshooting

### “AWS credentials not found”

Ensure the AWS CLI is configured by running `aws configure`. Verify your identity with `aws sts get-caller-identity`.

### “Insufficient permissions”

Your AWS user needs permissions to create IAM roles, SES identities, DynamoDB tables, Lambda functions, SQS queues, and EventBridge rules. Use an admin user for initial setup.

### “Domain verification pending”

Add the DKIM CNAME records to your DNS provider. Run `wraps email domains verify -d yourdomain.com` to check progress. DNS propagation can take up to 72 hours, though it usually completes in minutes. Need help with sandbox approval? See our [SES sandbox escape guide](https://wraps.dev/blog/ses-sandbox-guide).

### “OIDC provider already exists”

The Vercel OIDC provider may exist from another project. The CLI handles this gracefully and reuses existing providers—no action needed on your part.

## What You're Not Paying For

Traditional email APIs charge you per email sent, plus platform fees, plus storage fees. With Wraps, the model is different:

-   You pay AWS directly for sending ($0.10 per 1,000 emails)
-   Your data stays in your AWS account
-   No vendor lock-in—your infrastructure keeps working even if you stop using Wraps

If you're sending 100,000 emails per month: Resend ~$90/month vs Wraps ~$12 to AWS.

## Design Principles

Every CLI decision follows these principles:

#### Non-Destructive

We never modify your existing AWS resources

#### Namespaced

All resources prefixed with wraps-email- for easy identification

#### Fail Fast

Validate early, so you know immediately if something’s wrong

#### Great UX

Clear output, helpful error messages, suggestions for next steps

#### Zero Credentials Stored

OIDC authentication only

## Continue Learning

[

### Next.js + Vercel + AWS SES Guide

Complete tutorial for Next.js apps with OIDC authentication

](https://wraps.dev/blog/nextjs-vercel-ses-guide)[

### Escape AWS SES Sandbox

Get production access on your first try

](https://wraps.dev/blog/ses-sandbox-guide)[

### SES Production Architecture

Dedicated IPs, bounce handling, and scaling patterns

](https://wraps.dev/blog/ses-production-architecture)[

### Your DMARC Policy Is Useless

Interactive deep-dive into email authentication

](https://wraps.dev/blog/your-dmarc-policy-is-useless)[

### The SPF 10-Lookup Limit

Why your SPF record might be failing silently

](https://wraps.dev/blog/spf-guide)[

### Email Tools

Check your domain's email authentication setup

](https://wraps.dev/tools)

## Ready to escape the AWS console?

You'll have production email infrastructure in your AWS account in minutes.

npx @wraps.dev/cli email init

[Explore the CLI](https://wraps.dev/cli)[Read the Docs](https://wraps.dev/docs)
