Providers
AWS SES
Uses the Ahena CLI (beta). Install it with npm install -g @ahena/cli, or prefix commands with npx @ahena/cli. See Installation. The dashboard covers projects, connections, Doctor, the Stack Graph, plans and approvals without it.
Overview
Ahena checks your Amazon SES account is ready to send from your domain: production access
(out of the sandbox), sending enabled, reputation status, domain verification, DKIM, custom
MAIL FROM and configuration sets. It creates a missing domain identity (Easy DKIM) and
configuration set, lists the exact DKIM records to publish, and generates the email adapter.
Your app sends through SES directly with its own credentials. Ahena never sends email, not
even a test.
AWS is not one provider in Ahena: SES and S3 are separate providers with separate connections. Ahena doesn't manage EC2, RDS, Lambda, Route 53, CloudFront, ECS, EKS or IAM.
Package: @ahena/provider-aws-ses · Category: email · API: SES v2 (email.<region>.amazonaws.com/v2/email/…, SigV4 signed with WebCrypto)
Capabilities: account-status, domain-identities, dkim, configuration-sets, production-access
Maturity: verified against a fake of the API; not yet verified against the real service.
Connection
| Field | Secret | Notes |
|---|---|---|
AWS_ACCESS_KEY_ID |
yes | Access key of a dedicated IAM user for Ahena. |
AWS_SECRET_ACCESS_KEY |
yes | Shown once when the key is created. |
AWS_SESSION_TOKEN |
yes, optional | Only for temporary credentials. |
AWS_REGION |
no (setting) | The SES Region your app sends from. Identities, quotas and production access are separate per Region. |
AWS_ACCESS_KEY_ID=… AWS_SECRET_ACCESS_KEY=… ahena connect aws-ses -e production --set AWS_REGION=us-east-1
Supported Regions: us-east-1, us-east-2, us-west-1, us-west-2, ca-central-1,
sa-east-1, eu-central-1, eu-west-1, eu-west-2, eu-west-3, eu-north-1, ap-south-1,
ap-northeast-1, ap-northeast-2, ap-northeast-3, ap-southeast-1, ap-southeast-2.
Network: only email.<region>.amazonaws.com and sts.<region>.amazonaws.com for those Regions
(34 exact hosts, enforced).
Permissions
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AhenaSesRead",
"Effect": "Allow",
"Action": [
"ses:GetAccount",
"ses:ListEmailIdentities",
"ses:GetEmailIdentity",
"ses:ListConfigurationSets",
"ses:GetConfigurationSet",
"ses:GetConfigurationSetEventDestinations"
],
"Resource": "*"
},
{
"Sid": "AhenaSesCreate",
"Effect": "Allow",
"Action": ["ses:CreateEmailIdentity", "ses:CreateConfigurationSet"],
"Resource": "*"
},
{
"Sid": "AhenaNeverSends",
"Effect": "Deny",
"Action": ["ses:SendEmail", "ses:SendRawEmail", "ses:SendBulkEmail", "ses:SendTemplatedEmail", "ses:SendBulkTemplatedEmail"],
"Resource": "*"
}
]
}
The Deny statement is optional and makes "Ahena never sends" enforceable by AWS. You can
scope AhenaSesCreate to arn:aws:ses:<region>:<account>:identity/<domain> and
arn:aws:ses:<region>:<account>:configuration-set/<name>. For read-only use, leave
AhenaSesCreate out. Ahena never needs delete, tag, policy or sending actions.
Permission check: checked. ahena connect calls STS GetCallerIdentity, then probes the
three account-wide reads, whose IAM resource is * so the answer holds for every resource:
GetAccount, ListEmailIdentities (page size 1) and ListConfigurationSets (page size 1). Each
is reported granted, missing (AccessDeniedException) or :unknown (throttled or an outage);
a missing one stops the connection at configuration_required. ses:GetEmailIdentity,
ses:GetConfigurationSet, ses:CreateEmailIdentity and ses:CreateConfigurationSet are always
reported :unknown: they can be scoped to resources, and creates can't be tested without
creating something. Gaps there show as 403s in Doctor or apply, naming the action.
Capabilities
| Capability | Access | Verification | Changes | Refuses |
|---|---|---|---|---|
account-status |
read-only | sending email (not even a test) | ||
domain-identities |
writable | read-back | CONFIRMATION_REQUIRED | deleting identities; email-address identities (they send a verification email); Bring Your Own DKIM keys |
dkim |
manual | |||
configuration-sets |
writable | read-back | CONFIRMATION_REQUIRED | deleting configuration sets; changing event destinations |
production-access |
manual |
ahena inspect aws-ses shows the account (sandbox, sending enabled, enforcement status, quota,
production-access request status), every identity with its verification status, and for the
first 25 domains: DKIM status, the DKIM CNAME records, custom MAIL FROM and the default
configuration set; and each configuration set's event destinations (name, enabled, event types,
destination kind; never ARNs). Contact details from GetAccount, identity sending policies and
tags are dropped. ahena lock records production access, sending enabled, the declared
identity's verification/DKIM/MAIL FROM and the configuration set's destinations.
Configure
email: {
provider: "aws-ses",
domain: { production: "leo.example.com" },
from: "Leo <hello@leo.example.com>",
configurationSet: "leo",
region: "us-east-1", // optional: Doctor checks the connection uses this Region
},
| Change | Classification | Notes |
|---|---|---|
| Create the domain identity (Easy DKIM) | CONFIRMATION_REQUIRED | SES generates a 2048-bit key and three DKIM tokens. Then publish the three CNAME records (MANUAL, listed by Doctor). |
| Create the configuration set | CONFIRMATION_REQUIRED | Name only; default settings, no event destinations. |
DKIM records: <token>._domainkey.<domain> CNAME <token>.dkim.amazonses.com
(<token>.dkim.ap-northeast-3.amazonses.com in Osaka). The existing
ahena configure cloudflare --records-from flow only reads Resend's records today, so even with
DNS on Cloudflare these are added by hand (or with ahena configure cloudflare --desired <file>
listing them as dnsRecords).
Every apply step re-reads first and creates only if absent; SES's AlreadyExistsException
(a race) counts as done. A plan made for one Region is refused after the connection's Region
changes. After apply, Ahena plans again: an empty plan is the verification. Domain
verification itself completes when SES sees the DNS records; Doctor tracks it.
Refused outright: delete, deleteIdentity, deleteConfigurationSet, send, testEmail,
productionAccess, email-address identities.
Generate
ahena generate aws-ses writes the same email interface as the Resend adapter (same exported
names and SendEmail fields), so ahena switch email resend aws-ses keeps app code:
src/ahena/email/index.ts:email.send({ to, subject, html?, text?, from?, replyTo? })→{ id }(the SES message id),email.raw; sends withSendEmailCommandand, when set,SES_CONFIGURATION_SETsrc/ahena/providers/aws-ses.ts: anSESv2Clientfrom@aws-sdk/client-sesv2(credentials from the AWS default chain, Region fromAWS_REGION).env.example.aws-ses
Give the app its own role or user allowed only ses:SendEmail on your identity.
Limitations
- Opt-in Regions and GovCloud/China aren't supported.
- Ahena never sends email, deletes identities or configuration sets, changes event destinations, sets up a custom MAIL FROM domain, or uses Bring Your Own DKIM.
- DKIM records aren't added to DNS automatically, even on Cloudflare (
--records-fromreads only Resend's records today). - Production access is an AWS support request; Ahena gives the steps.
- An existing identity's default configuration set isn't changed.
- Dedicated IPs, suppression lists, templates, contact lists and VDM aren't managed.
Manual steps
- Create the IAM user and policy above.
- Publish the three DKIM CNAME records Doctor lists at your DNS host.
- Request production access (out of the sandbox) for production: SES console (your Region) → Account dashboard → Request production access → describe your use case, opt-in and bounce/complaint handling → submit. AWS answers through a support case, usually within a day.
- Optional: set up a custom MAIL FROM domain (SES console → Identities → your domain → Custom MAIL FROM domain) so SPF aligns for DMARC.
Doctor checks
| Id | Severity | Meaning |
|---|---|---|
aws-ses.credential |
PASS / FAIL | STS accepts the credentials. |
aws-ses.region |
PASS / FAIL | AWS_REGION unsupported, or differs from email.region. |
aws-ses.account.sandbox |
PASS / FAIL / INFO | In the sandbox: FAIL in production (MANUAL, console steps), INFO elsewhere. |
aws-ses.account.sending |
PASS / FAIL | Sending disabled for the account in this Region. |
aws-ses.account.enforcement |
PASS / WARNING / FAIL | PROBATION (WARNING), SHUTDOWN (FAIL). |
aws-ses.account.quota |
INFO | 24-hour quota, rate, sent in the last 24 h. |
aws-ses.account |
FAIL | Account status couldn't be read (names the missing action on 403). |
aws-ses.domain.undeclared |
INFO | No email.domain, so domain checks are skipped. |
aws-ses.domain |
PASS / FAIL | Identity missing (CONFIRMATION_REQUIRED fix) or not verified. |
aws-ses.domain.dkim |
PASS / WARNING / FAIL | PENDING/TEMPORARY_FAILURE: WARNING (FAIL in production); FAILED/NOT_STARTED: FAIL. Evidence lists the exact CNAME records. |
aws-ses.domain.mail_from |
INFO | Custom MAIL FROM domain and status, or none. |
aws-ses.from |
PASS / WARNING | email.from isn't on the verified domain. |
aws-ses.configuration_set |
PASS / WARNING / FAIL | Missing (CONFIRMATION_REQUIRED fix), sending disabled on it, or present (event destinations in evidence). |
Disconnect behavior
Removes the connection and the credentials Ahena stored. Ahena can't revoke an IAM access key it didn't create: deactivate and delete it in IAM → Users → the Ahena user → Security credentials. Identities, configuration sets, suppression lists and sending history are untouched.
Set up step by step
Create the credential. AWS console → IAM → Users → Create user (e.g.
ahena-ses, no console access) → Add permissions → Create inline policy (JSON tab: the policy above) → the user → Security credentials → Create access key → "Application running outside AWS".Minimum permissions. The six read actions; add the two creates if Ahena should create the identity and configuration set. Keep the
Denyon sending.Connect.
AWS_ACCESS_KEY_ID=… AWS_SECRET_ACCESS_KEY=… ahena connect aws-ses -e production --set AWS_REGION=us-east-1Pipe the values or type them at the hidden prompt; never pass them as arguments.
Verify the connection.
ahena inspect aws-ses -e productionshows the account status, identities with DKIM records, and configuration sets.Run Doctor.
ahena doctor -e production.Plan. Add the
emailsection above toahena.config.ts, thenahena plan -e production(orahena configure aws-ses -e production).Apply an allowed change.
ahena apply -e productioncreates the configuration set and the domain identity (each needs confirmation). Then publish the three DKIM CNAME records Doctor lists, and request production access if Doctor says the account is in the sandbox.Verify.
ahena plan -e productionshows no SES changes, and once DNS propagatesahena doctor -e productionshows the domain verified and DKIMPASS. Thenahena generate aws-ses -e production, give the app its ownses:SendEmailcredentials, and set.env.example.aws-ses's variables.Troubleshooting.
Symptom Cause Fix "AWS rejected the credentials (InvalidClientTokenId)" Deleted, inactive or mistyped key Create a new access key and reconnect. "missing ses:ListConfigurationSets" at connect The read actions aren't in the policy Attach the policy above and reconnect. Doctor: "isn't an SES identity in eu-west-1" Identity created in another Region Connect with the Region you send from ( --set AWS_REGION=…).DKIM stays pending Records not published, wrong names, or not propagated Check the full names Doctor lists; some DNS hosts append the zone, so enter only the part before your domain. aws-ses.account.sandboxFAILProduction access not granted in this Region Request production access (Manual steps). aws-ses.account.enforcementWARNINGAccount under review for bounces/complaints Fix the cause in SES → Reputation metrics and reply to AWS's case. "configuration set name" refused Spaces or other characters Use up to 64 letters, digits, -and_.Disconnect and revoke.
ahena disconnect aws-ses -e production, then IAM → Users →ahena-ses→ Security credentials → deactivate and delete the access key.