AWS Database Blog

Automating Amazon RDS and Amazon Aurora recommendations via notification with AWS Lambda, Amazon EventBridge, and Amazon SES

Amazon RDS and Amazon Aurora provide automated recommendations for RDS and automated recommendations for Aurora to optimize your database instances and Aurora clusters and follow best practices. These recommendations are essential for maintaining the performance, security, and reliability of your database environment. Without notification automation, you must manually check the console for new recommendations, which is time consuming and risks overlooking important optimization opportunities.

In this post, we walk through a solution that automates the notification of Amazon RDS and Aurora recommendations through email using AWS Lambda, Amazon EventBridge and Amazon Simple Email Service (Amazon SES).

Solution overview

This solution uses AWS Lambda and Amazon EventBridge to retrieve RDS and Aurora recommendations periodically and send them as formatted HTML email messages using Amazon SES. The following architecture diagram illustrates how EventBridge triggers the Lambda function, which interacts with RDS and Aurora to retrieve recommendations, processes this information, and uses Amazon SES to deliver formatted reports to users, creating a fully automated notification system.

Note: This solution retrieves recommendations for RDS and Aurora instances within a single AWS Region. If you need to monitor recommendations across multiple Regions, you will need to deploy this solution separately in each Region.

 Architecture diagram showing user interaction with SES, Lambda, EventBridge, RDS and Aurora

To implement this solution, you need to complete the following high-level steps:

  1. Set up tagging on RDS and Aurora DB instances to filter recommendations by environment type.
  2. Set up Amazon SES to enable email delivery of recommendations.
  3. Create an AWS Identity and Access Management (IAM) policy and role for Lambda to securely access required services.
  4. Create the Lambda function to retrieve and format Amazon RDS and Aurora recommendations as emails.
  5. Create an EventBridge schedule to invoke the Lambda function execution at defined intervals.

Prerequisites

  • An AWS Account
  • One or more RDS/Aurora instances

Set up tagging on Amazon RDS instances, Aurora DB instances, and Aurora clusters

To tag your Amazon RDS instances for proper filtering:

  1. In the Aurora and Amazon RDS console, choose Databases in the left navigation pane.
  2. Choose the instance and cluster you want to tag.
  3. Choose the Tags tab on the instance details page.
  4. Choose Manage tags.
  5. Choose Add new tag.
  6. For Key, enter Environment.
  7. For Value, enter one of the following: Production, Staging, Development, or Test.
  8. Choose Save changes.

You’ve now tagged your Amazon RDS instances, Aurora DB instances and Aurora clusters with environment information that allows the Lambda function to filter recommendations based on operational importance, focusing on critical environments like production and staging while excluding less critical ones.

Set up Amazon SES for email delivery of notifications

To configure Amazon SES:

  1. In the Amazon SES console, choose Identities in the left navigation pane under Configuration.
  2. Choose Create identity.
  3. Choose Email address and enter the email address you want to use as both sender and recipient.
  4. Follow the verification process by selecting the link in each verification email you receive.

After verifying your email address with Amazon SES, you have established a trusted sender identity that the Lambda function will use to deliver recommendation notifications.

Create an IAM policy and role for Lambda

Create an IAM policy and role for the Lambda function to be able to execute the RDSRecommendations API and perform essential tasks including retrieving recommendation data, filtering based on tags, accessing CloudWatch logs, and sending emails through Amazon SES. First, complete the following steps to create the IAM policy:

  1. In the IAM console, choose Policies in the left navigation pane.
  2. Choose Create policy.
  3. On the JSON tab, paste the following into the policy editor:
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ],
      "Resource": "arn:aws:logs:${REGION}:${ACCOUNT_ID}:log-group:/aws/lambda/${LAMBDA_FUNCTION_NAME}:*"
    },
    {
      "Effect": "Allow",
      "Action": "ses:SendEmail",
      "Resource": "arn:aws:ses:${REGION}:${ACCOUNT_ID}:identity/${VERIFIED_EMAIL_ADDRESS}"
    },
    {
            "Effect": "Allow",
            "Action": [
                "rds:DescribeDBRecommendations",
                "rds:ListTagsForResource"
            ],
            "Resource": "*"
        }
  ]
}

Replace the following placeholders:

Placeholder Description Example
${REGION} Your AWS region us-east-1
${ACCOUNT_ID} Your AWS account ID 123456789012
${LAMBDA_FUNCTION_NAME} Your Lambda function name Send-RDS-Recommendations-Email
${VERIFIED_EMAIL_ADDRESS} Your verified Sender email address in SES Alejandro_rosalez@example.com
  1. Choose Review policy.
  2. For Policy Name, enter a policy name (such as Recommendationsv1-IAM-policy).
  3. Choose Create policy.

This policy you have created grants your Lambda function the specific permissions needed to publish CloudWatch logs, sending emails via SES, and accessing RDS and Aurora recommendations data along with resource tags.

Now you can create your IAM role to attach these permissions to your Lambda function:

  1. Choose Roles in the left navigation pane.
  2. Choose Create role.
  3. For Select type of trusted entity, choose AWS service.
  4. For Use case, choose Lambda.
  5. Choose Next.
  6. For IAM policy name, enter a name (such as Recommendationsv1-IAM-policy).
  7. Under Permissions policies, select the policy you created in the preceding procedure.
  8. Choose Next.
  9. For Role name, enter a name (such as Recommendationsv1-IAM-Role).
  10. Choose Create role.

You have now created an IAM role that links the permissions policy to your Lambda function. This role establishes the security boundary for your function, ensuring it has exactly the access needed to retrieve recommendations and send notifications.

Create the Lambda function to retrieve and format Amazon RDS and Aurora recommendations as emails

To create the Lambda function:

  1. In the Lambda console, choose Create function.
  2. Choose Author from scratch.
  3. For Function name, enter a name (such as Send-RDS-Recommendations-Email).
  4. For Runtime, choose Python 3.13 or higher.
  5. For Architecture, leave x86_64 which is default.
  6. For Execution role, select Use an existing role.
  7. Change default execution role to use an existing role, choose the role you created in the preceding procedure.
  8. Choose Create function.
  9. On the Lambda function’s details page, go to the Configuration tab.
  10. Choose General configuration in the left navigation pane.
  11. Choose Edit.
  12. For Timeout, enter 10 seconds or higher (the default timeout of 3 seconds may not be sufficient for retrieving and processing multiple recommendations).
  13. Choose Save.
  14. Now, go to the Code tab. In the Code Source section replace the lambda_function.py sample code with the following (change the Sender and Recipient email addresses to match the Amazon SES identity that you created and verified earlier in this post):

Note: When implementing this solution, you can customize several key parameters

Email Configuration:

  • SENDER_EMAIL: Your verified SES sender email address
  • RECIPIENT_EMAILS: List of verified SES recipient email addresses
  • MAX_RECOMMENDATIONS_PER_EMAIL: Number of recommendations per email (default: 20)

Environment Filtering:

  • REQUIRED_TAGS: List of environments to monitor (default: [‘Production’, ‘Staging’])
  • TAG_KEY: Tag key for environment identification (default: ‘Environment’)

Category Filtering:

  • DESIRED_CATEGORIES: Types of recommendations to include. Valid options:
    • performance efficiency
    • security
    • operational excellence
    • reliability
    • cost optimization
    • sustainability

You can customize DESIRED_CATEGORIES to focus on specific aspects of your database infrastructure. The categories are independent, allowing you to choose any combination. For example, below desired categories filter the recommendations to security and performance efficiency.


DESIRED_CATEGORIES = [
    'security',
    'performance efficiency'
]

The following Python code retrieves RDS and Aurora recommendations, filters them based on your environment tags, formats them into a readable HTML email, and sends them via Amazon SES.

import json, boto3, logging
from botocore.exceptions import ClientError
from collections import defaultdict
logger = logging.getLogger()
logger.setLevel(logging.INFO)
# Maximum number of recommendations to include in a single email
MAX_RECOMMENDATIONS_PER_EMAIL = 20
# List of environments to filter recommendations for
REQUIRED_TAGS = ['Production', 'Staging']
# The tag key used to identify environment type in AWS resource tags
TAG_KEY = 'Environment'
# Define categories to filter recommendations
DESIRED_CATEGORIES = [
    'performance efficiency',
    'security',
    'operational excellence',
    'reliability'
]
# Email Configuration
# Sender email address (must be verified in Amazon SES)
SENDER_EMAIL = "Alejandro_rosalez@example.com"
# List of recipient email addresses (must be verified in Amazon SES)
RECIPIENT_EMAILS = ["Alejandro_rosalez@example.com"]
# HTML template for email formatting
HTML_TEMPLATE = """<html><head><style>
    body{font-family:Arial,sans-serif}
    table{border-collapse:collapse;width:100%;margin-bottom:20px}
    th,td{border:1px solid #ddd;padding:8px;text-align:left}
    th{background-color:#f2f2f2}
    .severity-high{color:red}
    .severity-medium{color:orange}
    .severity-low{color:green}
    .severity-informational{color:blue}
    </style></head><body><h1>RDS DB Recommendations</h1>"""
def get_filtered_recommendations(rds_client): #Function to get and filter recommendations
    all_recs = rds_client.describe_db_recommendations().get('DBRecommendations', [])
    filtered_recs, counts = [], {'active': 0, 'dismissed': 0, 'pending': 0, 'resolved': 0, 'filtered': 0}
    logger.info(f"Total recommendations before filtering: {len(all_recs)}")
    for rec in all_recs:
        status = rec.get('Status', '').lower()
        category = rec.get('Category', '').lower()
      
        if status == 'resolved':
            counts['resolved'] += 1
            continue
        if status in ['active', 'dismissed', 'pending']:
            counts[status] += 1
            # Check if the recommendation category is in our desired categories
            if category in DESIRED_CATEGORIES:
                if 'pg:' in rec.get('ResourceArn', ''):
                    filtered_recs.append(rec)
                    logger.info(f"Including parameter group recommendation for {rec.get('ResourceArn')}")
                    continue
                try:
                    resource_arn = rec.get('ResourceArn')
                    tags = rds_client.list_tags_for_resource(ResourceName=resource_arn)['TagList']
                    env_tag = next((tag['Value'].lower() for tag in tags if tag['Key'] == TAG_KEY), None)
                    if env_tag in [tag.lower() for tag in REQUIRED_TAGS]:
                        filtered_recs.append(rec)
                    else:
                        counts['filtered'] += 1
                except ClientError as e:
                    logger.error(f"Error getting tags for {resource_arn}: {str(e)}")
            else:
                counts['filtered'] += 1
    # Log recommendation counts
    logger.info(f"Counts - Active: {counts['active']}, Dismissed: {counts['dismissed']}, " +
                f"Pending: {counts['pending']}, Resolved: {counts['resolved']}, Filtered: {counts['filtered']}")
    return filtered_recs
def convert_to_html(response):  #Function to convert recommendations into HTML formatted email content
    grouped_recs = defaultdict(list)
    for rec in response.get('DBRecommendations', []):
        if rec.get('Status', '').lower() != 'resolved':
            grouped_recs[rec.get('Recommendation', 'N/A')].append(rec)
    html = HTML_TEMPLATE
    if not grouped_recs:
        return html + "<p>No active, dismissed, or pending recommendations at this time.</p></body></html>"
    for rec_type, resources in grouped_recs.items():
        first = resources[0]
        html += f"<h2>Recommendation: {rec_type}</h2><p><strong>Description:</strong> {first.get('Description', 'N/A')}</p><p><strong>Category:</strong> {first.get('Category', 'N/A')}</p><p><strong>Impact:</strong> {first.get('Impact', 'N/A')}</p><h3>Affected Resources:</h3>"
        for res in resources:
            status = res.get('Status', 'N/A').lower()
            status_color = '#ff4444' if status == 'active' else '#808080' if status == 'dismissed' else '#ffa500' if status == 'pending' else '#000000'
            html += f"<table><tr><th>Attribute</th><th>Value</th></tr><tr><td>Resource ARN</td><td>{res.get('ResourceArn', 'N/A')}</td></tr><tr><td>Severity</td><td class='severity-{res.get('Severity', 'low').lower()}'>{res.get('Severity','N/A')}</td></tr><tr><td>Status</td><td style='color: {status_color}'><strong>{res.get('Status', 'N/A')}</strong></td></tr><tr><td>Created Time</td><td>{res.get('CreatedTime', 'N/A')}</td></tr><tr><td>Updated Time</td><td>{res.get('UpdatedTime', 'N/A')}</td></tr><tr><td>Detection</td><td>{res.get('Detection', 'N/A')}</td></tr></table>"
            html += "<h4>Recommended Actions:</h4><ul>" + ''.join([f"<li>{action.get('Operation', 'N/A')}: {', '.join([f'{param['Key']}={param['Value']}' for param in action.get('Parameters', [])])}</li>" + (f"<ul>{''.join([f'<li>{attr['Key']}: {attr['Value']}</li>' for attr in action.get('ContextAttributes', [])])}</ul>" if action.get('ContextAttributes') else '') for action in res.get('RecommendedActions', [])]) + "</ul>"
        html += f"<h3>Additional Information:</h3><p>{first.get('AdditionalInfo', 'N/A')}</p><h3>Useful Links:</h3><ul>" + ''.join([f"<li><a href='{l.get('Url', '#')}'>{l.get('Text', 'Link')}</a></li>" for l in first.get('Links', [])]) + "</ul><hr>"
    return html + "</body></html>"
def lambda_handler(event, context): #Main AWS Lambda function handler
    try:
        rds, ses = boto3.client('rds'), boto3.client('ses')
        # Get AWS Region and Account ID
        region = context.invoked_function_arn.split(":")[3]
        account_id = context.invoked_function_arn.split(":")[4]
        recs = get_filtered_recommendations(rds)
        if not recs:
            logger.info("No recommendations found for Production or Staging environments")
            return {'statusCode': 200, 'body': json.dumps('No recommendations to send')}
        chunks = [recs[i:i + MAX_RECOMMENDATIONS_PER_EMAIL] for i in range(0, len(recs), MAX_RECOMMENDATIONS_PER_EMAIL)]
        for i, chunk in enumerate(chunks):
            subject = f"Amazon RDS and Aurora Recommendations - Account: {account_id} - Region: {region}"
            if len(chunks) > 1:
                subject += f" (Part {i+1}/{len(chunks)})"
            ses.send_email(Source=SENDER_EMAIL, Destination={'ToAddresses': RECIPIENT_EMAILS}, Message={'Subject': {'Data': subject, 'Charset': 'UTF-8'}, 'Body': {'Html': {'Data': convert_to_html({'DBRecommendations': chunk}), 'Charset': 'UTF-8'}}})
        return {'statusCode': 200, 'body': json.dumps('Successfully sent RDS and Aurora recommendations via SES')}
    except ClientError as e:
        error_msg = f"An error occurred: {str(e)}"
        logger.error(error_msg)
        return {'statusCode': 500, 'body': json.dumps(error_msg)}
  1. Choose Deploy.

Create an EventBridge schedule to invoke the Lambda function execution at defined intervals

Let’s say you want to send the Amazon RDS and Aurora recommendation notifications every day at 21:00 UTC. Complete the following steps to create such a schedule in EventBridge:

  1. In the EventBridge console, choose Schedules in the left navigation pane.
  2. Choose Create schedule.
  3. For Name, enter a name (for example, SendRDSDBRecommendationEmail).
  4. For Occurrence, select Recurring schedule.
  5. For Time zone, choose UTC.
  6. For Schedule type, select Cron-based schedule.


AWS EventBridge schedule configuration interface

  1. For Cron expression, enter the following values:
    • Minutes: 00
    • Hours: 21
    • Day of month: *
    • Month: *
    • Day of the week: ?
    • Year: *
  2. For Flexible time window, select 10 minutes
  3. Choose Next.
  4. For Target API, select Templated targets.
  5. Select AWS Lambda Invoke.
  6. Choose the Lambda function (such as Send-RDS-Recommendations-Email) to be invoked and then choose Next.
  7. Choose Next on Settings.
  8. Choose Create schedule.

Recommendations notification provides a comprehensive assessment including the recommendation type, description, category, impact, affected resource details, severity level, and recommended actions for database improvements. When you receive these notifications, review the severity and impact to prioritize your actions. For high and medium severity items, consider implementing the recommended actions promptly to avoid potential performance issues or outages. For low severity recommendations, you can plan implementation during your next maintenance window. Each notification includes specific steps on how to implement the recommendation, making it easier to take action.

Sample email

Sample Amazon RDS and Aurora recommendation

Clean up

To clean up your resources, complete the following steps:

Conclusion

In this post, we shared a solution to guide you through setting up sending an automated notifications for Amazon RDS and Aurora recommendations. This post’s solution covers all the major steps, including Amazon SES setup, IAM policy and role creation, Lambda function creation, and scheduling using EventBridge. By implementing this solution, you can stay proactive about RDS optimizations without manual monitoring, while ensuring critical recommendations aren’t missed. You can also customize the filtering logic to match your organization’s priorities. If you have any comments or questions, leave them in the comments section

About the authors

Kedar Yarlapati

Kedar Yarlapati

Kedar is a senior cloud support engineer at AWS with 14 years of experience in database engineering and infrastructure architecture. He is an Aurora MySQL, RDS MySQL, and Amazon RDS for SQL Server subject matter expert. Kedar provides guidance and technical assistance to customers implementing AWS Cloud solutions.

Sravan Gogana

Sravan Gogana

Sravan is a senior cloud support database engineer at AWS with 13 years of experience. He specializes in Amazon RDS core services, Amazon Aurora, Amazon RDS for SQL Server, and MySQL. Sravan helps customers navigate their cloud journey through expert guidance on database migrations and optimization.

Jeril Jose

Jeril Jose

Jeril is a database specialist consultant with more than 16 years of experience in Microsoft SQL Server and other database technologies. He helps customers architect, migrate, and optimize their database solutions to AWS. Before joining AWS, Jeril supported production and mission-critical database implementation across financial and retail segments.

Srinivaasan Arumugam Sampath

Srinivaasan Arumugam Sampath

Srinivaasan is a cloud support DBE II at AWS. He has been with AWS for more than 2.5 years with more than 14 years of experience in database. Srinivaasan is an Amazon RDS for MySQL and Amazon RDS SQL Server subject matter expert. He works with AWS customers to provide technical guidance and solutions to resolve issues in the AWS Cloud.