最近研究 Gmail API 才發現,處理郵件是一件非常麻煩的事,操作 Gmail API 可說是我目前為止,所有經手過的 API 之中最難搞的一個。
如果可接受比較簡化的功能、不一定要使用 Gmail API 的話,那麼 Google Apps Script(以下簡稱 GAS) 提供了一個 GmailApp 服務,可以用最無腦的方式寄送 Gmail 郵件,不過缺點是一天最多 100 封郵件。
而操作 Gmail API 的優點是,一天至少可寄 500 封郵件(可參考官方文件「Usage Limits」),所以本篇還是說明 Gmail API 的使用方法,提供最簡易的操作環境及範例程式碼。
(圖片出處: pixabay.com)
1. 啟用 Gmail API
操作 Gmail API 第一步要解決的是 oAuth 身份驗證問題,這個流程很冗長、麻煩,網路上已有很多教學文章,例如可參考這篇「申請及啟用 Gmail API 的 OAuth 2.0 憑證」。
如果讀者方便使用 GAS 環境的話,恭喜你可以略過這些步驟,不需要處理身份驗證問題,參考官方文件「Advanced Gmail Service」,請見以下操作方式:
進入「Google 雲端硬碟」→「新增」→「更多」→「Google Apps Script」,可新增一個指令碼專案。
按「資源」→「進階 Google 服務」
往下捲到「Gmail API」,如上圖紅框,點擊右邊開關可開啟這個 API,再按下方「確定」即可。
很簡單的一個啟用步驟,以上完成後就可直接在 GAS 直接呼叫 Gmail API,完全不必進行身份驗證。
補充一下,以上流程適用於最近一兩個月內新增的 GAS 指令碼專案(2019.4.8 之後),如果你的指令碼專案在這個日期之前產生,請參考官方文件說明「Advanced Google services」,除了以上動作之外,還需要同時到 Google Cloud Platform 啟用 Gmail API,多做一個動作。不過其實 GAS 畫面上都會有說明以及附上連結,不必擔心。
2. 試用 Gmail API
同樣根據剛剛的官網文件,提供了幾個範例語法。在 GAS 中只要啟用 Gmail API 後就能直接執行全域物件 Gmail。
一方面可以參考 Gmail 官網「getProfile」的用法,調用以下語法就能取得自己的 profile 資料:
解決了麻煩的驗證問題後,終於可以開始寫信了,這下比較簡單了吧?很可惜,現在才是災難的開始。
1. 寄信語法
根據官方操作教學文件「Users.messages: send」,我找到 JS 範例語法如下:
需要帶入兩個變數,簡單說明一下:
但是郵件內容要怎麼填寫呢,直接放字串 "Hello World!" 這樣可以嗎?當然是不行的。
2. GmailApp
前面提到的字串 "Hello World!" 是指郵件內容的字串,但一封郵件需要填入的欄位很多,例如收件人、主旨、內容等等,所以 base64EncodedEmail 的內容不會是單純的一組字串。
以 GAS 提供的 GmailApp 為例,可參考說明文件「GmailApp」,提供的語法很簡單:
這樣是不是很無腦就能操作?我相信 Gmail API 也可以這麼做,但可惜並沒有針對這部分做簡化。
3. 郵件格式規範
從 Gmail API 官方文件,對於郵件內容字串只說要符合郵件格式規範:
而整份 API 說明文件看不到半個郵件內容範例,代表我們需要自行找出郵件格式規範 RFC 5322 來閱讀,然後想辦法弄成 Gmail API 可以接收、還能正常顯示的格式。但就算知道了什麼是 RFC 5322,也無法知道要怎麼包字串才能讓 Gmail API 接受,所以官方文件這部分可說是相當簡易,或許只有具備網路底層知識背景的工程師有辦法使用。
1. RFC 郵件規範
找到這個討論串「How to send a message successfully using the new Gmail REST API?」,終於知道什麼是 RFC 郵件規範,例如以下範例
不過實作上,真正需要的欄位主要是 To、Subject、以及郵件內容這三個。
2. base64 編碼
另外這個討論串「Google REST API - message in an RFC 2822 formatted and base64url encoded string」說明了如何針對郵件規範產生郵件,並進行 base64 編碼,作法重點如下:
如果你以上都看懂、知道如何進行了,官網文件「Users.messages: send」提供了 API 測試工具,可自行測試你的 base64 字串是否有效,能否成功寄出郵件。
3. GAS 轉換 base64 語法
GAS 官方提供了工具「Class Utilities」,其中 base64Encode() 就可直接進行 base64 編碼,但可惜有時測試成功,有時不行。
這篇「message in an RFC 2822 formatted and base64url encoded string」說明了 base64 編碼不一定能相容於網址字串,所以還需要對特定字元作轉換才行,例如 "+/" 這些字元。
所以最好 Utilities 使用另一個 base64EncodeWebSafe() 才能確保沒問題。
4. 中文編碼轉換
事情還沒完,坑非常多,使用中文傳送郵件會變成一堆問號 "??????",所以必須再將中文編碼成 UTF-8,GAS 完整的 base64 轉換語法如下:
5. 標題中文編碼轉換
以為中文編碼完終於可以正常寄信了,沒想到信件標題卻總是變成亂碼。
看了這篇「Gmail API not respecting UTF encoding in subject」,才知道原來根據 RFC 規範,郵件標題一律使用 7 位元編碼,難怪中文及需要 8 位元編碼的文字全都變亂碼,全世界各種語言都看得到災情。
這篇「unable to encode special characters email in right format, gmail api」,提供了正確處理方式及範例,需要將郵件標題用以下形式處理:
綜合以上心得及整理,以下附上 GAS 中執行 Gmail API 寄信的完整範例程式碼:
第一次執行時 GAS 會跳出要求授權的視窗,可參考「前端操作 Apps Script 上傳檔案」 →「三、撰寫 Apps Script 指令碼」的第 3 張圖開始看,開放權限給 API 即可執行。
接下來重點說明:
所以 GAS 這裡在操作 Gmail API 時,會面臨需要的功能,在官方文件找不到的窘境,解決方法請見下圖:
請依紅框處,手動輸入 Gmail API 的所有方法,GAS 系統都會出現提示文字以及該方法需要的變數,算是滿方便的。
試著執行以上範例程式碼,果然成功收到郵件,且可完全正常顯示了!
如果可接受比較簡化的功能、不一定要使用 Gmail API 的話,那麼 Google Apps Script(以下簡稱 GAS) 提供了一個 GmailApp 服務,可以用最無腦的方式寄送 Gmail 郵件,不過缺點是一天最多 100 封郵件。
而操作 Gmail API 的優點是,一天至少可寄 500 封郵件(可參考官方文件「Usage Limits」),所以本篇還是說明 Gmail API 的使用方法,提供最簡易的操作環境及範例程式碼。
(圖片出處: pixabay.com)
一、處理 OAuth 授權
1. 啟用 Gmail API
操作 Gmail API 第一步要解決的是 oAuth 身份驗證問題,這個流程很冗長、麻煩,網路上已有很多教學文章,例如可參考這篇「申請及啟用 Gmail API 的 OAuth 2.0 憑證」。
如果讀者方便使用 GAS 環境的話,恭喜你可以略過這些步驟,不需要處理身份驗證問題,參考官方文件「Advanced Gmail Service」,請見以下操作方式:
進入「Google 雲端硬碟」→「新增」→「更多」→「Google Apps Script」,可新增一個指令碼專案。
按「資源」→「進階 Google 服務」
往下捲到「Gmail API」,如上圖紅框,點擊右邊開關可開啟這個 API,再按下方「確定」即可。
很簡單的一個啟用步驟,以上完成後就可直接在 GAS 直接呼叫 Gmail API,完全不必進行身份驗證。
補充一下,以上流程適用於最近一兩個月內新增的 GAS 指令碼專案(2019.4.8 之後),如果你的指令碼專案在這個日期之前產生,請參考官方文件說明「Advanced Google services」,除了以上動作之外,還需要同時到 Google Cloud Platform 啟用 Gmail API,多做一個動作。不過其實 GAS 畫面上都會有說明以及附上連結,不必擔心。
2. 試用 Gmail API
同樣根據剛剛的官網文件,提供了幾個範例語法。在 GAS 中只要啟用 Gmail API 後就能直接執行全域物件 Gmail。
一方面可以參考 Gmail 官網「getProfile」的用法,調用以下語法就能取得自己的 profile 資料:
Logger.log(Gmail.Users.getProfile("me"))
二、Gmail API 寄信語法
解決了麻煩的驗證問題後,終於可以開始寫信了,這下比較簡單了吧?很可惜,現在才是災難的開始。
1. 寄信語法
根據官方操作教學文件「Users.messages: send」,我找到 JS 範例語法如下:
gmail.users.messages.send({
'userId': userId,
'resource': {
'raw': base64EncodedEmail
}
});
需要帶入兩個變數,簡單說明一下:
- userId 比較好解決,不用身份驗證後,只要填入字串 "me" 即可
- base64EncodedEmail 看起來是需要將郵件內容作 base64 編碼處理
但是郵件內容要怎麼填寫呢,直接放字串 "Hello World!" 這樣可以嗎?當然是不行的。
2. GmailApp
前面提到的字串 "Hello World!" 是指郵件內容的字串,但一封郵件需要填入的欄位很多,例如收件人、主旨、內容等等,所以 base64EncodedEmail 的內容不會是單純的一組字串。
以 GAS 提供的 GmailApp 為例,可參考說明文件「GmailApp」,提供的語法很簡單:
GmailApp.sendEmail(收件人, 主旨, 內容);
這樣是不是很無腦就能操作?我相信 Gmail API 也可以這麼做,但可惜並沒有針對這部分做簡化。
3. 郵件格式規範
從 Gmail API 官方文件,對於郵件內容字串只說要符合郵件格式規範:
* @param {String} email RFC 5322 formatted String.
而整份 API 說明文件看不到半個郵件內容範例,代表我們需要自行找出郵件格式規範 RFC 5322 來閱讀,然後想辦法弄成 Gmail API 可以接收、還能正常顯示的格式。但就算知道了什麼是 RFC 5322,也無法知道要怎麼包字串才能讓 Gmail API 接受,所以官方文件這部分可說是相當簡易,或許只有具備網路底層知識背景的工程師有辦法使用。
三、處理郵件格式
1. RFC 郵件規範
找到這個討論串「How to send a message successfully using the new Gmail REST API?」,終於知道什麼是 RFC 郵件規範,例如以下範例
From: John Doe
To: Mary Smith
Subject: Saying Hello
Date: Fri, 21 Nov 1997 09:55:06 -0600
Message-ID: <1234@local.machine.example>
This is a message just to say hello. So, "Hello".
不過實作上,真正需要的欄位主要是 To、Subject、以及郵件內容這三個。
2. base64 編碼
另外這個討論串「Google REST API - message in an RFC 2822 formatted and base64url encoded string」說明了如何針對郵件規範產生郵件,並進行 base64 編碼,作法重點如下:
- 從前面的郵件規範範例,每一行字串結尾都要加上 Enter 以及換行符號
- 將每行字串連結成一段完整字串
- 將此完整字串進行 base64 編碼
如果你以上都看懂、知道如何進行了,官網文件「Users.messages: send」提供了 API 測試工具,可自行測試你的 base64 字串是否有效,能否成功寄出郵件。
3. GAS 轉換 base64 語法
GAS 官方提供了工具「Class Utilities」,其中 base64Encode() 就可直接進行 base64 編碼,但可惜有時測試成功,有時不行。
這篇「message in an RFC 2822 formatted and base64url encoded string」說明了 base64 編碼不一定能相容於網址字串,所以還需要對特定字元作轉換才行,例如 "+/" 這些字元。
所以最好 Utilities 使用另一個 base64EncodeWebSafe() 才能確保沒問題。
4. 中文編碼轉換
事情還沒完,坑非常多,使用中文傳送郵件會變成一堆問號 "??????",所以必須再將中文編碼成 UTF-8,GAS 完整的 base64 轉換語法如下:
Utilities.base64EncodeWebSafe(符合郵件規範的字串, Utilities.Charset.UTF_8)
5. 標題中文編碼轉換
以為中文編碼完終於可以正常寄信了,沒想到信件標題卻總是變成亂碼。
看了這篇「Gmail API not respecting UTF encoding in subject」,才知道原來根據 RFC 規範,郵件標題一律使用 7 位元編碼,難怪中文及需要 8 位元編碼的文字全都變亂碼,全世界各種語言都看得到災情。
這篇「unable to encode special characters email in right format, gmail api」,提供了正確處理方式及範例,需要將郵件標題用以下形式處理:
Subject: =?utf-8?B?這裡放入經過base64編碼的中文字串?=
四、寄信範例程式碼
綜合以上心得及整理,以下附上 GAS 中執行 Gmail API 寄信的完整範例程式碼:
function sendMail() {
var recipient = "xxxxxxxx@gmail.com", // 收信者郵件地址
subject = "測試郵件主旨", // 郵件主旨
content = "這裡是郵件內容!!!", // 郵件內容
mailData = "",
rawData = {},
base64Data, response;
mailData += "To: " + recipient + "\r\n"; // 每行字串要加上 Enter 及換行符號
mailData += "Subject: =?utf-8?B?" + Utilities.base64Encode(subject, Utilities.Charset.UTF_8) + "?=\r\n\r\n"; // 主旨這一行一定要有兩個換行;主旨字串一定需要額外重新 base64 編碼過一次
mailData += content;
base64Data = Utilities.base64EncodeWebSafe(mailData, Utilities.Charset.UTF_8);
rawData = {
"raw": base64Data
};
response = Gmail.Users.Messages.send(rawData, "me");
Logger.log(response);
}
第一次執行時 GAS 會跳出要求授權的視窗,可參考「前端操作 Apps Script 上傳檔案」 →「三、撰寫 Apps Script 指令碼」的第 3 張圖開始看,開放權限給 API 即可執行。
接下來重點說明:
- recipient、subject、content 三個重要字串請見註解說明填寫
- 「收信者郵件地址」這一行後面一定要加上 Enter 及換行符號
- 「郵件主旨」這一行後面一定要加上兩個 Enter 及換行符號,否則郵件會無法寄出
- 所有的字串都需要經過 base64 編碼,而「郵件主旨」的字串需要額外再 base64 編碼一次,等於經過 2 次編碼,否則一定無法顯示中文
- Gmail.Users.Messages.send 的參數與 Gmail API 有出入,原因不明。
所以 GAS 這裡在操作 Gmail API 時,會面臨需要的功能,在官方文件找不到的窘境,解決方法請見下圖:
請依紅框處,手動輸入 Gmail API 的所有方法,GAS 系統都會出現提示文字以及該方法需要的變數,算是滿方便的。
試著執行以上範例程式碼,果然成功收到郵件,且可完全正常顯示了!
更多 Google Apps Script 使用技巧: