Wraps Logo
Credentials and permissionsAccessDeniedException

AWS SES "AccessDenied": Causes and Fix

How to read an SES AccessDenied message, which IAM action and resource it names, and the four things that deny a call AWS otherwise authenticated.

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.com

AWS 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. 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. 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-identity
  3. 3. 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. 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.