能否在Lambda触发器中获取AWS Cognito用户属性,实现S3触发发邮件?
解决方案
首先明确:Lambda触发器中完全可以获取AWS Cognito用户属性,你遇到的两个核心问题分别是事件结构解析错误、Lambda执行角色权限缺失,按以下步骤排查实现即可:
1. 解决KeyError: 'Records'报错
这个报错的核心原因是Lambda接收到的入参event中没有Records字段,对应排查方案:
- 99%的场景是你在Lambda控制台手动测试时使用了默认测试模板,没有替换为S3触发事件,配置测试事件时选择官方提供的
s3-put模板即可复现真实触发场景 - 先在
lambda_handler函数第一行添加print(json.dumps(event)),到CloudWatch日志中查看实际收到的入参结构,确认S3上传触发时的事件确实包含Records字段即可排除该问题 - 你提到的上传S3时能看到监控打点,说明触发逻辑是正常的,报错大概率是测试时用了错误的事件模板导致
2. 解决Cognito list_users接口在Lambda运行失败的问题
本地运行能成功是因为你本地AWS账号密钥有Cognito操作权限,而Lambda运行时依赖绑定的执行角色(Execution Role)的权限,默认没有Cognito访问权限,按以下配置:
- 打开Lambda控制台的「配置」-「权限」板块,点击执行角色的跳转链接进入IAM角色配置页
- 给角色添加如下权限策略,替换尖括号中的内容为你实际的资源信息:
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": "cognito-idp:ListUsers", "Resource": "arn:aws:cognito-idp:<AWS区域>:<AWS账号ID>:userpool/<Cognito用户池ID>" } ] }
- 确认你代码中的
UserPoolId参数已经填入了实际的Cognito用户池ID,不要留空
3. 完整实现S3上传后给所有Cognito用户发邮件的逻辑
发邮件推荐使用AWS SES服务,需要先在SES控制台验证你作为发件方的邮箱,若要发给未验证的收件邮箱需要提交工单解除SES沙箱限制,再给Lambda执行角色添加SES发送权限:
{ "Effect": "Allow", "Action": "ses:SendEmail", "Resource": "*" }
完整代码示例:
import boto3 import urllib.parse import json # 初始化AWS客户端 s3 = boto3.client('s3') cognito_client = boto3.client('cognito-idp') ses_client = boto3.client('ses', region_name='<SES服务所在区域>') # 固定配置项,替换为你实际的资源信息 USER_POOL_ID = '<Cognito用户池ID>' SENDER_EMAIL = '<SES验证过的发件邮箱>' EMAIL_SUBJECT = 'S3存储桶新文件上传通知' def lambda_handler(event, context): # 打印入参方便排查问题 print("Received event: ", json.dumps(event)) # 解析S3触发事件 try: bucket = event['Records'][0]['s3']['bucket']['name'] key = urllib.parse.unquote_plus(event['Records'][0]['s3']['object']['key'], encoding='utf-8') except KeyError as e: print(f"Event structure error, missing field: {e}") raise e # 读取S3文件内容(保留你原有逻辑) try: s3_resp = s3.get_object(Bucket=bucket, Key=key) file_content = s3_resp["Body"].read().decode() data = json.loads(file_content) print("S3 file content: ", data) except Exception as e: print(f"Read S3 file failed: {e}") raise e # 拉取所有Cognito用户的邮箱,处理分页(用户量超过60需要翻页) try: all_users = [] pagination_token = None while True: if pagination_token: cognito_resp = cognito_client.list_users(UserPoolId=USER_POOL_ID, PaginationToken=pagination_token) else: cognito_resp = cognito_client.list_users(UserPoolId=USER_POOL_ID) all_users.extend(cognito_resp['Users']) pagination_token = cognito_resp.get('PaginationToken') if not pagination_token: break # 提取有效邮箱,可根据需求过滤已验证的邮箱 email_list = [] for user in all_users: for attr in user['Attributes']: if attr['Name'] == 'email' and attr['Value']: # 可选校验邮箱是否已验证:if next((a for a in user['Attributes'] if a['Name'] == 'email_verified'), {}).get('Value') == 'true' email_list.append(attr['Value']) # 邮箱去重 email_list = list(set(email_list)) print("Target email list: ", email_list) except Exception as e: print(f"Fetch Cognito users failed: {e}") raise e # 批量发送邮件 try: for email in email_list: email_body = f""" <p>您好:</p> <p>S3存储桶 {bucket} 已上传新文件:{key}</p> <p>文件内容摘要:{json.dumps(data, indent=2, ensure_ascii=False)}</p> """ ses_client.send_email( Source=SENDER_EMAIL, Destination={'ToAddresses': [email]}, Message={ 'Subject': {'Data': EMAIL_SUBJECT, 'Charset': 'UTF-8'}, 'Body': {'Html': {'Data': email_body, 'Charset': 'UTF-8'}} } ) print(f"Successfully sent email to {len(email_list)} users") return 'Success!' except Exception as e: print(f"Send email failed: {e}") raise e
4. 额外注意事项
- 如果Cognito用户量超过1000,建议把发邮件逻辑拆分到SQS队列异步处理,避免Lambda运行超时
- Lambda默认超时时间是3秒,建议调整到30秒以上,避免接口调用超时导致执行失败
- SES沙箱环境默认只能发给已经验证过的邮箱,需要给任意邮箱发信要提交AWS工单解除沙箱限制
内容的提问来源于stack exchange,提问作者s hyun
相关产品推荐
相关产品推荐

