The error
User: arn:aws:iam::123456789012:user/dev is not authorized to perform: ses:SendEmail on resource: arn:aws:ses:us-east-1:123456789012:identity/example.comAWS exception AccessDeniedException. The Wraps CLI reports it as IAM_PERMISSION_DENIED.
Why this happens
AWS knew exactly who you were and refused anyway. That makes this the most readable failure in the set: the message names the principal, the action and the resource. Everything you need to fix it is in the string, provided you read all three parts.
- The identity policy does not grant the named action.
- The policy grants the action but not on the named resource.
- A permissions boundary or service control policy denies it above the identity policy.
- An identity policy on the SES identity itself denies this principal.
How to fix it
1. Read the three parts of the message
The principal is who was refused, the action is the exact IAM action to grant, and the resource is the ARN to grant it on. A policy that grants ses:SendEmail on a different identity ARN produces this error even though it looks correct.
2. Confirm the principal is the one you meant
The ARN in the message is authoritative and often surprising, especially in CI where a role is assumed on your behalf.
aws sts get-caller-identity3. Grant the action on the resource that was named
Add the specific action to the identity's policy, scoped to the resource in the message. Grant what was denied rather than widening to a wildcard to make the error go away.
4. Check for a deny above the identity policy
If the identity policy plainly allows it, look for a permissions boundary or an organization service control policy. An explicit deny at either level wins over any allow below it.
How to stop it recurring
Check permissions before the deploy, not during
The set of IAM actions a send path needs is knowable up front. Verifying them ahead of time is cheaper than discovering them one denial at a time.
Keep CI and local identities aligned
Most surprise denials are a CI role that was never granted what a developer's own identity already had.
Common questions
Which IAM action does SES need for sending?
Read it out of the message rather than guessing. The AccessDenied string names the exact action that was denied, which is the one to grant.
Why is my policy allowing this but the call still denied?
Either the resource ARN in the policy does not match the one in the message, or an explicit deny in a permissions boundary or service control policy is overriding the allow.
Is AccessDenied the same as a credentials problem?
No, and the difference matters. AccessDenied means authentication succeeded and authorization failed. Credential errors mean AWS never established who you were.
Why does it work locally but not in CI?
Different principals. The ARN in the error tells you which identity CI actually assumed, and that identity usually has a narrower policy than your own.
What Wraps does about this
The Wraps CLI maps AccessDenied, AccessDeniedException and UnauthorizedAccess to IAM_PERMISSION_DENIED, quotes the action back to you, and points at `wraps permissions --json`, which prints the actions a given command needs. Changing an IAM policy is a change in your AWS account, so it stays yours to make.

