昨天說明了如何發送純文字郵件。然而在商業平台,大多會設計 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 字串,沒有添加圖片。就算有,也會是透過一個完整 URL 作為來源,而非第三節所介紹的內嵌(embed)。
下圖是一個純 HTML 的例子。
發送 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 郵件的例子。
下面的範例,是發送有內嵌圖片的 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
,例如從File
、FileOutputStream
與BufferedImage
都可以。
思路上,是先將圖片以附件的形式放入郵件,再讓 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 結果如下圖。
最後分享一下筆者剛開始接觸 HTML 郵件時的小心得。
一樣的 HTML 原始碼,在各個電子郵件客戶端,有可能會呈現出不一致的樣子。這是因為用來渲染 HTML 的引擎不同的關係。例如在 Gmail 和 Yahoo Mail 都能呈現出想要的樣子,但換做是 Outlook,有些 style 可能就不支援。
筆者還記得有次製作「圓角矩形」的按鈕時,在 Outlook 的桌面版,按鈕的四個角依然是直角的,一直無法呈現出圓角。
工作上實作 HTML 時,建議避免使用較「新穎」的 tag 或屬性,確保在大多數的客戶端都支援,能夠如預期呈現。或者說開發團隊接受特定的客戶端呈現不出某些 style,那就另當別論。
另外可以善用 <table>
這個 tag 來排版。透過表格的限制,讓文字和圖片不要亂跑。基於這一點,郵件內容的 UI 需要設計得單純一點,使用 <table>
實作會比較好處理。
今日文章到此結束!
最後推廣一下自己的部落格,我是「新手工程師的程式教室」的作者,請多指教