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

JavaMail 實戰:163 郵箱發送郵件至 QQ 郵箱完整範例(含附件與 SSL)

     編輯:Java 相關知識

前言

郵件發送是企業級應用的基礎功能。本文提供一個生產環境可用的完整範例,展示如何使用 JavaMail API 透過 163 SMTP 伺服器(SSL 加密)發送 HTML 郵件至 QQ 郵箱,並支援多收件人與附件功能。

關鍵前置準備

  1. 開啟 163 郵箱 SMTP/IMAP 服務:設定 → 客戶端授權密碼 → 開啟 SMTP 服務
  2. 獲取授權碼:非登入密碼,而是專用的 16 位元授權碼
  3. 引入依賴:javax.mail:javax.mail-api:1.6.2、com.sun.mail:javax.mail:1.6.2

完整發送程式碼

package 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.SSLHandshakeExceptionJDK 版本過舊不支援 TLS 1.2+升級 JDK 8u161+ 或配置信任憑證
附件中文檔名亂碼未編碼檔名使用 MimeUtility.encodeText()
發送成功但對方未收到進垃圾信箱/被反垃圾攔截檢查 SPF/DKIM、避免敏感關鍵字、使用企業郵箱

進階優化建議

  • 連線池化:大量發送時使用 Transport.connect() 復用連線,避免重複 SSL 握手
  • 非同步發送:結合 CompletableFuture 或訊息佇列(RabbitMQ/Kafka)解耦業務流程
  • 模板引擎:整合 Thymeleaf/FreeMarker 產生複雜 HTML 郵件
  • 退信處理:監聽 Bounce 郵件,自動清理無效地址
  • 安全性:生產環境請勿使用 setTrustAllHosts(true),應正確載入信任憑證鏈

同事踩坑實錄

有同事直接複製代碼卻報錯,最終發現是授權碼過期/錯誤。重新在 163 郵箱後台「重置客戶端授權碼」後即恢復正常。授權碼異常是 90% 發送失敗的主因,請優先排查。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved