AWS SES channel
WIPAWS Simple Email Service via @aws-sdk/client-ses.
Install
bun add @aws-sdk/client-ses
Setup
import { SESClient } from '@aws-sdk/client-ses'
import { AuthSesChannel } from '@gentleduck/auth/channels/ses'
const client = new SESClient({ region: 'us-east-1' })
const channel = new AuthSesChannel({
client,
from: 'no-reply@example.com',
id: 'ses',
})
Verify the from address (or its domain) in the SES console. If your
account is still in the sandbox, also verify recipient addresses.
IAM permissions
The IAM role / user running your service needs the ses:SendEmail
permission (and ses:SendRawEmail if you send with custom headers /
attachments):
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["ses:SendEmail", "ses:SendRawEmail"],
"Resource": "*"
}
]
}
With magic-link
auth.providers.use(
magicLink({
channels: { email: channel },
findIdentityByEmail: (email) => auth.identities.findByEmail(email),
}),
)
Configuration set / event publishing
For bounce + complaint webhooks, wire SES Configuration Sets and SNS:
- Create a configuration set in the SES console.
- Subscribe an SNS topic to bounce + complaint events.
- Subscribe your service to the SNS topic.
- Mark identities undeliverable from the handler:
app.post('/webhooks/ses', async (req, res) => {
const event = parseSnsMessage(req.body)
if (event.eventType === 'Bounce') {
for (const recipient of event.bounce.bouncedRecipients) {
const identity = await auth.identities.findByEmail(recipient.emailAddress)
if (identity) {
await auth.identities.markEmailUndeliverable(identity.id, { reason: 'bounce' })
}
}
}
res.json({ ok: true })
})
Throughput
SES has account-level send rate (msg/s) and daily quotas. Once you
leave the sandbox, quotas auto-scale based on reputation. duck-auth
itself does not throttle outbound; if you push above SES limits,
SendEmail returns Throttling and AuthSesChannel raises
AUTH/RATE_LIMITED.
For backfill / bulk transactional (rare in auth flows), shard sends across regions / accounts to stay under the per-account ceiling.