如何在私有子网的Elastic Beanstalk Spring Boot应用中使用AWS SES
我有一个基于Spring Boot的应用,使用AWS SDK v2 for Java的software.amazon.awssdk.services.sesv2.SesV2Client执行添加联系人、发送邮件等操作,开发环境正常,但生产环境部署在私有子网(通过Elastic Beanstalk的EC2实例),依赖VPC端点和AWS服务通信。尝试为SES配置VPC端点时,发现只有com.amazonaws.us-east-1.email-smtp可用,无法支持SDK的调用,应用报错连接超时:
Jul 24 22:46:15 ip-10-0-2-126 web: 2022-07-24 22:46:15.999 DEBUG 1747 --- [nio-5000-exec-6] .m.m.a.ExceptionHandlerExceptionResolver : Using @ExceptionHandler com.oddsapi.odysseyapi.controllers.exceptionhandling.GlobalControllerExceptionHandler#defaultErrorHandler(HttpServletRequest, Exception)
Jul 24 22:46:16 ip-10-0-2-126 web: 2022-07-24 22:46:15.999 DEBUG 1747 --- [nio-5000-exec-6] o.s.web.servlet.DispatcherServlet : Failed to complete request: software.amazon.awssdk.core.exception.SdkClientException: Unable to execute HTTP request: Connect to email.us-east-1.amazonaws.com:443 [email.us-east-1.amazonaws.com/52.5.91.104, email.us-east-1.amazonaws.com/18.206.141.93, email.us-east-1.amazonaws.com/52.20.100.70, email.us-east-1.amazonaws.com/34.230.97.163, email.us-east-1.amazonaws.com/3.217.95.76, email.us-east-1.amazonaws.com/34.237.162.86, email.us-east-1.amazonaws.com/54.164.105.38, email.us-east-1.amazonaws.com/3.220.196.1] failed: connect timed out
假设验证
你的假设完全正确:私有子网内无公网访问权限,且缺少对应SES API的VPC端点,导致SDK无法连接email.us-east-1.amazonaws.com(SES v2 API的公网端点)而超时。当前AWS提供的SES VPC端点仅支持SMTP协议(端口587/465),不支持SES的REST API调用,这是核心问题。
可行替代方案
方案1:配置NAT网关/实例
给私有子网配置NAT网关(推荐)或NAT实例,让EC2实例通过NAT访问公网的SES API端点。无需修改代码,但会产生额外的NAT网关费用。方案2:切换到SES SMTP协议
放弃SES v2 SDK,改用SMTP协议发送邮件,利用已有的email-smtpVPC端点。注意:SMTP仅支持发送邮件操作,无法实现添加联系人、管理邮件列表等API功能,若应用依赖这些功能则不适用。方案3:使用Lambda作为中间层
在公有子网部署Lambda函数,由它调用SES v2 API完成所有操作;私有子网内的应用通过VPC端点调用Lambda(需配置Lambda的VPC端点)间接使用SES服务。既保留SES API全功能,又不需要私有子网访问公网。方案4:使用SNS转发邮件请求
若仅需发送邮件,可将请求发送到SNS主题,配置SNS订阅触发SES发邮件。私有子网应用通过SNS的VPC端点访问SNS,间接实现邮件发送。同样不支持联系人管理等SES API操作。
代码/配置修改建议
方案2:切换到SMTP发送邮件
如果仅需发送邮件,修改如下:
- 添加JavaMail依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-mail</artifactId> </dependency>
- 配置
application.properties:
spring.mail.host=email-smtp.us-east-1.amazonaws.com spring.mail.port=587 spring.mail.username=你的SES SMTP用户名(IAM Access Key) spring.mail.password=你的SES SMTP密码(IAM Secret Key) spring.mail.properties.mail.smtp.auth=true spring.mail.properties.mail.smtp.starttls.enable=true spring.mail.properties.mail.smtp.starttls.required=true
- 编写邮件发送服务:
import org.springframework.mail.SimpleMailMessage; import org.springframework.mail.javamail.JavaMailSender; import org.springframework.stereotype.Service; @Service public class EmailService { private final JavaMailSender mailSender; public EmailService(JavaMailSender mailSender) { this.mailSender = mailSender; } public void sendEmail(String to, String subject, String text) { SimpleMailMessage message = new SimpleMailMessage(); message.setFrom("你的验证邮箱"); message.setTo(to); message.setSubject(subject); message.setText(text); mailSender.send(message); } }
方案3:使用Lambda中间层
- 创建Lambda函数调用SES v2 API:
import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.sesv2.SesV2Client; import software.amazon.awssdk.services.sesv2.model.SendEmailRequest; import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestHandler; public class SesLambdaHandler implements RequestHandler<EmailRequest, String> { private final SesV2Client sesClient = SesV2Client.builder() .region(Region.US_EAST_1) .build(); @Override public String handleRequest(EmailRequest request, Context context) { SendEmailRequest sendEmailRequest = SendEmailRequest.builder() .fromEmailAddress(request.getFrom()) .destination(d -> d.toAddresses(request.getTo())) .content(c -> c.simple(s -> s.subject(sub -> sub.data(request.getSubject())) .body(b -> b.text(text -> text.data(request.getBody()))))) .build(); sesClient.sendEmail(sendEmailRequest); return "Email sent successfully"; } public static class EmailRequest { private String from; private String to; private String subject; private String body; // 省略getter/setter } }
- 给Lambda配置IAM权限,允许调用SES v2 API。
- 在Spring Boot应用中调用Lambda:
import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.lambda.LambdaClient; import software.amazon.awssdk.services.lambda.model.InvokeRequest; import com.fasterxml.jackson.databind.ObjectMapper; import software.amazon.awssdk.core.SdkBytes; public class LambdaSesService { private final LambdaClient lambdaClient = LambdaClient.builder() .region(Region.US_EAST_1) .build(); private final ObjectMapper objectMapper = new ObjectMapper(); public void sendEmailViaLambda(String from, String to, String subject, String body) throws Exception { EmailRequest request = new EmailRequest(from, to, subject, body); String payload = objectMapper.writeValueAsString(request); InvokeRequest invokeRequest = InvokeRequest.builder() .functionName("你的Lambda函数名") .payload(SdkBytes.fromUtf8String(payload)) .build(); lambdaClient.invoke(invokeRequest); } private static class EmailRequest { private final String from; private final String to; private final String subject; private final String body; public EmailRequest(String from, String to, String subject, String body) { this.from = from; this.to = to; this.subject = subject; this.body = body; } // 省略getter } }
- 确保VPC配置了Lambda的VPC端点(
com.amazonaws.us-east-1.lambda),且Lambda安全组允许私有子网EC2实例访问。
补充说明
类似问题包括:Fargate容器在私有子网中无法通过SES端点发送邮件、Spring应用在Fargate中无法连接SES,核心原因均为SES VPC端点仅支持SMTP协议,不兼容API调用。官方文档也明确了这一点。
内容的提问来源于stack exchange,提问作者After_Sunset

