Guide

Connect your AWS SES account with an IAM role

BYOC sends through your own AWS SES account. This guide creates the IAM role SendFleet assumes, explains what the ExternalId condition is for, and gives you the permissions policy to attach. About ten minutes, and nothing here sends a test email.

What you are creating

One IAM role in your AWS account, with two policies attached:

  • A trust policy, which says who is allowed to assume the role. SendFleet is the only party, and only with the right ExternalId.
  • A permissions policy, which says what SendFleet may do once it has the role. Eleven SES actions and nothing else.

Your AWS credentials are never shared with SendFleet and never appear in SendFleet's systems. SendFleet assumes the role for the length of a send, using a short-lived session.

Step 1: create the role

  1. Open the role creator

    In the AWS console go to IAM → Roles → Create role, then choose Custom trust policy rather than any of the listed service options.
  2. Name it SendFleet-BYOC-Role

    The role name must be exactly SendFleet-BYOC-Role, case sensitive. SendFleet only assumes roles with that name, so a different name will validate and then fail to connect.
  3. Paste the trust policy

    Use the copy at Dashboard → Settings → BYOC, not the template below. See the note under the sample.
IAM trust policy, template shape
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::<SENDFLEET_ACCOUNT_ID>:root"
      },
      "Action": "sts:AssumeRole",
      "Condition": {
        "StringEquals": {
          "sts:ExternalId": "<YOUR_EXTERNAL_ID>"
        }
      }
    }
  ]
}

Pasted as it stands this will not work: the two angle-bracket values are placeholders. The version in your dashboard has both of them filled in.

What the ExternalId is for

The sts:ExternalId condition is what stops anyone else from borrowing your role.

A role ARN is not a secret. If yours leaks, anyone who finds it can try to assume the role directly against your account. Without the condition, that attempt would succeed if the trust policy granted it. This is the confused deputy problem: a party that is trusted to act on your behalf being talked into acting for somebody else.

With the condition in place, an assume-role call only succeeds if it also carries the exact ExternalId that was generated for your account. SendFleet holds that value and nobody else does, so a leaked ARN is not enough. Keeping the condition costs you nothing and closes the hole.

Step 2: attach the permissions policy

Attach a permissions policy to the role, either as an inline policy or as a customer-managed one. These are the only eleven actions SendFleet uses.

IAM permissions policy
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "SendFleetSESPermissions",
      "Effect": "Allow",
      "Action": [
        "ses:SendEmail",
        "ses:SendRawEmail",
        "ses:VerifyDomainIdentity",
        "ses:VerifyDomainDkim",
        "ses:GetIdentityVerificationAttributes",
        "ses:GetIdentityDkimAttributes",
        "ses:GetIdentityMailFromDomainAttributes",
        "ses:SetIdentityMailFromDomain",
        "ses:DeleteIdentity",
        "ses:GetAccountSendingEnabled",
        "ses:GetSendQuota"
      ],
      "Resource": "*"
    }
  ]
}

What each permission is for

  • ses:SendEmail, ses:SendRawEmail: delivering your email. SendRawEmail is the one that carries attachments.
  • ses:VerifyDomainIdentity, ses:VerifyDomainDkim: registering a sending domain and asking for its DKIM records.
  • ses:GetIdentityVerificationAttributes, ses:GetIdentityDkimAttributes: reading back the verification state so the dashboard can show you whether it passed.
  • ses:GetIdentityMailFromDomainAttributes, ses:SetIdentityMailFromDomain: configuring and checking the custom MAIL FROM subdomain.
  • ses:DeleteIdentity: removing the SES identity when you delete a domain from SendFleet.
  • ses:GetAccountSendingEnabled, ses:GetSendQuota: confirming that sending is enabled and that you are out of the sandbox.

Step 3: connect it

  1. Open the BYOC settings

    Go to Dashboard → Settings → BYOC. The trust policy template with your values in it is on this page.
  2. Enter your role ARN and region

    Paste the role ARN, which is the arn:aws:iam::…:role/SendFleet-BYOC-Role value at the top of the role's detail page in the AWS console, and select the SES region your account uses in that same region list.
  3. Choose Validate & save

    SaveFleet checks the connection before accepting it. A success here means the role exists, the trust policy is right and the permissions are attached.
  4. Add a sending domain

    Once the connection validates, add a domain under Dashboard → Domains. It is registered in your own SES account and needs the DNS records covered in the DNS Records reference.

If validation fails

  • The role name is not exactly SendFleet-BYOC-Role. Check for a trailing space, a different case, or a suffix someone added.
  • The ExternalId does not match. It is case sensitive. Copy it again from the dashboard rather than retyping it.
  • The permissions policy is on the user, not the role. It has to be attached to the role itself.
  • The region does not match the identity's region. An SES identity lives in one region, and the role has to be able to reach that one.
  • The account is still in the SES sandbox. Check the sending enabled state in SES itself.

Next

With the role connected and a domain added, the next two steps are the DNS records for that domain and then your first send. The DNS Records reference has every record and the one-request send guide has working samples in five languages.