SMTP (Simple Mail Transfer Protocol) 基於 TCP 連接埠 25/465/587,採用請求-回應文本協議。核心流程:
. 結束資料public class SmtpMailSender {
private final String smtpHost;
private final int smtpPort;
private final String username;
private final String password;
private final String from;
private final List<String> toList = new ArrayList<>();
private String subject;
private String htmlContent;
private boolean debug = false;
public SmtpMailSender(String host, int port, String username, String password, String from) {
this.smtpHost = host;
this.smtpPort = port;
this.username = username;
this.password = password;
this.from = from;
}
public void addRecipient(String to) { toList.add(to); }
public void setSubject(String subject) { this.subject = subject; }
public void setHtmlContent(String htmlContent) { this.htmlContent = htmlContent; }
public void setDebug(boolean debug) { this.debug = debug; }
public void send() throws IOException {
if (toList.isEmpty()) throw new IllegalStateException("收件人不能為空");
if (htmlContent == null) throw new IllegalStateException("郵件內容不能為空");
try (Socket socket = new Socket(smtpHost, smtpPort);
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
BufferedWriter out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()))) {
socket.setSoTimeout(10000);
readResponse(in); // 220 歡迎
sendCommand(out, "EHLO " + smtpHost);
readResponse(in); // 250
sendCommand(out, "AUTH LOGIN");
readResponse(in); // 334
sendCommand(out, Base64.getEncoder().encodeToString(username.getBytes(StandardCharsets.UTF_8)));
readResponse(in); // 334
sendCommand(out, Base64.getEncoder().encodeToString(password.getBytes(StandardCharsets.UTF_8)));
readResponse(in); // 235
sendCommand(out, "MAIL FROM:<" + from + ">");
readResponse(in); // 250
for (String to : toList) {
sendCommand(out, "RCPT TO:<" + to + ">");
readResponse(in); // 250
}
sendCommand(out, "DATA");
readResponse(in); // 354
// 郵件頭
sendData(out, "From: " + from);
sendData(out, "To: " + String.join(", ", toList));
sendData(out, "Subject: " + MimeUtility.encodeText(subject, "UTF-8", "B"));
sendData(out, "MIME-Version: 1.0");
sendData(out, "Content-Type: text/html; charset=UTF-8");
sendData(out, ""); // 空行分隔頭與體
// 郵件體
sendData(out, htmlContent);
sendData(out, "."); // 結束標記
readResponse(in); // 250
sendCommand(out, "QUIT");
readResponse(in); // 221
}
}
private void sendCommand(BufferedWriter out, String cmd) throws IOException {
out.write(cmd + "\r\n");
out.flush();
if (debug) System.out.println("> " + cmd);
}
private void sendData(BufferedWriter out, String line) throws IOException {
out.write(line + "\r\n");
out.flush();
if (debug) System.out.println("> " + line);
}
private void readResponse(BufferedReader in) throws IOException {
String line;
while ((line = in.readLine()) != null) {
if (debug) System.out.println("< " + line);
int code = Integer.parseInt(line.substring(0, 3));
if (code >= 400) throw new IOException("SMTP 錯誤: " + line);
if (line.length() > 3 && line.charAt(3) == ' ') break; // 最後一行
}
}
}
public static void main(String[] args) throws Exception {
SmtpMailSender sender = new SmtpMailSender(
"smtp.qq.com", 465, // 或 587 (STARTTLS)
"your@qq.com", "授權碼", // 非登入密碼
"your@qq.com"
);
sender.addRecipient("friend@gmail.com");
sender.addRecipient("colleague@company.com");
sender.setSubject("測試郵件 - Socket SMTP");
sender.setHtmlContent("<h1>你好</h1><p>這是一封<b>HTML</b>郵件。</p>");
sender.setDebug(true);
sender.send();
System.out.println("發送成功!");
}
| 項目 | 說明 |
|---|---|
| 連接埠選擇 | 25 (明文), 465 (SSL 隱式), 587 (STARTTLS 顯式)。主流郵箱建議 465/587 |
| 認證方式 | AUTH LOGIN (Base64),非加密。生產環境必須配合 SSL/TLS |
| 授權碼 vs 密碼 | QQ/163/Gmail 等需開啟 SMTP 服務並生成專用授權碼,不可用登入密碼 |
| 中文主題 | 使用 MimeUtility.encodeText(subject, "UTF-8", "B") 進行 MIME B 編碼 |
| 多收件人 | RCPT TO 多次發送;DATA 階段 To 頭用逗號分隔 |
| 附件/嵌入圖片 | 需構建 MIME multipart/mixed 或 multipart/related,手動拼接較複雜,建議改用 JavaMail |
| 超時設定 | socket.setSoTimeout(ms) 防止讀寫阻塞 |