1. 概述
發送電子郵件是現代 Web 應用程序的重要功能,無論是在用户註冊、密碼重置還是促銷活動中。
本教程將介紹如何在 Spring Boot 應用程序中使用 SendGrid 發送電子郵件。 我們將逐步講解所需的配置並實現各種用例中的電子郵件發送功能。
2. 設置 SendGrid
為了遵循本教程,我們首先需要一個 SendGrid 賬户。SendGrid 提供免費層級,允許我們每天發送最多 100 封電子郵件,這對於我們的演示來説已經足夠。
註冊後,我們需要創建一個 API 密鑰,以便對 SendGrid 服務進行身份驗證。
最後,我們需要驗證我們的 發件人身份,才能成功發送電子郵件。
3. 項目設置
在開始使用 SendGrid 發送電子郵件之前,我們需要包含 SDK 依賴項並正確配置應用程序。
3.1. 依賴項
讓我們首先將 SendGrid SDK 依賴項 添加到我們項目的 pom.xml 文件中:
<dependency>
<groupId>com.sendgrid</groupId>
<artifactId>sendgrid-java</artifactId>
<version>4.10.2</version>
</dependency>該依賴項為我們提供了與 SendGrid 服務交互以及從我們的應用程序發送電子郵件所需的類。
3.2. 定義 SendGrid 配置屬性
現在,為了與 SendGrid 服務交互並向我們的用户發送電子郵件,我們需要配置 API 密鑰以進行 API 請求的身份驗證。 我們還需要配置發件人的名稱和電子郵件地址,這些地址應與我們在 SendGrid 帳户中設置的發件人身份相匹配。
我們將這些屬性存儲在項目的 <em >application.yaml</em> 文件中,並使用 <em >@ConfigurationProperties</em> 將值映射到 POJO,該 POJO 由服務層在與 SendGrid 交互時引用:
@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;
// standard setters and getters
}我們還添加了驗證註釋,以確保所有必需的屬性已正確配置。如果定義的驗證失敗,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
現在我們已經配置了屬性,接下來我們將使用它們來定義必要的 Bean:
@Configuration
@EnableConfigurationProperties(SendGridConfigurationProperties.class)
class SendGridConfiguration {
private final SendGridConfigurationProperties sendGridConfigurationProperties;
// standard constructor
@Bean
public SendGrid sendGrid() {
String apiKey = sendGridConfigurationProperties.getApiKey();
return new SendGrid(apiKey);
}
}使用構造函數注入,我們注入了我們之前創建的 SendGridConfigurationProperties 類的一個實例。然後,我們使用配置的 API 密鑰來創建 SendGrid Bean。
接下來,我們將創建一個 Bean 來表示所有發件人信息:
@Bean
public Email fromEmail() {
String fromEmail = sendGridConfigurationProperties.getFromEmail();
String fromName = sendGridConfigurationProperties.getFromName();
return new Email(fromEmail, fromName);
}有了這些 Bean 部署好後,我們可以在服務層中自動注入它們,以便與 SendGrid 服務進行交互。
4. 發送簡單的電子郵件
現在我們已經定義了豆子,讓我們創建一個 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 對象的請求體。
最後,我們使用 SendGrid Bean 將 request 發送到 SendGrid 服務。
5. 發送帶有附件的郵件
除了發送簡單的純文本郵件外,SendGrid 還允許我們發送帶有附件的郵件。
首先,我們將創建一個輔助方法,將 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) {
// ... same as above
if (files != null && !files.isEmpty()) {
for (MultipartFile file : files) {
Attachments attachment = createAttachment(file);
mail.addAttachments(attachment);
}
}
// ... same as above
}我們遍歷參數中的每一個 文件,使用我們的 createAttachment() 方法創建其對應的 附件 對象,並將該對象添加到我們的 Mail 對象中。 其餘方法邏輯不變。
6. 使用動態模板發送電子郵件
SendGrid 允許我們使用 HTML 和 Handlebars 語法 創建動態電子郵件模板。
對於本次演示,我們將以一個示例為例,即向我們的用户發送個性化的補水提醒電子郵件。
6.1. 創建 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
創建模板後,SendGrid 會為其分配一個唯一的模板 ID。
為了保存此模板 ID,我們將定義 SendGridConfigurationProperties 類內的嵌套類:
@Valid
private HydrationAlertNotification hydrationAlertNotification = new HydrationAlertNotification();
class HydrationAlertNotification {
@NotBlank
@Pattern(regexp = "^d-[a-f0-9]{32}$")
private String templateId;
// standard setter and getter
}我們再次添加驗證註釋,以確保正確配置模板 ID 並使其與預期格式匹配。
同樣,讓我們在 application.yaml 文件中添加相應的模板 ID 屬性:
com:
baeldung:
sendgrid:
hydration-alert-notification:
template-id: ${HYDRATION_ALERT_TEMPLATE_ID}我們將在 EmailDispatcher 類中,使用此配置的模板 ID 發送我們的“補水提醒”電子郵件。
6.3. 發送模板郵件
現在我們已經配置好模板 ID,接下來創建一個自定義 個性化 類來存儲佔位符鍵名及其對應的值:
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 映射,我們使用 add() 方法來填充它。
現在,讓我們創建一個新的服務方法來發送我們的水合提示:
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);
// ... sending request process same as previous
}在我們的 dispatchHydrationAlert() 方法中,我們創建了 DynamicTemplatePersonalization 類的一個實例,併為我們在 HTML 模板中定義的佔位符添加了自定義值。
然後,我們將這個 personalization 對象以及 templateId 設置到 Mail 對象上,在發送請求到 SendGrid 之前。
SendGrid 將會用提供的動態數據替換我們 HTML 模板中的佔位符。 這有助於我們向用户發送個性化的電子郵件,同時保持一致的設計和佈局。
7. 測試 SendGrid 集成
現在我們已經實現了使用 SendGrid 發送電子郵件的功能,接下來讓我們看看如何測試這個集成。
測試外部服務可能具有挑戰性,因為我們不想在測試過程中實際向 SendGrid 發送 API 調用。 這正是使用 MockServer 的作用,它將允許我們模擬向 SendGrid 發出的調用。
7.1. 配置測試環境
在編寫測試之前,我們將創建一個 <em src/test/resources/application-integration-test.yaml</em> 文件,內容如下:
com:
baeldung:
sendgrid:
api-key: SG0101010101010101010101010101010101010101010101010101010101010101010
from-email: [email protected]
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 實例並創建 server.url 測試屬性,其中包含 mockServerPort 佔位符。 此佔位符將被選定的 MockServer 自由端口替換,我們將在此處引用它,在下一部分中配置我們自定義的 SendGrid REST 客户端。
7.2. 自定義 SendGrid REST 客户端配置
為了將我們的 SendGrid API 請求路由到 MockServer,我們需要為 SendGrid SDK 配置一個自定義 REST 客户端。
我們將創建一個 <em @TestConfiguration</em> 類,該類定義了一個新的 <em SendGrid</em> Bean,並帶有自定義的 <em HttpClient</em>>:
@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 類中,我們創建了一個自定義的 Client,該 Client 通過 server.url 屬性指定的代理服務器路由所有請求。 我們還配置了 SSL 上下文以信任所有證書,因為 MockServer 默認使用自簽名證書。
為了在我們的集成測試中使用此測試配置,我們需要將 @ContextConfiguration 註解添加到我們的測試類中:
@ContextConfiguration(classes = TestSendGridConfiguration.class)這確保我們的應用程序使用我們在 TestSendGridConfiguration 類中定義的 Bean,而不是我們在 SendGridConfiguration 類中定義的 Bean,在運行集成測試時
7.3. 驗證 SendGrid 請求
最後,我們編寫一個測試用例來驗證我們的 dispatchEmail() 方法是否將預期的請求發送到 SendGrid:
// Set up test data
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();
// Create JSON body
String jsonBody = String.format("""
{
"from": {
"name": "%s",
"email": "%s"
},
"subject": "%s",
"personalizations": [{
"to": [{
"email": "%s"
}]
}],
"content": [{
"value": "%s"
}]
}
""", fromName, fromEmail, emailSubject, toEmail, emailBody);
// Configure mock server expectations
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));
// Invoke method under test
emailDispatcher.dispatchEmail(toEmail, emailSubject, emailBody);
// Verify the expected request was made
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 標頭和 JSON 響應體。我們還指示 MockServer 在收到該請求時返回 202 狀態碼。
接下來,我們使用測試數據調用我們的 dispatchEmail() 方法,並驗證預期請求是否在 MockServer 上精確一次。
通過使用 MockServer 模擬 SendGrid API,我們確保我們的集成按預期工作,而無需實際發送任何電子郵件或產生任何費用.
8. 結論
在本文中,我們探討了如何使用 SendGrid 從 Spring Boot 應用程序中發送電子郵件。
我們完成了必要的配置,並實現了發送簡單電子郵件、帶附件的電子郵件以及使用動態模板的 HTML 電子郵件的功能。
最後,為了驗證我們的應用程序是否將正確的請求發送到 SendGrid,我們使用 MockServer 編寫了集成測試。