首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java Socket 實現 SMTP 協議發送郵件完整教學

     編輯:Java 相關知識

SMTP 協議基礎

SMTP (Simple Mail Transfer Protocol) 基於 TCP 連接埠 25/465/587,採用請求-回應文本協議。核心流程:

  1. 建立 TCP 連線
  2. HELO/EHLO 問候
  3. AUTH LOGIN 認證 (Base64 編碼用戶名/密碼)
  4. MAIL FROM: 發件人
  5. RCPT TO: 收件人 (可多個)
  6. DATA 開始資料傳輸
  7. 郵件頭 + 空行 + 郵件體
  8. . 結束資料
  9. QUIT 斷開

完整實現代碼

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) 防止讀寫阻塞

為何生產環境建議用 JavaMail

  • 完整支援 MIME 多部分 (附件、內嵌圖片、替代文本/HTML)
  • 內建 SSL/TLS (SSLSocketFactory, STARTTLS)
  • 支援 OAuth2、DKIM 簽名、郵件隊列、重試機制
  • 編碼、換行、邊界字串等邊界情況處理完善
  • Socket 手寫僅適合教學、極簡環境、或無依賴限制場景
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved