郵件發送是企業級應用的基礎功能。本文提供一個生產環境可用的完整範例,展示如何使用 JavaMail API 透過 163 SMTP 伺服器(SSL 加密)發送 HTML 郵件至 QQ 郵箱,並支援多收件人與附件功能。
javax.mail:javax.mail-api:1.6.2、com.sun.mail:javax.mail:1.6.2package com.example.mail;
import com.sun.mail.util.MailSSLSocketFactory;
import javax.activation.DataHandler;
import javax.activation.DataSource;
import javax.activation.FileDataSource;
import javax.mail.*;
import javax.mail.internet.*;
import java.util.*;
public class MailSender {
private static final String SMTP_HOST = "smtp.163.com";
private static final String USERNAME = "your_account@163.com"; // 不含 @163.com
private static final String AUTH_CODE = "your_16_digit_auth_code";
private static final String FROM_ADDR = "your_account@163.com";
public static void sendMail(List<String> toAddresses, String subject,
String htmlContent, List<String> attachmentPaths) throws Exception {
// 1. SSL 連線屬性設定
Properties props = new Properties();
props.put("mail.smtp.host", SMTP_HOST);
props.put("mail.smtp.auth", "true");
props.put("mail.smtp.ssl.enable", "true");
MailSSLSocketFactory sf = new MailSSLSocketFactory();
sf.setTrustAllHosts(true); // 生產環境建議改為信任特定憑證
props.put("mail.smtp.ssl.socketFactory", sf);
// 2. 建立 Session(含認證)
Session session = Session.getInstance(props, new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(USERNAME, AUTH_CODE);
}
});
session.setDebug(true); // 開發階段開啟除錯輸出
// 3. 建立 MimeMessage
MimeMessage message = new MimeMessage(session);
message.setFrom(new InternetAddress(FROM_ADDR));
// 收件人驗證與設定
StringBuilder validRecipients = new StringBuilder();
String emailRegex = "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$";
for (int i = 0; i < toAddresses.size(); i++) {
String addr = toAddresses.get(i);
if (addr.matches(emailRegex)) {
validRecipients.append(addr);
if (i < toAddresses.size() - 1) validRecipients.append(",");
}
}
if (validRecipients.length() == 0) throw new MessagingException("無有效收件人");
message.setRecipients(Message.RecipientType.TO,
InternetAddress.parse(validRecipients.toString()));
message.setSubject(subject, "UTF-8");
message.setSentDate(new Date());
// 4. 建立多部份內容容器
MimeMultipart multipart = new MimeMultipart();
// 4.1 HTML 正文
MimeBodyPart htmlPart = new MimeBodyPart();
htmlPart.setContent(htmlContent, "text/html;charset=UTF-8");
multipart.addBodyPart(htmlPart);
// 4.2 附件處理
if (attachmentPaths != null) {
for (String path : attachmentPaths) {
if (path == null || path.isEmpty()) continue;
MimeBodyPart attachPart = new MimeBodyPart();
DataSource source = new FileDataSource(path);
attachPart.setDataHandler(new DataHandler(source));
// 解決中文檔名亂碼
attachPart.setFileName(MimeUtility.encodeText(source.getName()));
multipart.addBodyPart(attachPart);
}
}
message.setContent(multipart);
message.saveChanges();
// 5. 發送
Transport.send(message);
System.out.println("郵件發送成功:" + subject);
}
// 測試主程式
public static void main(String[] args) {
try {
List<String> to = Arrays.asList("target@qq.com");
List<String> attachments = Arrays.asList("C:/docs/report.pdf");
sendMail(to, "測試主旨", "<h3>這是 HTML 內容</h3><p>支援中文</p>", attachments);
} catch (Exception e) {
e.printStackTrace();
}
}
}
| 錯誤現象 | 可能原因 | 解決方法 |
|---|---|---|
| 535 Error: authentication failed | 使用登入密碼而非授權碼 | 在 163 郵箱設定生成專用授權碼 |
| Could not connect to SMTP host | 防火牆/網路阻擋 465/994 port | 確認網路連通性、企業防火牆放行 |
| javax.net.ssl.SSLHandshakeException | JDK 版本過舊不支援 TLS 1.2+ | 升級 JDK 8u161+ 或配置信任憑證 |
| 附件中文檔名亂碼 | 未編碼檔名 | 使用 MimeUtility.encodeText() |
| 發送成功但對方未收到 | 進垃圾信箱/被反垃圾攔截 | 檢查 SPF/DKIM、避免敏感關鍵字、使用企業郵箱 |
Transport.connect() 復用連線,避免重複 SSL 握手CompletableFuture 或訊息佇列(RabbitMQ/Kafka)解耦業務流程setTrustAllHosts(true),應正確載入信任憑證鏈有同事直接複製代碼卻報錯,最終發現是授權碼過期/錯誤。重新在 163 郵箱後台「重置客戶端授權碼」後即恢復正常。授權碼異常是 90% 發送失敗的主因,請優先排查。