IAM policies¶
SESKit must never ask for AdministratorAccess. There are three policies because
they grant genuinely different things, and you should be able to run SESKit
without the second and the third.
Sending — seven actions (required)¶
Everything except delivery events. All of it is scoped to SES, and only one action can create anything.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"sts:GetCallerIdentity",
"ses:GetAccount",
"ses:CreateEmailIdentity",
"ses:GetEmailIdentity",
"ses:DeleteEmailIdentity",
"ses:SendEmail",
"ses:PutAccountDetails"
],
"Resource": "*"
}
]
}
The first two are read-only and are all that connecting an account needs, so you can grant just those to look around before committing to anything. The identity actions create and remove the verified senders on the Domains page.
ses:SendEmail is the one to grant deliberately: it is the only action here
that can reach the outside world and appear on your AWS bill. You do not need
it to try SESKit — without an AWS connection, sending
goes to Mailpit locally.
Removing an identity is the only destructive thing here, and it is guarded: SESKit deletes the identity in SES only when no other project is still using it.
ses:PutAccountDetails is what requesting production
access from the AWS page uses. It changes nothing about the
account by itself - it files the request AWS reviews - and a key without it
is told so, by name, when the button is pressed. A key made before this line
was here still connects and sends; add the action and the request works.
Delivery events — fifteen more (optional)¶
Read this one before granting it
Everything above is scoped to SES and mostly read-only. This adds permission to create and delete SNS topics and SQS queues in your account — a different kind of trust, and the reason it is a separate policy and a separate button rather than something the connect flow quietly needs.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"sqs:CreateQueue",
"sqs:GetQueueAttributes",
"sqs:SetQueueAttributes",
"sqs:DeleteQueue",
"sqs:ReceiveMessage",
"sqs:DeleteMessage"
],
"Resource": "arn:aws:sqs:*:*:seskit-*"
},
{
"Effect": "Allow",
"Action": [
"sns:CreateTopic",
"sns:Subscribe",
"sns:Unsubscribe",
"sns:DeleteTopic"
],
"Resource": "arn:aws:sns:*:*:seskit-*"
},
{
"Effect": "Allow",
"Action": [
"ses:CreateConfigurationSet",
"ses:DeleteConfigurationSet",
"ses:CreateConfigurationSetEventDestination",
"ses:UpdateConfigurationSetEventDestination",
"ses:DeleteConfigurationSetEventDestination"
],
"Resource": "*"
}
]
}
The SQS and SNS statements are scoped by resource to names beginning
seskit-, so this policy cannot touch anything else you own even by mistake.
That matches the default name and the one the installer generates for each
instance (seskit-a1b2c3-events). If you set EVENT_RESOURCE_PREFIX to
something that does not begin with seskit-, change those ARNs to match.
The delete permissions are there so that removing event reporting, or disconnecting the account, actually cleans up. Granting create without delete would leave SESKit able to make resources in your account and unable to tidy them away.
sqs:SendMessage is deliberately absent. SESKit never writes to the queue —
SNS does, under a queue policy SESKit sets during setup that admits that one
topic and nothing else. Adding it here would grant a permission nothing uses.
Receiving mail — sixteen more (optional)¶
Read this one before granting it
This creates S3 buckets and edits SES receipt rules in your account — the first is storage that holds other people's mail, and the second changes how your account receives it. It is a third policy and a separate button for the same reason the second is. You only need it to receive mail.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:CreateBucket",
"s3:PutBucketPublicAccessBlock",
"s3:PutEncryptionConfiguration",
"s3:PutLifecycleConfiguration",
"s3:PutBucketPolicy",
"s3:DeleteBucket",
"s3:ListBucket"
],
"Resource": "arn:aws:s3:::seskit-*"
},
{
"Effect": "Allow",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::seskit-*/*"
},
{
"Effect": "Allow",
"Action": [
"ses:CreateReceiptRuleSet",
"ses:CreateReceiptRule",
"ses:UpdateReceiptRule",
"ses:DeleteReceiptRule",
"ses:DescribeActiveReceiptRuleSet",
"ses:DescribeReceiptRuleSet",
"ses:SetActiveReceiptRuleSet",
"ses:DeleteReceiptRuleSet"
],
"Resource": "*"
}
]
}
The topic and queue SESKit creates for received mail are named seskit-inbound, so
the SNS and SQS statements in the second policy
already cover them. If you did not grant that policy, receiving needs those two
statements as well.
The bucket statements are scoped by resource to names beginning seskit-, so
this policy cannot touch a bucket you made yourself. The same prefix as the second
policy applies: if you set EVENT_RESOURCE_PREFIX to something that does not begin
with seskit-, change those ARNs to match.
s3:ListBucket is easy to leave out and worth keeping. Without it S3 answers a
request for a message that has expired with access denied instead of not found,
so an ordinary expiry would look like a permissions failure.
The SES receipt-rule actions are granted on *. Whether they can be narrowed to a
single rule set has not been checked, and a policy that claimed a scope AWS does not
enforce would be worse than one that says it has none.
s3:DeleteBucket is there so that turning receiving off, or disconnecting, can
tidy up. A bucket that still holds mail is never emptied: it is left in place and
removed by retention in time.
Starting smaller¶
A reasonable order, if you would rather not grant everything at once:
| Stage | Grant | You can then |
|---|---|---|
| Look around | sts:GetCallerIdentity, ses:GetAccount |
Connect, see your quota and sandbox status |
| Verify senders | + ses:*EmailIdentity |
Add domains and addresses |
| Send for real | + ses:SendEmail |
Deliver through SES |
| Leave the sandbox | + ses:PutAccountDetails |
Request production access from the AWS page |
| See what happened | the second policy | Delivery events and webhooks |
| Receive mail | the third policy | Receiving on a domain, the Inbox, and email.received |
Each stage is usable on its own, and the dashboard says what is missing rather than failing obscurely.