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

JavaMail 發送 HTML 郵件完整實戰:支援附件、內嵌圖片、SSL/TLS

     編輯:Java 程式設計入門

JavaMail 核心架構

JavaMail API 提供平台無關、協議無關的郵件發送框架。核心類別:Session (環境)、Message (郵件)、Transport (傳輸)、Store (接收)。需引入 javax.mail:mail 或 Spring Boot spring-boot-starter-mail。

Maven 依賴


<dependency>
    <groupId>com.sun.mail</groupId>
    <artifactId>jakarta.mail</artifactId>
    <version>2.0.1</version>
</dependency>

完整發送服務實現

1. 郵件配置屬性類


@Data
@Builder
public class MailProperties {
    private String host;           // SMTP 伺服器
    private int port = 465;        // SSL 埠口 (587 for STARTTLS)
    private String username;       // 發信帳號
    private String password;       // 授權碼/密碼
    private String from;           // 發件人
    private String protocol = "smtp";
    private boolean sslEnable = true;
    private boolean starttlsEnable = false;
    private int timeout = 10000;   // 連線逾時 ms
    private String[] defaultTo;    // 預設收件人
}

2. 郵件內容建構器 (支援 HTML、附件、內嵌圖片)


public class MimeMessageBuilder {

    private final Session session;
    private final MailProperties props;

    public MimeMessageBuilder(Session session, MailProperties props) {
        this.session = session;
        this.props = props;
    }

    public MimeMessage buildSimple(String to, String subject, String htmlContent) 
            throws MessagingException {
        MimeMessage message = new MimeMessage(session);
        message.setFrom(new InternetAddress(props.getFrom()));
        message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(to));
        message.setSubject(subject, "UTF-8");
        message.setContent(htmlContent, "text/html;charset=UTF-8");
        message.setSentDate(new Date());
        return message;
    }

    public MimeMessage buildWithAttachments(String to, String subject, String htmlContent,
                                            List attachments, Map inlineImages) 
            throws MessagingException {
        MimeMessage message = new MimeMessage(session);
        message.setFrom(new InternetAddress(props.getFrom()));
        message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(to));
        message.setSubject(subject, "UTF-8");
        message.setSentDate(new Date());

        // 根容器:mixed (附件 + related)
        Multipart root = new MimeMultipart("mixed");

        // 關聯容器:related (HTML + 內嵌圖片)
        Multipart related = new MimeMultipart("related");

        // HTML 內容部分
        MimeBodyPart htmlPart = new MimeBodyPart();
        htmlPart.setContent(htmlContent, "text/html;charset=UTF-8");
        related.addBodyPart(htmlPart);

        // 內嵌圖片:cid: 引用
        if (inlineImages != null) {
            for (Map.Entry entry : inlineImages.entrySet()) {
                MimeBodyPart imgPart = new MimeBodyPart();
                imgPart.attachFile(entry.getValue());
                imgPart.setContentID("<" + entry.getKey() + ">"); // 
                imgPart.setDisposition(MimeBodyPart.INLINE);
                related.addBodyPart(imgPart);
            }
        }

        // 將 related 包裝進 mixed
        MimeBodyPart relatedWrapper = new MimeBodyPart();
        relatedWrapper.setContent(related);
        root.addBodyPart(relatedWrapper);

        // 附件部分
        if (attachments != null) {
            for (File file : attachments) {
                MimeBodyPart attPart = new MimeBodyPart();
                attPart.attachFile(file);
                attPart.setFileName(MimeUtility.encodeText(file.getName(), "UTF-8", "B"));
                root.addBodyPart(attPart);
            }
        }

        message.setContent(root);
        return message;
    }
}

3. 發送服務核心類


@Service
public class MailService {

    private final MailProperties props;
    private final Session session;

    public MailService(MailProperties props) {
        this.props = props;
        Properties javaMailProps = new Properties();
        javaMailProps.put("mail.smtp.host", props.getHost());
        javaMailProps.put("mail.smtp.port", props.getPort());
        javaMailProps.put("mail.smtp.auth", "true");
        javaMailProps.put("mail.smtp.timeout", props.getTimeout());
        javaMailProps.put("mail.smtp.connectiontimeout", props.getTimeout());

        if (props.isSslEnable()) {
            javaMailProps.put("mail.smtp.ssl.enable", "true");
            javaMailProps.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
            javaMailProps.put("mail.smtp.socketFactory.port", props.getPort());
        }
        if (props.isStarttlsEnable()) {
            javaMailProps.put("mail.smtp.starttls.enable", "true");
        }

        this.session = Session.getInstance(javaMailProps, new Authenticator() {
            @Override
            protected PasswordAuthentication getPasswordAuthentication() {
                return new PasswordAuthentication(props.getUsername(), props.getPassword());
            }
        });
        // 除錯模式
        // session.setDebug(true);
    }

    public void sendSimple(String to, String subject, String htmlContent) {
        send(Collections.singletonList(to), subject, htmlContent, null, null);
    }

    public void sendWithAttachments(String to, String subject, String htmlContent,
                                    List attachments, Map inlineImages) {
        send(Collections.singletonList(to), subject, htmlContent, attachments, inlineImages);
    }

    private void send(List toList, String subject, String htmlContent,
                      List attachments, Map inlineImages) {
        try {
            MimeMessageBuilder builder = new MimeMessageBuilder(session, props);
            MimeMessage message;
            if (attachments != null || inlineImages != null) {
                message = builder.buildWithAttachments(
                    String.join(",", toList), subject, htmlContent, attachments, inlineImages);
            } else {
                message = builder.buildSimple(String.join(",", toList), subject, htmlContent);
            }

            Transport.send(message);
            log.info("郵件發送成功: {} -> {}", props.getFrom(), toList);
        } catch (MessagingException e) {
            log.error("郵件發送失敗", e);
            throw new MailSendException("發送失敗: " + e.getMessage(), e);
        }
    }
}

主流郵件服務商 SMTP 設定

服務商SMTP 主機SSL 埠口STARTTLS 埠口備註
Gmailsmtp.gmail.com465587需開啟「應用程式密碼」
Outlook/Hotmailsmtp.office365.com465587企業帳號可能不同
QQ/騰訊企業郵smtp.qq.com / smtp.exmail.qq.com465587需開啟 SMTP 服務取得授權碼
163/126smtp.163.com465/994587需客戶端授權密碼
新浪smtp.sina.com465587免費帳號有發送限制

Spring Boot 整合最佳實踐

application.yml


spring:
  mail:
    host: smtp.gmail.com
    port: 465
    username: your@gmail.com
    password: 16位應用程式密碼
    protocol: smtp
    properties:
      mail:
        smtp:
          ssl:
            enable: true
          auth: true
          timeout: 10000
          connectiontimeout: 10000
        debug: false

使用 JavaMailSender (Spring 封裝)


@Service
@RequiredArgsConstructor
public class SpringMailService {
    private final JavaMailSender mailSender;

    public void sendHtml(String to, String subject, String html) {
        MimeMessage message = mailSender.createMimeMessage();
        try {
            MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
            helper.setFrom("your@gmail.com");
            helper.setTo(to);
            helper.setSubject(subject);
            helper.setText(html, true); // true = HTML
            // 附件
            // helper.addAttachment("report.pdf", new File("report.pdf"));
            // 內嵌圖片
            // helper.addInline("logo", new ClassPathResource("static/logo.png"));
            mailSender.send(message);
        } catch (MessagingException e) {
            throw new MailException("發送失敗", e);
        }
    }
}

常見問題與解決方案

  1. 535 Authentication failed:密碼錯誤、未開啟 SMTP/授權碼、雙因子認證需應用程式密碼
  2. Connection timeout:防火牆阻擋、埠口錯誤、VPN/代理干擾、雲主機安全組未放行
  3. 中文附件檔名亂碼:MimeUtility.encodeText(filename, "UTF-8", "B")
  4. HTML 圖片不顯示:需用 cid:contentId 並放在 multipart/related 中
  5. 大量發送效能:複用 Transport 連線、非同步佇列、批次發送、專用郵件閘道 (SendGrid、Amazon SES)

安全性最佳實踐

  • 密碼/授權碼存放於環境變數或配置中心,嚴禁硬編碼
  • 啟用 SSL/TLS 加密傳輸,禁止明文 SMTP
  • 發送端實作頻率限制,防止被列入黑名單
  • 加入 List-Unsubscribe 標頭,符合反垃圾郵件法規
  • 定期輪換授權碼,審計發送日誌
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved