﻿# 如何使用 Google 的 OAuth 2.0 與 Gmail 的 REST API 發信

前陣子在企業授課 OAuth 2.0 的時候，我原本想現場示範走 Google 的 OAuth 2.0 流程，並且在取得 Access Token 之後怎樣透過原始的 REST API 發信。原本以為這是個很簡單的任務，誰知道膝蓋中了一箭，拿 Token 都很簡單，唯讀「[發信](https://developers.google.com/gmail/api/guides/sending)」的困難度超出了我的想像，原因無他，就是 Google 的參考文件實在是寫太爛了。這篇文章我打算跟大家說個官方文件沒寫的資訊，教大家怎樣順利透過 [Gmail API](https://developers.google.com/gmail/api/reference/rest) 發出郵件。

![An abstract wide image showcasing the concept of using Google OAuth 2.0 with Gmail REST API for sending emails](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/d2c39e30452352d3defd-8a9881b7-b835-4a92-bcbd-566a3b6b7008.webp)

> 這邊我先跳過 OAuth 2.0 的流程，假設大家已經取得 Access Token 的前提下，該如何成功發信。若想學習 OAuth 2.0 授權協定，我預計在 2025 上半年[開辦一堂新課](https://www.accupass.com/organizer/detail/1507090942441281249650)，好好的幫大家梳理一下 OAuth 2.0 協定。

### TL;DR

我直接顯示能發信成功完整送出 HTTP 要求並能成功發信的完整封包內容給大家看，明眼人應該一看就懂：

```yaml
POST https://gmail.googleapis.com/upload/gmail/v1/users/username@gmail.com/messages/send
Authorization: Bearer ya29.a0ARW5m77F24cmI0AeVdecSp0Hk77zYW3ktfo6o3Khcb7zAIAgSixM3XIQEf_9FS6ITz5cQI5gqoU8Th5XCHpFuSLm0ixbbV7DMYAzwoRwtPIv3cwp1PzPIrkeEZJ-hamOIDUaSRwmOL4gB69xJa3w2bi2K6YHr6oYAq-dtiTHaCgYKAb0SARESFQHGX2Miv7cgoQaUd6pJBYow-fQFOw0175
Content-Type: message/rfc822

From: "Will from Gmail" <username@gmail.com>
To: user@example.com
Subject: Hello World
MIME-Version: 1.0
Content-Type: text/html; charset=utf-8

這是一封測試信件
```

這份 HTTP 封包，比較特別的地方有：

1.  `Content-Type` 必須使用 `message/rfc822` 內容類型。
    
    這是 [RFC822](https://datatracker.ietf.org/doc/html/rfc822) 規範的格式，大部分人應該不會特別去學這種格式。
    
2.  整個 HTTP 的要求主體 (Request Body) 並非 JSON 格式，而是你必須要瞭解 [RFC822](https://datatracker.ietf.org/doc/html/rfc822) 規範才知道怎麼寫。
    

我們從結果出發，整件事看起來當然是非常簡單。但你要知道這樣寫，本身就必須經歷一連串鬼打牆過程，原因就是 Google 文件完全沒有提及。

### 鬼打牆的過程

OAuth 2.0 的概念非常簡單，說穿了就是要規範授權的流程，其最終的產出物就是讓**用戶端**拿到 **Access Token** 而已。但是要能深入理解 OAuth 2.0 所有授權流程，要讓學員能夠全盤瞭解，真的不是非常容易，所以我需要花上一整天的時間讓學員理解全貌。

我們拿到 **Access Token** 之後，理論上透過 [RFC 6750: OAuth 2.0 Bearer Token Usage](https://oauth.net/2/bearer-tokens/) 的方式將 **Access Token** 加入到 `Authorization` 標頭即可呼叫**資源伺服器** (Resource Server)。

所以，當我想透過 Gmail API 發信，在既有的 OAuth 2.0 架構下，開發人員應該要能想到以下步驟：

1.  先決定你想呼叫哪個 API
2.  再查詢你想呼叫的 API 需要哪些權限 (`scope`)
3.  想辦法完成 OAuth 2.0 授權流程並取得 Access Token
4.  呼叫你想呼叫的 API 並帶入 Access Token

是的，就上述四步驟而已。相信我，雖然 OAuth 2.0 不是很好學，但最難的有時候不一定是在 OAuth 的環節！😅

因為我已經完成了上述前 3 個步驟，第四個步驟不就查一下文件就可以了嗎？

沒錯，你看看 [Gmail API](https://developers.google.com/gmail/api/reference/rest) 文件，你能找到主要發信的 [REST Resource: v1.users.messages](https://developers.google.com/gmail/api/reference/rest/v1/users.messages) 相關說明。

我真的不相信有人類第一次用的時候看的懂啊！😒

雖然 Google 還是有一份 [Sending Email](https://developers.google.com/gmail/api/guides/sending) 文件，很詳盡的提供了範例，但是只有提供 Java 與 Python 程式語言的範例而已。這兩個範例主要使用了 Google 開發的 Google APIs 套件，使用上並不複雜，但是 C# 怎麼辦？沒說！要透過 curl 搭配 bash 呼叫怎麼辦？沒說！我覺得這並不是一份**開發人員友善**(Developer Friendly)的文件！連 REST API 文件都沒有！差評！🔥

最後，我還是要靠 [Felo Search](https://felo.ai/?invite=dOLn1YloyaD3j) 幫我到處上網找答案！

但是找到的答案也不正確，網路上的資料依然很亂，還需要靠 AI 進行「深度推理」才能理解怎樣處理，我最常遇到的錯誤訊息是：

```
Error 400：Media type 'application/json' is not supported.
```

簡單來說，Gmail API 根本不支援 `application/json` 這種內容類型，但 Google 的 Gmail API 文件都用 JSON 呈現，會讓人誤以為只能用 JSON 來呼叫，這文件完全誤導了開發人員理解的方向。

最後我才理解到，要求主體 (Request Body) 格式是錯誤的，應該用 RFC822 格式才對吧？所以我在 [Felo Search](https://felo.ai/?invite=dOLn1YloyaD3j) 追問，而且還用不同的「提示」來問，這才得到正確的解答：

```
我得到以下錯誤：
Error 400：Media type 'application/json' is not supported.

但是目前 curl 的 -d 似乎還是傳 JSON 格式，應該調整成什麼內容才對？
```

最終我得到以下答案：

```bash
curl -X POST \
  -H "Authorization: Bearer <YOUR_ACCESS_TOKEN>" \
  -H "Content-Type: message/rfc822" \
  --data-raw "From: sender@example.com
To: recipient@example.com
Subject: Test Email
Content-Type: text/plain

This is the email body." \
  "https://gmail.googleapis.com/gmail/v1/users/me/messages/send"
```

如此一來，我終於可以成功發信了！👍

### 相關連結

-   [Gmail API | Google for Developers](https://developers.google.com/gmail/api/reference/rest)
-   [Sending Email | Gmail | Google for Developers](https://developers.google.com/gmail/api/guides/sending)
-   [Method: users.messages.send | Gmail | Google for Developers](https://developers.google.com/gmail/api/reference/rest/v1/users.messages/send)
-   [REST Resource: users.messages | Gmail | Google for Developers](https://developers.google.com/gmail/api/reference/rest/v1/users.messages)
