Skip to content

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.