# AWS SES (/docs/v/0.6.1/adapters/ses)



The SES adapter calls the [SES v2 SendEmail API](https://docs.aws.amazon.com/ses/latest/APIReference-V2/API_SendEmail.html) over `fetch` and signs every request with AWS Signature V4 itself — your bundle never pulls in the AWS SDK. Pick a region, optionally a configuration set, and send.

<ProviderBadge adapter="ses" />

## Configure [#configure]

Use credentials allowed to call SES v2 `SendEmail` in your region, and verify the sender identity in that same region. New AWS accounts start in the SES sandbox, which limits recipients to verified addresses.

```ts title="lib/email.ts"
import { createEmailClient } from "@opencoredev/email-sdk";
import { ses } from "@opencoredev/email-sdk/ses";

export const email = createEmailClient({
  adapters: [
    ses({
      accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
      secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
      region: process.env.AWS_REGION!,
    }),
  ],
});
```

<TypeTable
  type="{
  accessKeyId: {
    description: &#x22;AWS access key ID.&#x22;,
    type: &#x22;string&#x22;,
    required: true,
  },
  secretAccessKey: {
    description: &#x22;AWS secret access key.&#x22;,
    type: &#x22;string&#x22;,
    required: true,
  },
  region: {
    description: &#x22;SES region, e.g. us-east-1.&#x22;,
    type: &#x22;string&#x22;,
    required: true,
  },
  sessionToken: {
    description: &#x22;Session token for temporary credentials (STS, IAM roles).&#x22;,
    type: &#x22;string&#x22;,
  },
  configurationSetName: {
    description: &#x22;SES configuration set applied to every send.&#x22;,
    type: &#x22;string&#x22;,
  },
  charset: {
    description: &#x22;Charset for subject and body content.&#x22;,
    type: &#x22;string&#x22;,
    default: '&#x22;UTF-8&#x22;',
  },
  baseUrl: {
    description: &#x22;Override the API origin.&#x22;,
    type: &#x22;string&#x22;,
    default: '&#x22;https://email.{region}.amazonaws.com&#x22;',
  },
  fetch: {
    description: &#x22;Custom fetch implementation for tests or special runtimes.&#x22;,
    type: &#x22;typeof fetch&#x22;,
  },
}"
/>

## Send [#send]

SES maps `cc`, `bcc`, `replyTo`, `headers`, `attachments`, and `tags` (as SES `EmailTags`, which flow to configuration-set event destinations).

```ts
const result = await email.send({
  from: "Acme <billing@acme.com>",
  to: "user@example.com",
  replyTo: "support@example.com",
  subject: "Your May invoice",
  html: "<p>Your invoice for May is ready.</p>",
  tags: [{ name: "type", value: "invoice" }],
});

console.log(result.id); // SES MessageId, also exposed as result.messageId
```

<Callout type="warn" title="No metadata field">
  SES v2 `SendEmail` has no metadata concept, so `metadata` on a message throws an
  `EmailValidationError` before any request is made. Use `tags` instead, or pick a
  [metadata-capable adapter](/docs/v/0.6.1/adapters/field-support).
</Callout>

## Verify from the CLI [#verify-from-the-cli]

```bash
AWS_ACCESS_KEY_ID="..." AWS_SECRET_ACCESS_KEY="..." AWS_REGION="us-east-1" \
  npx email-sdk doctor --adapter ses
```

```bash
npx email-sdk send \
  --adapter ses \
  --access-key-id "$AWS_ACCESS_KEY_ID" \
  --secret-access-key "$AWS_SECRET_ACCESS_KEY" \
  --region us-east-1 \
  --from "Acme <hello@acme.com>" \
  --to user@example.com \
  --subject "SES smoke test" \
  --text "It works" \
  --dry-run
```

`--session-token` and `--configuration-set` flags are also available. Drop `--dry-run` for one real send — that is the only check that proves the identity is verified and the account is out of the sandbox.
