電子郵件系統基於標準協議體系:發送端通過 SMTP (Simple Mail Transfer Protocol) 將郵件提交至發件服務器,經由互聯網路由到收件服務器,接收端通過 POP3/IMAP 從服務器拉取郵件。
發送者 -> [SMTP Client] -> 發件服務器 (SMTP Server)
-> [互聯網路由] -> 收件服務器 (POP3/IMAP Server)
<- [POP3/IMAP Client] <- 接收者
生產環境建議使用企業郵箱 (騰訊企業郵、阿里雲郵、Exchange) 或雲郵件服務 (SendGrid、Amazon SES)。開發測試可搭建本地服務器:
dev.local)、管理員帳號、密碼service@dev.local / 123456localhost 或本機 IP| 類/接口 | 核心作用 | 常用方法 |
|---|---|---|
| Session | 郵件會話上下文,持有配置屬性 | getInstance(props, authenticator) |
| Message (MimeMessage) | 郵件模型,封裝發件人、收件人、主題、內容 | setFrom(), addRecipient(), setSubject(), setContent() |
| Transport | 郵件傳輸服務 | send(message) |
| Authenticator | 身份認證器 | getPasswordAuthentication() |
| InternetAddress | 郵箱地址封裝 | new InternetAddress(email) |
package com.shop.util;
import javax.mail.*;
import javax.mail.internet.*;
import java.util.Properties;
public class MailUtils {
// 配置常量 (建議從配置文件讀取)
private static final String SMTP_HOST = "localhost";
private static final String SMTP_PORT = "25";
private static final String FROM_EMAIL = "service@dev.local";
private static final String FROM_PASSWORD = "123456";
private static final String ENCODING = "UTF-8";
/**
* 發送 HTML 格式郵件
* @param toEmail 收件人郵箱
* @param subject 郵件主題
* @param htmlContent HTML 內容
*/
public static void sendHtmlMail(String toEmail, String subject, String htmlContent) {
// 1. 構建會話屬性
Properties props = new Properties();
props.put("mail.smtp.host", SMTP_HOST);
props.put("mail.smtp.port", SMTP_PORT);
props.put("mail.smtp.auth", "true");
props.put("mail.transport.protocol", "smtp");
props.put("mail.smtp.timeout", "10000");
props.put("mail.smtp.connectiontimeout", "10000");
// 2. 創建認證會話
Session session = Session.getInstance(props, new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(FROM_EMAIL, FROM_PASSWORD);
}
});
session.setDebug(true); // 開發環境開啟調試
try {
// 3. 構建 MIME 郵件
MimeMessage message = new MimeMessage(session);
message.setFrom(new InternetAddress(FROM_EMAIL, "電商系統", ENCODING));
message.addRecipient(Message.RecipientType.TO, new InternetAddress(toEmail));
message.setSubject(subject, ENCODING);
// 4. 設置 HTML 內容 (支持圖片、附件擴展)
MimeMultipart multipart = new MimeMultipart("related");
// 文本/HTML 部分
MimeBodyPart htmlPart = new MimeBodyPart();
htmlPart.setContent(htmlContent, "text/html;charset=" + ENCODING);
multipart.addBodyPart(htmlPart);
message.setContent(multipart);
message.setSentDate(new java.util.Date());
message.saveChanges();
// 5. 發送
Transport.send(message);
System.out.println("郵件發送成功至: " + toEmail);
} catch (MessagingException | UnsupportedEncodingException e) {
throw new RuntimeException("郵件發送失敗: " + e.getMessage(), e);
}
}
/**
* 發送帶附件郵件
*/
public static void sendWithAttachment(String toEmail, String subject,
String htmlContent, String... filePaths) {
// ... 實現類似,添加 MimeBodyPart 附件部分
}
}
@Service
public class UserService {
@Autowired private MailUtils mailUtils;
public void register(User user) {
// 1. 生成激活碼 (JWT 或 UUID + Redis 存儲)
String activationCode = UUID.randomUUID().toString().replace("-", "");
redisTemplate.opsForValue().set("activation:" + activationCode,
user.getId(), 24, TimeUnit.HOURS);
// 2. 構建激活鏈接
String activateUrl = "https://shop.dev.local/user/activate?code=" + activationCode;
// 3. 郵件模板 (建議使用 Thymeleaf/FreeMarker)
String htmlContent = "<h1>歡迎註冊電商平台</h1>" +
"<p>請點擊下方鏈接激活賬號 (24小時有效):</p>" +
"<p><a href='" + activateUrl + "'>" + activateUrl + "</a></p>" +
"<p>如非本人操作,請忽略此郵件。</p>";
// 4. 異步發送 (生產環境建議使用消息隊列)
CompletableFuture.runAsync(() ->
mailUtils.sendHtmlMail(user.getEmail(), "賬號激活郵件", htmlContent)
);
}
@Transactional
public boolean activate(String code) {
String userId = redisTemplate.opsForValue().get("activation:" + code);
if (userId == null) return false;
User user = userDao.findById(Long.parseLong(userId));
user.setStatus(UserStatus.ACTIVE);
userDao.update(user);
redisTemplate.delete("activation:" + code);
return true;
}
}
<dependency>
<groupId>com.sun.mail</groupId>
<artifactId>javax.mail</artifactId>
<version>1.6.2</version>
</dependency>
spring:
mail:
host: smtp.exmail.qq.com
port: 465
username: noreply@company.com
password: xxx
protocol: smtps
properties:
mail:
smtp:
ssl:
enable: true
auth: true
starttls:
enable: true
connectiontimeout: 5000
timeout: 10000
writetimeout: 10000
# 連接池 (需自行配置或使用第三方庫)
@Component
public class MailProducer {
@Autowired private RabbitTemplate rabbitTemplate;
public void sendMailAsync(MailMessage mail) {
rabbitTemplate.convertAndSend("mail.exchange", "mail.send", mail);
}
}
@Component
@RabbitListener(queues = "mail.queue")
public class MailConsumer {
@Autowired private MailUtils mailUtils;
@RabbitHandler
public void handle(MailMessage mail) {
try {
mailUtils.sendHtmlMail(mail.getTo(), mail.getSubject(), mail.getContent());
} catch (Exception e) {
// 重試機制、死信隊列、告警
log.error("郵件發送失敗: {}", mail, e);
throw new AmqpRejectAndDontRequeueException(e);
}
}
}
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
@Service
public class TemplateMailService {
@Autowired private TemplateEngine templateEngine;
@Autowired private MailUtils mailUtils;
public void sendTemplateMail(String to, String subject, String template, Context ctx) {
String html = templateEngine.process(template, ctx);
mailUtils.sendHtmlMail(to, subject, html);
}
}
// 使用
Context ctx = new Context();
ctx.setVariable("username", user.getName());
ctx.setVariable("activateUrl", activateUrl);
templateMailService.sendTemplateMail(user.getEmail(), "激活郵件", "mail/activate", ctx);
@Entity
public class MailLog {
@Id @GeneratedValue
private Long id;
private String toEmail;
private String subject;
private String status; // SENT, FAILED, BOUNCED, OPENED, CLICKED
private String messageId; // SMTP 返回 ID
private String errorMessage;
private LocalDateTime createTime;
private LocalDateTime sentTime;
// 埋點追蹤: 打開率、點擊率、退信率
}
| 現象 | 可能原因 | 解決方案 |
|---|---|---|
| 連接超時 | 防火牆/安全組阻擋 25/465/587 端口 | 雲服務器控制台放行端口、使用 465 SSL 或 587 STARTTLS |
| 認證失敗 | 密碼錯誤、未開啟 SMTP 服務、需授權碼 | 郵箱後台開啟 SMTP、使用客戶端授權碼而非登錄密碼 |
| 進垃圾箱 | 無 SPF/DKIM/DMARC、內容關鍵詞、IP 信譽低 | 域名配置 SPF/TXT 記錄、DKIM 簽名、DMARC 政策、預熱 IP |
| 發送緩慢 | 同步阻塞、無連接池、DNS 解析慢 | 異步發送、配置連接池、本地 DNS 緩存 |
| 中文亂碼 | 編碼未統一 UTF-8 | MimeMessage 設置 charset、Java 文件編碼、數據庫編碼統一 |
郵件發送看似簡單,但生產環境涉及高可用、高併發、送達率、安全合規等多維挑戰。建議採用「異步發送 + 模板引擎 + 狀態追蹤 + 監控告警」的完整體系,並優先選擇成熟的雲郵件服務降低運維成本。