iT邦幫忙

2023 iThome 鐵人賽

DAY 13
0
Software Development

救救我啊我救我!CRUD 工程師的惡補日記系列 第 13

【Spring Boot】使用 Java Mail 發送 HTML 郵件

  • 分享至 

  • xImage
  •  

昨天說明了如何發送純文字郵件。然而在商業平台,大多會設計 HTML 郵件,看起來較美觀且專業,也有人稱它為「EDM」。本文會示範發送 HTML 郵件,以及嵌入圖片的具體做法。而明天將引進 FreeMarker 引擎,有 HTML 基礎的讀者,屆時可整合今日內容,製作漂亮的郵件。


一、範例專案簡介

在上一篇文章,已經準備好 Spring Boot 專案,並在 application.properties 檔案提供參數,串接上 Gmail 服務。筆者在此就做個複習,不再贅述細節。

本文範例使用的郵件函式庫是由 Spring Boot 提供的。它對 Java Mail 進行了封裝,讓我們更容易使用。

<!-- Spring Boot Mail -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-mail</artifactId>
</dependency>

接著準備一個專門用來寄信的 Spring 元件。

@Service
public class MailService {

    @Autowired
    private JavaMailSender mailSender;
    
    // TODO
}

JavaMailSender 是一個介面,Spring Mail 的函式庫會自行建立實作類(JavaMailSenderImpl),才注入進來。

二、發送純 HTML 郵件

為何筆者特別加一個「純」字呢?純的意思是指郵件內容為基本的 HTML 字串,沒有添加圖片。就算有,也會是透過一個完整 URL 作為來源,而非第三節所介紹的內嵌(embed)。

下圖是一個純 HTML 的例子。
https://ithelp.ithome.com.tw/upload/images/20230912/20131107SKqHhmoazF.jpg

(一)發送郵件

發送 HTML 內容的做法相當容易,範例程式如下。

@Service
public class MailService {

    @Autowired
    private JavaMailSender mailSender;

    public void sendSimpleHtml(Collection<String> receivers, String subject, String content) {
        try {
            MimeMessage message = mailSender.createMimeMessage();
            MimeMessageHelper helper = new MimeMessageHelper(message);
            helper.setTo(receivers.toArray(new String[0]));
            helper.setSubject(subject);
            helper.setText(content, true);

            mailSender.send(message);
        } catch (MessagingException e) {
            throw new RuntimeException(e);
        }
    }
}

流程與上一篇寄送附件的範例程式大同小異。會先建立 MimeMessage 物件,將收件人、主旨與內容設置進去。

要注意的是,在呼叫 setText 方法設置內容時,第二個參數要給予 true。這個 boolean 參數的名稱是 html,用途在於聲明其內容為 HTML。所以看信時,客戶端要渲染(render)出應有的樣子。若設置為 false,只會看到 HTML 原始碼。

(二)測試

下面是一個發送簡單 HTML 的測試。

@Test
public void testSimpleHtml() {
    mailService.sendSimpleHtml(
            List.of("foo@gmail.com", "bar@yahoo.com.tw"),
            "Simple html",
            "<html><body><p>你好!</p><p>My name is <b>Vincent</b>.</p></body></html>"
    );
}

三、在郵件中嵌入圖片

在郵件中添加圖片,能夠讓內容看起來更專業。下圖是一個嵌入圖片的 HTML 郵件的例子。
https://ithelp.ithome.com.tw/upload/images/20230912/20131107QdgCoKoVVL.jpg

(一)發送郵件

下面的範例,是發送有內嵌圖片的 HTML 郵件,實作上會比較複雜。

public void sendMixedHtml(Collection<String> receivers, String subject, String htmlContent, Collection<DataSource> attachments) {
    try {
        // 設置收件人和主旨
        MimeMessage mimeMessage = mailSender.createMimeMessage();
        MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true, "utf-8");
        helper.setTo(receivers.toArray(new String[0]));
        helper.setSubject(subject);

        // 郵件內容由多個部份組成
        Multipart mimeMultipart = new MimeMultipart();
        mimeMessage.setContent(mimeMultipart);

        // 添加內文部份
        MimeBodyPart contentBodyPart = new MimeBodyPart();
        contentBodyPart.setContent(htmlContent, "text/html;charset=utf-8");
        mimeMultipart.addBodyPart(contentBodyPart);

        // 添加附件們
        for (DataSource source : attachments) {
            MimeBodyPart sourceBodyPart = new MimeBodyPart();
            
            // 用於一般附件
            sourceBodyPart.setFileName(source.getName());
            sourceBodyPart.setDataHandler(new DataHandler(source));
            
            // 用於內嵌圖片
            sourceBodyPart.setContentID("<" + source.getName() + ">");

            // 添加一個附件
            mimeMultipart.addBodyPart(sourceBodyPart);
        }

        // 儲存變更並發送郵件
        mimeMessage.saveChanges();
        mailSender.send(mimeMessage);
    } catch (MessagingException e) {
        throw new RuntimeException(e);
    }
}

這裡在構成郵件內容時,應用到了「Multipart」的概念。意思是郵件內容(MimeMultipart)會由多個部份(MimeBodyPart)組成。從範例程式來看,一開始先加進了內文部份。接著再把 DataSource 型態的附件,也放入 MimeBodyPart,並加進 MimeMultipart

附件資料會以 DataSource 型態的物件,添加到郵件中。昨天的文章已經說明如何得到 DataSource,例如從 FileFileOutputStreamBufferedImage 都可以。

(二)讓 HTML 引用圖片附件

思路上,是先將圖片以附件的形式放入郵件,再讓 HTML 的 <img> tag 去引用就行了。

在範例程式中,附件被設置了「Content ID」,這就是用來被參照的 key。假設附件的 Content ID 值被設成 "<logo.png>",則 <img> 的用法如下。

<img src='cid:logo.png' />

src 屬性值的前綴為「cid:」,後面再接著 Content ID 即可。當然讀者也可以給予其他的 style,例如長跟寬。

附帶一提,雖然 MimeMessageHelper.addAttachment 方法也可以添加附件,但為了方便,所以筆者在範例程式中選擇與內嵌圖片放在同一個 Collection<DataSource> 集合一同處理。

(三)測試

下面是一個發送有內嵌圖片的郵件的測試。

@Test
public void testEmbeddedHtmlMailWithAttachment() throws Exception {
    FileDataSource imageSource = new FileDataSource("logo.png");

    String htmlContent =
            "<html><body>"
            + "<p>Top</p>"
            + "<img src='cid:" + imageSource.getName() + "'/>"
            + "<p>Bottom</p>"
            + "</body></html>";

    mailService.sendMixedHtml(
            List.of("foo@gmail.com"),
            "Mixed Html",
            htmlContent,
            List.of(imageSource)
    );
}

得到的 Email 結果如下圖。
https://ithelp.ithome.com.tw/upload/images/20230928/20131107wvgvRsBGRf.jpg

四、心得分享

最後分享一下筆者剛開始接觸 HTML 郵件時的小心得。

一樣的 HTML 原始碼,在各個電子郵件客戶端,有可能會呈現出不一致的樣子。這是因為用來渲染 HTML 的引擎不同的關係。例如在 Gmail 和 Yahoo Mail 都能呈現出想要的樣子,但換做是 Outlook,有些 style 可能就不支援。

筆者還記得有次製作「圓角矩形」的按鈕時,在 Outlook 的桌面版,按鈕的四個角依然是直角的,一直無法呈現出圓角。

工作上實作 HTML 時,建議避免使用較「新穎」的 tag 或屬性,確保在大多數的客戶端都支援,能夠如預期呈現。或者說開發團隊接受特定的客戶端呈現不出某些 style,那就另當別論。

另外可以善用 <table> 這個 tag 來排版。透過表格的限制,讓文字和圖片不要亂跑。基於這一點,郵件內容的 UI 需要設計得單純一點,使用 <table> 實作會比較好處理。


今日文章到此結束!
最後推廣一下自己的部落格,我是「新手工程師的程式教室」的作者,請多指教/images/emoticon/emoticon41.gif


上一篇
【Spring Boot】使用 Java Mail 發送純文字郵件與附件
下一篇
【Spring Boot】整合 FreeMarker 產生 HTML 內容
系列文
救救我啊我救我!CRUD 工程師的惡補日記50
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言