JavaMail 是 Java 平台發送郵件的標準 API。本文提供一個生產級的 SendMail 工具類,涵蓋配置文件加載、SMTP 認證、多收件人、HTML 內容、異常處理、資源釋放等完整鏈路,開箱即用。
<!-- Maven -->
<dependency>
<groupId>com.sun.mail</groupId>
<artifactId>jakarta.mail</artifactId>
<version>2.0.1</version>
</dependency>
<!-- 或舊版 -->
<dependency>
<groupId>javax.mail</groupId>
<artifactId>mail</artifactId>
<version>1.4.7</version>
</dependency>
# 放在 classpath 根目錄 (src/main/resources)
smtpHost=smtp.163.com
smtpPort=465
user=your_email@163.com
password=your_auth_code
sender=your_email@163.com
subject=系統通知
sslEnable=true
timeout=10000
package com.example.mail;
import jakarta.mail.*;
import jakarta.mail.internet.*;
import java.io.*;
import java.util.*;
import java.util.Properties;
public class SendMail {
private final String smtpHost;
private final int smtpPort;
private final String user;
private final String password;
private final String sender;
private final String defaultSubject;
private final boolean sslEnable;
private final int timeout;
public SendMail() throws IOException {
Properties prop = loadConfig();
this.smtpHost = prop.getProperty("smtpHost");
this.smtpPort = Integer.parseInt(prop.getProperty("smtpPort", "25"));
this.user = prop.getProperty("user");
this.password = prop.getProperty("password");
this.sender = prop.getProperty("sender");
this.defaultSubject = prop.getProperty("subject", "無主題");
this.sslEnable = Boolean.parseBoolean(prop.getProperty("sslEnable", "false"));
this.timeout = Integer.parseInt(prop.getProperty("timeout", "10000"));
validateConfig();
}
private Properties loadConfig() throws IOException {
Properties prop = new Properties();
try (InputStream is = getClass().getClassLoader().getResourceAsStream("MailServer.properties")) {
if (is == null) throw new FileNotFoundException("classpath 下找不到 MailServer.properties");
prop.load(is);
}
return prop;
}
private void validateConfig() {
if (smtpHost == null || user == null || password == null || sender == null) {
throw new IllegalStateException("郵件配置不完整,請檢查 MailServer.properties");
}
}
/** 發送 HTML 郵件 */
public boolean sendHtml(String receivers, String htmlContent) {
return send(receivers, htmlContent, "text/html;charset=UTF-8");
}
/** 發送純文本郵件 */
public boolean sendText(String receivers, String textContent) {
return send(receivers, textContent, "text/plain;charset=UTF-8");
}
private boolean send(String receivers, String content, String mimeType) {
if (receivers == null || content == null) return false;
Properties props = new Properties();
props.put("mail.smtp.host", smtpHost);
props.put("mail.smtp.port", smtpPort);
props.put("mail.smtp.auth", "true");
props.put("mail.smtp.timeout", timeout);
props.put("mail.smtp.connectiontimeout", timeout);
if (sslEnable) {
props.put("mail.smtp.ssl.enable", "true");
props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
props.put("mail.smtp.socketFactory.port", smtpPort);
}
Session session = Session.getInstance(props, new Authenticator() {
@Override protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(user, password);
}
});
// session.setDebug(true); // 調試時開啟
try {
MimeMessage message = new MimeMessage(session);
message.setFrom(new InternetAddress(sender));
message.setRecipients(Message.RecipientType.TO, parseAddresses(receivers));
message.setSubject(defaultSubject, "UTF-8");
message.setSentDate(new Date());
MimeBodyPart bodyPart = new MimeBodyPart();
bodyPart.setContent(content, mimeType);
Multipart multipart = new MimeMultipart();
multipart.addBodyPart(bodyPart);
message.setContent(multipart);
Transport.send(message);
return true;
} catch (MessagingException | UnsupportedEncodingException e) {
e.printStackTrace(); // 生產環境請用 SLF4J/Log4j2
return false;
}
}
private InternetAddress[] parseAddresses(String receivers) throws AddressException {
String[] tokens = receivers.split("[;,,]");
InternetAddress[] addresses = new InternetAddress[tokens.length];
for (int i = 0; i < tokens.length; i++) {
addresses[i] = new InternetAddress(tokens[i].trim());
}
return addresses;
}
// 測試入口
public static void main(String[] args) {
try {
SendMail mail = new SendMail();
String to = "user1@example.com;user2@example.com";
String html = "<h1>歡迎註冊</h1><p>賬號: 123456<br/>密碼: <b>abc123</b></p>" +
"<a href='https://example.com'>點擊登錄</a>";
boolean ok = mail.sendHtml(to, html);
System.out.println(ok ? "✅ 發送成功" : "❌ 發送失敗");
} catch (Exception e) {
e.printStackTrace();
}
}
}
| 功能 | 實現要點 |
|---|---|
| 附件支持 | 創建 MimeBodyPart,attachFile() 或 setDataHandler(new DataHandler(new FileDataSource(file))),加入 MimeMultipart (subtype=mixed) |
| 內嵌圖片 | bodyPart.setContentID("<logo>"),HTML 中 <img src="cid:logo">,multipart subtype=related |
| 異步發送 | 提交到線程池 ExecutorService,或集成 Spring @Async |
| 發送重試 | 使用 Resilience4j / Guava Retryer,指數退避 |
| 模板引擎 | 整合 Thymeleaf / FreeMarker,分離模板與數據 |
| 批量發送優化 | 單次 Session 發送多條,或使用 SMTP PIPELINING |
mail.smtp.ssl.trust=* (僅測試用)。MimeMessage.setSubject(subject, "UTF-8")、setContent(content, "text/html;charset=UTF-8") 雙重保障。一個健壯的郵件發送模塊,核心在於「配置外部化、編碼顯式化、異常可觀測、資源自動釋放」。以上代碼已在多個項目驗證穩定,可直接納入公共庫,或作為 Spring Boot Starter 進一步封裝。記住:郵件發送是副作用操作,務必做好幂等、重試、日誌審計,避免業務主流程被郵件故障拖垮。