Skip to content
Linuxbeast
Subscribe
  • Home
  • Today in Tech
  • Who is Hiring?
  • About Me
  • Work With Me
  • Contact
Subscribe
Home › AWS › How to Send Email Using AWS SES with Cross-Account Secrets Manager
AWS

How to Send Email Using AWS SES with Cross-Account Secrets Manager

If you're running a multi-account AWS setup, you might need one account to send emails through SES while the SMTP credentials live in another…

Linuxbeast Developer Aug 1, 2025 5 min read 358 views
How to Send Email Using AWS SES with Cross-Account Secrets Manager
Share:
In This Article
How Cross-Account SES Email Works› Prerequisites› Step 1: Add a Resource Policy to the Secret in Account A› Step 2: Grant IAM Permissions in Account B›

How to Send Email Using AWS SES with Cross-Account Secrets Manager

March 9, 2026August 1, 2025 by Linuxbeast Developer
5 min read
358 views

If you’re running a multi-account AWS setup, you might need one account to send emails through SES while the SMTP credentials live in another account’s Secrets Manager. This guide walks you through setting up cross-account Secrets Manager access for AWS SES — the resource policy, IAM permissions, and a working Python script.

How Cross-Account SES Email Works

Account A owns the SES domain and stores SMTP credentials in Secrets Manager. Account B runs the Lambda or script that sends the email. Instead of duplicating credentials, you grant Account B permission to read the secret from Account A.

Advertisement

This needs two things: a resource-based policy on the secret in Account A, and an IAM policy on Account B’s role that allows secretsmanager:GetSecretValue. Both sides have to agree — that’s how cross-account access works in AWS. For a deeper look at the general pattern, see How to Access AWS Secrets Manager from Another Account.

Prerequisites

  • Two AWS accounts — Account A (SES + Secrets Manager) and Account B (runs the email code)
  • A verified SES domain or email address in Account A
  • SES SMTP credentials stored in Secrets Manager (Account A)
  • AWS CLI configured — see How to Install AWS CLI v2 on Ubuntu 22.04 if needed
  • A Lambda function or Python environment in Account B with boto3

Step 1: Add a Resource Policy to the Secret in Account A

In Secrets Manager (Account A), open the secret and attach this resource-based policy:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowCrossAccountSecretAccess",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::123456789012:role/your-lambda-execution-role"
      },
      "Action": "secretsmanager:GetSecretValue",
      "Resource": "*"
    }
  ]
}

Here’s what each field does:

Advertisement
  • Principal — the IAM role ARN from Account B that’s allowed to access the secret. Replace 123456789012 with Account B’s account ID and your-lambda-execution-role with the actual role name.
  • Action — only allows GetSecretValue, which is the minimum permission needed to read the secret.
  • Resource — set to * here, but you can tighten it by using the full secret ARN instead.

If your accounts are in an AWS Organization, you can also add an aws:PrincipalOrgID condition to restrict access to your org:

"Condition": {
  "StringEquals": {
    "aws:PrincipalOrgID": "o-your-org-id"
  }
}

Step 2: Grant IAM Permissions in Account B

The resource policy alone isn’t enough. Account B’s Lambda role also needs an IAM policy that allows reading the secret:

{
  "Effect": "Allow",
  "Action": "secretsmanager:GetSecretValue",
  "Resource": "arn:aws:secretsmanager:us-east-1:111111111111:secret:your-ses-smtp-secret-*"
}
  • Action — same as Account A’s policy, only GetSecretValue.
  • Resource — the full ARN of the secret in Account A. Replace 111111111111 with Account A’s account ID. The -* suffix is needed because Secrets Manager appends a random string to the ARN.

Both policies must be in place. The resource policy says “I trust this role” and the IAM policy says “this role is allowed to reach out.” If either is missing, you’ll get an AccessDeniedException.

Step 3: Python Script to Retrieve Credentials and Send Email

This script pulls SMTP credentials from Secrets Manager in Account A and sends an email through the SES SMTP endpoint:

import boto3
import json
import smtplib
from email.mime.text import MIMEText
from botocore.exceptions import ClientError

AWS_REGION = "us-east-1"
SECRET_ARN = "arn:aws:secretsmanager:us-east-1:111111111111:secret:your-ses-smtp-secret"
SENDER_EMAIL = "noreply@example.com"
RECIPIENT_EMAIL = "recipient@example.com"


def get_ses_credentials():
    client = boto3.client("secretsmanager", region_name=AWS_REGION)

    try:
        response = client.get_secret_value(SecretId=SECRET_ARN)
        secret = json.loads(response["SecretString"])

        return {
            "server": secret["SMTP_SERVER"],
            "port": int(secret["SMTP_PORT"]),
            "username": secret["SMTP_USERNAME"],
            "password": secret["SMTP_PASSWORD"],
        }
    except ClientError as e:
        print(f"Failed to retrieve secret: {e}")
        raise


def send_email():
    creds = get_ses_credentials()

    msg = MIMEText("Hello from AWS SES via cross-account Secrets Manager!")
    msg["Subject"] = "Test Email from Account B"
    msg["From"] = SENDER_EMAIL
    msg["To"] = RECIPIENT_EMAIL

    with smtplib.SMTP(creds["server"], creds["port"]) as server:
        server.starttls()
        server.login(creds["username"], creds["password"])
        server.send_message(msg)

    print("Email sent successfully.")


send_email()

Here’s what the key parts do:

  • boto3.client("secretsmanager") — creates a Secrets Manager client that uses Account B’s IAM role to make API calls.
  • get_secret_value(SecretId=SECRET_ARN) — retrieves the secret. Use the full ARN, not just the name — cross-account access requires the ARN.
  • SENDER_EMAIL — must be a verified email or domain in SES (Account A). This is separate from the SMTP username.
  • smtplib.SMTP(server, port) — connects to the SES SMTP endpoint. The server is typically email-smtp.us-east-1.amazonaws.com and port 587 is for STARTTLS.
  • server.starttls() — upgrades the connection to TLS before sending credentials.

SES SMTP Credentials vs IAM Credentials

SES SMTP credentials are not the same as IAM access keys. When you create SMTP credentials in the SES console, AWS generates a separate username and password for SMTP authentication. These go into Secrets Manager and are used with smtplib. Your IAM credentials are what boto3 uses to call the Secrets Manager API. So there are two layers here: IAM handles the secret retrieval, SMTP credentials handle the email sending.

Why Use This Pattern

  • Single source of truth — credentials exist in one place. Rotate once, every account picks up the change.
  • Least privilege — Account B never stores credentials locally, only reads them at runtime.
  • Auditing — CloudTrail in Account A logs every secret access.
  • Scales easily — same pattern whether you have 2 accounts or 20.

This is the same trust model used for other cross-account resources. If you’ve done cross-account access with AssumeRole or copied S3 objects across accounts, you’ll recognize the pattern.

Troubleshooting

AccessDeniedException when retrieving the secret

Check that both policies (resource policy in Account A and IAM policy in Account B) are in place. Also make sure you’re using the secret’s full ARN in SecretId, not just the name.

SMTP authentication failure

Confirm the SMTP credentials in Secrets Manager are correct and haven’t been rotated without updating the secret value. Also check that the sender email or domain is verified in SES. If SES is still in sandbox mode, you can only send to verified addresses.

Connection timeout on port 587

If your Lambda runs inside a VPC, it might not have internet access. You’ll need a NAT gateway or VPC endpoint for Secrets Manager, plus outbound access for SMTP. Lambda outside a VPC has internet access by default.

Conclusion

That covers sending email through AWS SES with SMTP credentials stored in another account’s Secrets Manager. The main thing to remember is that cross-account access requires policies on both sides — one on the secret and one on the calling role. Once that’s in place, the Python script handles the rest.

For more cross-account patterns, check out Setting Up Cross-Account S3 Upload with Lambda or Setting Up Cross-Account SNS to SQS Subscription in AWS.

Recommended for Linux builders

Contabo VPS hosting for Linux projects

Run Linux servers, automation tools, and self-hosted apps on a Contabo VPS with generous resources and predictable pricing.

n8n VPS

Contabo n8n VPS

A ready-to-configure VPS option for self-hosted n8n automation workflows.

Explore n8n VPSContabo n8n VPS
OpenClaw VPS

Contabo OpenClaw VPS

A dedicated VPS configuration for running OpenClaw projects with your own setup.

Explore OpenClaw VPSContabo OpenClaw VPS
Linux VPS

Contabo

Generous resources at a budget price for home-lab and small servers.

Compare Linux VPSContabo
Our customers sayExcellent★★★★★4.6 out of 5 based on 11,008 reviews★ Trustpilot

Some links are affiliate links. This may earn LinuxBeast a commission at no extra cost to you.

On This Page
How Cross-Account SES Email Works Prerequisites Step 1: Add a Resource Policy to the Secret in Account A Step 2: Grant IAM Permissions in Account B Step 3: Python Script to Retrieve Credentials and Send Email SES SMTP Credentials vs IAM Credentials Why Use This Pattern Troubleshooting AccessDeniedException when retrieving the secret SMTP authentication failure Connection timeout on port 587 Conclusion

Key Takeaways

  • Two AWS accounts — Account A (SES + Secrets Manager) and Account B (runs the email code)
  • A verified SES domain or email address in Account A
  • SES SMTP credentials stored in Secrets Manager (Account A)
  • AWS CLI configured — see How to Install AWS CLI v2 on Ubuntu 22.04 if needed

Tags

AWS SES Boto3 Cross-Account Email Automation IAM Lambda Python Secrets Manager

Level Up Your DevOps Skills

Practical guides, tutorials, and real-world best practices delivered weekly.

No spam, ever. Unsubscribe anytime.

Popular Articles

How to Set Up a DevOps Onboarding Environment on Windows with WSL2 and AI Tools How to Set Up a DevOps Onboarding Environment on Windows with WSL2 and AI Tools 475 views How to Fix WSL CreateVm/HCS/ERROR_FILE_NOT_FOUND on Windows How to Fix WSL CreateVm/HCS/ERROR_FILE_NOT_FOUND on Windows 11.3K views How to Mount S3 Bucket on Ubuntu with s3fs-fuse How to Mount S3 Bucket on Ubuntu 22.04 with S3FS Fuse 10.3K views How to Install and Configure Claude Code CLI on Windows PowerShell How to Install and Configure Claude Code CLI on Windows PowerShell 7.7K views How to Install Ubuntu in WSL 2 on Windows 10 and 11 How to Install Ubuntu in WSL 2 on Windows 10 and Windows 11 7K views How to Configure MCP Servers in VS Code, Claude Desktop, and Claude Code on WSL How to Configure MCP Servers in VS Code, Claude Desktop, and Claude Code on WSL 5.8K views
Advertisement

Sponsored

Contabo Linux VPS hosting plans

Some links are affiliate links. This may earn LinuxBeast a commission at no extra cost to you.

Understanding Lambda's Event and Context Parameters in Python← PreviousUnderstanding Lambda’s Event and Context Parameters in PythonNext →How to Parse Custom Logs in Datadog Using Grok RulesHow to Parse Custom Logs in Datadog Using Grok Rules

Related Articles

How to Configure AWS SSO CLI Access for Windows PowerShell
AWS

How to Configure AWS SSO CLI Access for Windows PowerShell

741 views  •  Feb 28, 2026
How to Mount S3 Bucket on Ubuntu with s3fs-fuse
AWS

How to Mount S3 Bucket on Ubuntu 22.04 with S3FS Fuse

10.3K views  •  Apr 13, 2024
How to Set Up Cross-Account Access in AWS with AssumeRole
AWS

How to Set Up Cross-Account Access in AWS with AssumeRole

950 views  •  May 15, 2024
linuxbeast-logo

Built for DevOps, Cloud & Automation.

Popular Topics

  • AWS
  • WSL & Windows
  • DevOps
  • Developer Tools
  • Git
  • Programming

Resources

  • Home
  • Today in Tech
  • Who is Hiring?
  • About Me
  • Work With Me
  • Contact

Legal

  • Privacy Policy
  • Cookie Policy

Stay updated with LinuxBeast

Get the best Linux, DevOps, and Cloud content delivered to your inbox.

No spam, ever. Unsubscribe anytime.

© 2026 LinuxBeast. All rights reserved. Made with ♥ for the DevOps community.
© 2026 Linuxbeast · All rights reserved.
Manage Consent
To provide the best experiences, we use technologies like cookies to store and/or access device information. Consenting to these technologies will allow us to process data such as browsing behavior or unique IDs on this site. Not consenting or withdrawing consent, may adversely affect certain features and functions.
Functional Always active
The technical storage or access is strictly necessary for the legitimate purpose of enabling the use of a specific service explicitly requested by the subscriber or user, or for the sole purpose of carrying out the transmission of a communication over an electronic communications network.
Preferences
The technical storage or access is necessary for the legitimate purpose of storing preferences that are not requested by the subscriber or user.
Statistics
The technical storage or access that is used exclusively for statistical purposes. The technical storage or access that is used exclusively for anonymous statistical purposes. Without a subpoena, voluntary compliance on the part of your Internet Service Provider, or additional records from a third party, information stored or retrieved for this purpose alone cannot usually be used to identify you.
Marketing
The technical storage or access is required to create user profiles to send advertising, or to track the user on a website or across several websites for similar marketing purposes.
  • Manage options
  • Manage services
  • Manage {vendor_count} vendors
  • Read more about these purposes
View preferences
  • {title}
  • {title}
  • {title}
Esc to closeCtrl+K to open