在 Spring Boot 中使用 SendGrid 傳送電子郵件
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 帳戶中設定的寄件者身分相符。
我們將這些屬性儲存在專案的application.yaml檔案中,並使用@ConfigurationProperties將值對應到 POJO,我們的服務層在與 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;
// 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. 發送簡單的電子郵件
現在我們已經定義了 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物件的請求主體。
最後,我們使用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
}
我們迭代files參數中的每個file ,使用createAttachment()方法建立其對應的ttachments對象,並將其新增至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 並且它與預期格式相符。
同樣,讓我們將相應的模板 ID 屬性添加到application.yaml檔案中:
com:
baeldung:
sendgrid:
hydration-alert-notification:
template-id: ${HYDRATION_ALERT_TEMPLATE_ID}
發送水合警報電子郵件時,我們將在EmailDispatcher類別中使用此配置的範本 ID。
6.3.傳送範本電子郵件
現在我們已經配置了模板 ID,讓我們建立一個自Personalization類別來保存佔位符鍵名稱及其對應的值:
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範本中定義的佔位符新增自訂值。
然後,在將請求傳送到 SendGrid 之前,我們在Mail物件上設定此personalization物件以及templateId 。
SendGrid 將使用提供的動態資料來取代 HTML 範本中的佔位符。這有助於我們向使用者發送個人化電子郵件,同時保持一致的設計和佈局。
7. 測試 SendGrid 集成
現在我們已經實現了使用 SendGrid 發送電子郵件的功能,讓我們看看如何測試此整合。
測試外部服務可能具有挑戰性,因為我們不想在測試期間對 SendGrid 進行實際的 API 呼叫。這是我們將使用 MockServer 的地方,它允許我們模擬傳出的 SendGrid 呼叫。
7.1.配置測試環境
在編寫測試之前,我們將在src/test/resources目錄中建立application-integration-test.yaml文件,其中包含以下內容:
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 的實例,並使用${mockServerPort}佔位符建立server.url測試屬性。這將被為 MockServer 選擇的可用連接埠所取代,我們將在下一節配置自訂 SendGrid REST 用戶端時引用該連接埠。
7.2.配置自訂 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 上下文配置為信任所有證書,因為 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 配置為期望使用Authorization標頭和 JSON 正文對 SendGrid API 路徑發出 POST 請求。我們也指示 MockServer 在發出此請求時以 202 狀態碼回應。
接下來,我們使用測試資料呼叫dispatchEmail()方法,並驗證是否向MockServer發出了一次預期的請求。
透過使用 MockServer 模擬 SendGrid API,我們確保我們的整合能如預期運作,而不會實際發送任何電子郵件或產生任何費用。
八、結論
在本文中,我們探討如何從 Spring Boot 應用程式使用 SendGrid 發送電子郵件。
我們完成了必要的配置並實現了發送簡單電子郵件、帶有附件的電子郵件以及帶有動態模板的 HTML 電子郵件的功能。
最後,為了驗證我們的應用程式是否向 SendGrid 發送了正確的請求,我們使用 MockServer 編寫了一個整合測試。
與往常一樣,本文中使用的所有程式碼範例都可以在 GitHub 上找到。