1、概览 {#1概览}
无论是用户注册、密码重置还是促销活动,发送电子邮件都是现代 Web 应用的一项重要功能。
本文将带你了解如何在 Spring Boot 应用中使用 SendGrid 发送电子邮件。
2、SendGrid 设置 {#2sendgrid-设置}
在开始之前,我们首先需要一个 SendGrid 账户。SendGrid 提供了免费套餐,允许我们每天发送多达 100 封电子邮件,这对于演示来说已经足够了。
注册完成后,需要创建一个 API Key 来对我们发送到 SendGrid 服务的请求进行 身份认证。
3、项目设置 {#3项目设置}
在开始使用 SendGrid 发送电子邮件之前,需要添加 SDK 依赖并配置应用。
3.1、依赖 {#31依赖}
首先,在项目的 pom.xml
文件中添加 SendGrid SDK 依赖:
<dependency>
<groupId>com.sendgrid</groupId>
<artifactId>sendgrid-java</artifactId>
<version>4.10.2</version>
</dependency>
该依赖为我们提供了与 SendGrid 服务交互和从应用发送电子邮件所需的类。
3.2、定义 SendGrid 配置属性 {#32定义-sendgrid-配置属性}
现在,为了与 SendGrid 服务交互并向用户发送电子邮件,我们需要配置 API Key 以验证 API 请求。我们还需要配置发件人姓名和电子邮件地址,它们应与我们在 SendGrid 账户中设置的发件人身份相匹配。
我们在项目的 application.yaml
文件中配置这些属性,并使用 @ConfigurationProperties
将这些值映射到 POJO ,Service 层在与 SendGrid
交互时会引用配置的 POJO:
@Validated
@ConfigurationProperties(prefix = "com.baeldung.sendgrid")
class SendGridConfigurationProperties {
@NotBlank
@Pattern(regexp = "^SG[0-9a-zA-Z._]{67}$")
private String apiKey;
@Email
@NotBlank
private String fromEmail;
@NotBlank
private String fromName;
// 标准的 Getter / Setter
}
如上。还添加了 @Validated
验证注解,以确保正确配置所有必要属性。如果定义的任何验证失败,Spring ApplicationContext
将无法启动("快速失败" 原则)。
下面是 application.yaml
文件的配置片段,其中定义了将要自动映射到 SendGridConfigurationProperties
类的所需属性:
com:
baeldung:
sendgrid:
api-key: ${SENDGRID_API_KEY}
from-email: ${SENDGRID_FROM_EMAIL}
from-name: ${SENDGRID_FROM_NAME}
我们使用 ${}
属性占位符从 环境变量 中加载属性值。这种设置允许我们将 SendGrid 属性外部化,并在应用中轻松访问它们。
3.3、配置 SendGrid Bean {#33配置-sendgrid-bean}
配置了属性后,引用它们来定义必要的 Bean:
@Configuration
@EnableConfigurationProperties(SendGridConfigurationProperties.class)
class SendGridConfiguration {
private final SendGridConfigurationProperties sendGridConfigurationProperties;
// 构造函数注入
public SendGridConfiguration (SendGridConfigurationProperties sendGridConfigurationProperties){
this.sendGridConfigurationProperties = sendGridConfigurationProperties;
}
@Bean
public SendGrid sendGrid() {
String apiKey = sendGridConfigurationProperties.getApiKey();
return new SendGrid(apiKey);
}
}
通过构造函数注入,我们注入了之前创建的 SendGridConfigurationProperties
类的实例。然后,我们使用配置的 API Key 创建 SendGrid
Bean。
接下来,创建一个 Bean 来代表所有我们发送的邮件的发件人:
@Bean
public Email fromEmail() {
String fromEmail = sendGridConfigurationProperties.getFromEmail();
String fromName = sendGridConfigurationProperties.getFromName();
return new Email(fromEmail, fromName);
}
有了这些 Bean,我们就可以在 Service 层中自动装配它们,以便与 SendGrid 服务交互。
4、发送简单的电子邮件 {#4发送简单的电子邮件}
Bean 定义好后,让我们创建一个 EmailDispatcher
类并引用它们来发送一封简单的电子邮件:
private static final String EMAIL_ENDPOINT = "mail/send";
public void dispatchEmail(String emailId, String subject, String body) {
Email toEmail = new Email(emailId);
Content content = new Content("text/plain", body);
Mail mail = new Mail(fromEmail, subject, toEmail, content);
Request request = new Request();
request.setMethod(Method.POST);
request.setEndpoint(EMAIL_ENDPOINT);
request.setBody(mail.build());
sendGrid.api(request);
}
在 dispatchEmail()
方法中,我们创建了一个新的 Mail
对象,代表我们要发送的电子邮件,然后将其设置为 Request
对象的请求体(Request Body)。
最后,使用 SendGrid
Bean 将请求发送到 SendGrid
服务。
5、发送带附件的电子邮件 {#5发送带附件的电子邮件}
除了发送简单的纯文本电子邮件,SendGrid 还允许我们发送带附件的电子邮件。
首先,创建一个 helper 方法,用于将 MultipartFile
转换为 SendGrid
SDK 中的 Attachments
对象:
private Attachments createAttachment(MultipartFile file) {
byte[] encodedFileContent = Base64.getEncoder().encode(file.getBytes());
Attachments attachment = new Attachments();
attachment.setDisposition("attachment");
attachment.setType(file.getContentType());
attachment.setFilename(file.getOriginalFilename());
attachment.setContent(new String(encodedFileContent, StandardCharsets.UTF_8));
return attachment;
}
在 createAttachment()
方法中,我们创建一个新的 Attachments
对象,并根据 MultipartFile
参数设置其属性。
注意,在将文件内容设置到 Attachments
对象之前,我们对其进行了 Base64 编码。
接下来,更新 dispatchEmail()
方法,使其接受一个可选的 MultipartFile
对象集合:
public void dispatchEmail(String emailId, String subject, String body, List<MultipartFile> files) {
// ... 同上
if (files != null && !files.isEmpty()) {
for (MultipartFile file : files) {
Attachments attachment = createAttachment(file);
mail.addAttachments(attachment);
}
}
// ... 同上
}
遍历 files
参数中的每个文件,使用 createAttachment()
方法创建相应的 Attachments
对象,并将其添加到 Mail
对象中。该方法的其余部分保持不变。
6、用动态模板发送电子邮件 {#6用动态模板发送电子邮件}
SendGrid 还允许我们使用 HTML 和 Handlebars 语法 创建动态电子邮件模板。
以向用户发送个性化 "喝水提醒" 电子邮件为例。
6.1、创建 HTML 模板 {#61创建-html-模板}
首先,要为 "喝水提醒" 电子邮件创建 HTML 模板:
<html>
<head>
<style>
body { font-family: Arial; line-height: 2; text-align: Center; }
h2 { color: DeepSkyBlue; }
.alert { background: Red; color: White; padding: 1rem; font-size: 1.5rem; font-weight: bold; }
.message { border: .3rem solid DeepSkyBlue; padding: 1rem; margin-top: 1rem; }
.status { background: LightCyan; padding: 1rem; margin-top: 1rem; }
</style>
</head>
<body>
<div class="alert">⚠️ URGENT HYDRATION ALERT ⚠️</div>
<div class="message">
<h2>It's time to drink water!</h2>
<p>Hey {{name}}, this is your friendly reminder to stay hydrated. Your body will thank you!</p>
<div class="status">
<p><strong>Last drink:</strong> {{lastDrinkTime}}</p>
<p><strong>Hydration status:</strong> {{hydrationStatus}}</p>
</div>
</div>
</body>
</html>
在模板中,我们使用 Handlebars 语法定义了 {{name}}
, {{lastDrinkTime}}
和 {{hydrationStatus}}
的占位符。发送电子邮件时,将用实际值替换这些占位符。
我们还使用了内联 CSS 来美化电子邮件模板。
6.2、配置模板 ID {#62配置模板-id}
在 SendGrid
中创建了模板后,需要为它分配一个唯一的模板 ID。
要保存这个模板 ID,我们可以在 SendGridConfigurationProperties
类中定义一个嵌套类:
@Valid
private HydrationAlertNotification hydrationAlertNotification = new HydrationAlertNotification();
class HydrationAlertNotification {
@NotBlank
@Pattern(regexp = "^d-[a-f0-9]{32}$")
private String templateId;
// Getter / Setter 方法省略
}
再次添加 @Valid
校验注解,以确保正确配置模板 ID 并使其符合预期格式。
同样,在 application.yaml
文件中添加相应的模板 ID 属性:
com:
baeldung:
sendgrid:
hydration-alert-notification:
template-id: ${HYDRATION_ALERT_TEMPLATE_ID}
发送 "喝水提醒" 电子邮件时,我们将在 EmailDispatcher
类中使用此配置的模板 ID。
6.3、发送模板电子邮件 {#63发送模板电子邮件}
配置了模板 ID 后,让我们创建一个自定义 Personalization
类来保存我们的占位符 KEY 名及其相应的值:
class DynamicTemplatePersonalization extends Personalization {
private final Map<String, Object> dynamicTemplateData = new HashMap<>();
public void add(String key, String value) {
dynamicTemplateData.put(key, value);
}
@Override
public Map<String, Object> getDynamicTemplateData() {
return dynamicTemplateData;
}
}
覆写 getDynamicTemplateData()
方法来返回 dynamicTemplateData
Map,并使用 add()
方法对其进行填充。
现在,创建一个新的 Service 方法来发送 "喝水提醒":
public void dispatchHydrationAlert(String emailId, String username) {
Email toEmail = new Email(emailId);
String templateId = sendGridConfigurationProperties.getHydrationAlertNotification().getTemplateId();
DynamicTemplatePersonalization personalization = new DynamicTemplatePersonalization();
personalization.add("name", username);
personalization.add("lastDrinkTime", "Way too long ago");
personalization.add("hydrationStatus", "Thirsty as a camel");
personalization.addTo(toEmail);
Mail mail = new Mail();
mail.setFrom(fromEmail);
mail.setTemplateId(templateId);
mail.addPersonalization(personalization);
// ... 发送请求流程与之前相同
}
在 dispatchHydrationAlert()
方法中,我们创建了 DynamicTemplatePersonalization
类的实例,并为 HTML 模板中定义的占位符添加了自定义值。
然后,在向 SendGrid 发送请求之前,我们会在 Mail
对象上设置该 personalization
对象和 templateId
。
SendGrid 将使用提供的动态数据替换我们 HTML 模板中的占位符。这有助于我们向用户发送个性化的邮件,同时保持一致的设计和布局。
7、测试 SendGrid {#7测试-sendgrid}
现在,我们已经使用 SendGrid 实现了发送电子邮件的功能,接下来看看如何测试这种集成。
测试外部服务有一点麻烦,因为我们不想在测试过程中实际调用 SendGrid 的 API。
我们可以使用 MockServer,它可以让我们模拟 SendGrid 的外部调用。
7.1、配置测试环境 {#71配置测试环境}
在编写测试之前,先在 src/test/resources
目录中创建一个包含以下内容的 application-integration-test.yaml
文件:
com:
baeldung:
sendgrid:
api-key: SG0101010101010101010101010101010101010101010101010101010101010101010
from-email: no-reply@baeldung.com
from-name: Baeldung
hydration-alert-notification:
template-id: d-01010101010101010101010101010101
这些虚拟值绕过了我们之前在 SendGridConfigurationProperties
类中配置的验证。
现在,创建测试类:
@SpringBootTest
@ActiveProfiles("integration-test")
@MockServerTest("server.url=http://localhost:${mockServerPort}")
@EnableConfigurationProperties(SendGridConfigurationProperties.class)
class EmailDispatcherIntegrationTest {
private MockServerClient mockServerClient;
@Autowired
private EmailDispatcher emailDispatcher;
@Autowired
private SendGridConfigurationProperties sendGridConfigurationProperties;
private static final String SENDGRID_EMAIL_API_PATH = "/v3/mail/send";
}
我们使用 @ActiveProfiles
注解加载特定于集成测试的属性。
我们还使用 @MockServerTest
注解启动了一个 MockServer 实例,并创建了一个带有 ${mockServerPort}
占位符的 server.url
测试属性。这将被 MockServer 选择的空闲端口替换,我们会在下一节中引用这个端口,在那里我们要配置我们的自定义 SendGrid REST 客户端。
7.2、配置自定义 SendGrid REST 客户端 {#72配置自定义-sendgrid-rest-客户端}
为了将 SendGrid API 请求路由到 MockServer ,我们需要为 SendGrid SDK 配置一个自定义 REST 客户端。
创建一个 @TestConfiguration
类,该类定义了一个带有自定义 HttpClient
的新 SendGrid Bean:
@TestConfiguration
@EnableConfigurationProperties(SendGridConfigurationProperties.class)
class TestSendGridConfiguration {
@Value("${server.url}")
private URI serverUrl;
@Autowired
private SendGridConfigurationProperties sendGridConfigurationProperties;
@Bean
@Primary
public SendGrid testSendGrid() {
SSLContext sslContext = SSLContextBuilder.create()
.loadTrustMaterial((chain, authType) -> true)
.build();
HttpClientBuilder clientBuilder = HttpClientBuilder.create()
.setSSLContext(sslContext)
.setProxy(new HttpHost(serverUrl.getHost(), serverUrl.getPort()));
Client client = new Client(clientBuilder.build(), true);
client.buildUri(serverUrl.toString(), null, null);
String apiKey = sendGridConfigurationProperties.getApiKey();
return new SendGrid(apiKey, client);
}
}
在 TestSendGridConfiguration
类中,我们创建了一个自定义客户端,通过 server.url
属性指定的代理服务器路由所有请求。我们还配置了 SSL Context 以信任所有证书,因为 MockServer 默认使用自签名证书。
要在集成测试中使用此测试配置,需要在测试类中添加 @ContextConfiguration
注解:
@ContextConfiguration(classes = TestSendGridConfiguration.class)
这将确保我们的应用在运行集成测试时使用的是我们在 TestSendGridConfiguration
类中定义的 Bean,而不是在 SendGridConfiguration
类中定义的 Bean。
7.3、验证 SendGrid 请求 {#73验证-sendgrid-请求}
最后,编写一个测试用例来验证我们的 dispatchEmail()
方法是否向 SendGrid 发送了预期的请求:
// 设置测试数据
String toEmail = RandomString.make() + "@baeldung.it";
String emailSubject = RandomString.make();
String emailBody = RandomString.make();
String fromName = sendGridConfigurationProperties.getFromName();
String fromEmail = sendGridConfigurationProperties.getFromEmail();
String apiKey = sendGridConfigurationProperties.getApiKey();
// 创建 JSON 请求体
String jsonBody = String.format("""
{
"from": {
"name": "%s",
"email": "%s"
},
"subject": "%s",
"personalizations": [{
"to": [{
"email": "%s"
}]
}],
"content": [{
"value": "%s"
}]
}
""", fromName, fromEmail, emailSubject, toEmail, emailBody);
// 配置模拟服务器预期值
mockServerClient
.when(request()
.withMethod("POST")
.withPath(SENDGRID_EMAIL_API_PATH)
.withHeader("Authorization", "Bearer " + apiKey)
.withBody(new JsonBody(jsonBody, MatchType.ONLY_MATCHING_FIELDS)
))
.respond(response().withStatusCode(202));
// 调用被测方法
emailDispatcher.dispatchEmail(toEmail, emailSubject, emailBody);
// 验证请求是否符合预期
mockServerClient
.verify(request()
.withMethod("POST")
.withPath(SENDGRID_EMAIL_API_PATH)
.withHeader("Authorization", "Bearer " + apiKey)
.withBody(new JsonBody(jsonBody, MatchType.ONLY_MATCHING_FIELDS)
), VerificationTimes.once());
在我们的测试方法中,首先设置了测试数据,并为 SendGrid 请求创建了预期的 JSON 请求体。然后,对 MockServer 进行配置,使其能够接收到发送到 SendGrid API 路径的 POST 请求,并包含 Authorization
Header 和 JSON 请求体。我们还指示 MockServer 在发出请求时响应 202 状态代码。
接下来,使用测试数据调用 dispatchEmail()
方法,并验证是否向 MockServer 发送了符合预期的请求。
通过使用 MockServer 来模拟 SendGrid API,可以确保我们的集成能够按照预期运行,而无需实际发送任何电子邮件或产生任何费用。
8、总结 {#8总结}
本文介绍了如何在 Spring Boot 中使用 SendGrid 发送电子邮件,首先介绍了如何整合、配置 SendGrid,然后介绍了发送简单电子邮件、带附件电子邮件和动态 HTML 模板电子邮件等功能,最后,使用 MockServer 编写集成测试来验证应用是否向 SendGrid 发送了正确的请求。
Ref:https://www.baeldung.com/java-email-sendgrid