The Will Will Web

記載著 Will 在網路世界的學習心得與技術分享

如何透過 .NET 操作 Google Workspace 的 Gmail API 發送郵件

Gmail API 是一個 RESTful API,主要用來管理 Gmail 信箱與發送郵件,而要通過認證的方法,就是使用 Google 的 OAuth 來進行驗證與授權,若你要用 .NET 來開發 Google API 應用,可以說幾乎找不到完整的文件與範例程式可供參考,所以上手確實有點難度。今天我就整理一下今天的研究心得,記錄一些常見的程式寫法。

glowing-sky-sphere-orbits-starry-galaxy-generated-by-ai-2

若為主控台應用程式該如何取得 OAuth 授權並發信

由於 OAuth 2.0 的 Authorization Code Flow 是一個需要人工介入的流程,例如你想用 abc@gmail.com 這個信箱發信,就要請 abc@gmail.com 這個帳號進行授權,讓你得到 Access Token 之後,才能代表 abc@gmail.com 這個帳號,拿著最終的 Access Token 來發信。但是 Console (主控台應用程式) 類型的應用程式並沒有瀏覽器可以跟使用者互動,所以解決方法就是另開瀏覽器來進行 OAuth 授權流程。

  1. 先到 Google Cloud console 網站初始化一些設定

    主要是建立專案、啟用 Gmail API 服務、建立 OAuth 2.0 Client ID 憑證(請記得要選取 DesktopInstalled application 類型)、下載 client_secret.json 檔案 (裡面包含 Client Secrets 資訊)。

  2. 安裝一些 NuGet 套件

    Install-Package MailKit
    Install-Package Google.Apis.Auth
    Install-Package Google.Apis.Gmail.v1
    
  3. 透過 GoogleWebAuthorizationBroker 進行驗證

    這個 GoogleWebAuthorizationBroker.AuthorizeAsync() 會協助使用者進行驗證,並回傳 UserCredential 物件,會在第一次執行時全自動替用戶端開啟一個瀏覽器,幫助使用者進行 OAuth 2.0 的 Authorization Code Flow!

    這個 GoogleWebAuthorizationBroker.AuthorizeAsync() 其實背後做了很多事,他會先在本機 Listen 一個 Port 專門用來當成 OAuth 的 Redirect URI 使用,好讓 OAuth 授權流程都完成後,自動取回 Access Token 與相關資訊,然後再關閉這個 Port 的使用。因此,請千萬不要在 Web 應用程式中使用這個 API 喔,你只能用在 Console 或 Windows Application 之類的應用上,也就是 Google 所稱呼的 Installed application

    using Google.Apis.Auth.OAuth2;
    using Google.Apis.Gmail.v1;
    using Google.Apis.Util.Store;
    using Google.Apis.Services;
    
    string[] scopes = { GmailService.Scope.GmailSend };
    
    UserCredential credential;
    
    using var stream = new FileStream("client_secret.json", FileMode.Open, FileAccess.Read);
    
    var credentialsPath = ".credentials";
    
    credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
        GoogleClientSecrets.FromStream(stream).Secrets,
        scopes,
        "user",
        CancellationToken.None,
        new FileDataStore(credentialsPath, true));
    
    if (credential.Token.IsExpired(Google.Apis.Util.SystemClock.Default))
    {
        if (!await credential.RefreshTokenAsync(CancellationToken.None))
        {
            throw new Exception("Access Token 已經過期,而且執行 Refresh Token 作業失敗!");
        }
    }
    
    Console.WriteLine("Credential file saved to: " + credentialsPath);
    

    上述程式碼的目的在於取得一個 UserCredential 型別的 credential 物件!

  4. 準備郵件內容

    這裡我使用 MailKit 套件來準備郵件內容,程式碼如下:

    var fromName = "寄件者名稱";
    var fromEmail = "abc@gmail.com";
    var toName = "收件者名稱";
    var toEmail = "target@gmail.com";
    var subject = "這是一封測試郵件";
    var body = @"這是一封測試郵件,請勿回覆。";
    
    var message = new MimeMessage();
    message.From.Add(new MailboxAddress(fromName, fromEmail));
    message.To.Add(new MailboxAddress(toName, toEmail));
    message.Subject = subject;
    
    var builder = new BodyBuilder();
    builder.TextBody = body;
    message.Body = builder.ToMessageBody();
    
  5. 準備 RFC2822 格式的郵件內容

    我們可以透過 MimeMessage 型別下的 WriteTo 方法將內容先寫入到 MemoryStream 之中,再透過 Encoding.UTF8.GetString() 取得完整的內容。

    using var mem = new MemoryStream();
    message.WriteTo(mem);
    var rfc2822 = Encoding.UTF8.GetString(mem.ToArray());
    
  6. 準備一個 Google.Apis.Gmail.v1.Data.Message 物件

    其實 Gmail API 有點難用,他必須要你自己準備好 RFC2822 格式的郵件內容,然後用 Base64 編碼後才能送出郵件!

    string Base64UrlEncode(string input)
    {
        var inputBytes = System.Text.Encoding.UTF8.GetBytes(input);
        return Convert.ToBase64String(inputBytes).Replace("+", "-").Replace("/", "_").Replace("=", "");
    }
    
    var msg = new Google.Apis.Gmail.v1.Data.Message();
    msg.Raw = Base64UrlEncode(rfc2822); // 一定要用 Base64 編碼過
    
  7. 建立一個 Gmail API 服務物件 (即 GmailService 實例)

    這裡要帶入 credential 物件到 HttpClientInitializer 屬性中,他會在呼叫 RESTful API 時自動加入必要的 client_idclient_secret 資訊:

    var service = new GmailService(new BaseClientService.Initializer()
    {
        HttpClientInitializer = credential,
        ApplicationName = "Gmail Sender",
    });
    
  8. 透過 Gmail API 發送郵件

    // 這裡的 "me" 代表著通過 OAuth 驗證的那個使用者
    service.Users.Messages.Send(msg, "me").Execute();
    

若為 ASP.NET Core 應用程式該如何取得 OAuth 授權並發信

  1. 先到 Google Cloud console 網站初始化一些設定

    主要是建立專案、啟用 Gmail API 服務、建立 OAuth 2.0 Client ID 憑證(請記得要選取 Web application 類型)、下載 client_secret.json 檔案 (裡面包含 Client Secrets 資訊)。

  2. 透過標準的 OAuth 2.0 流程,在 redirect_uri 端點取得 Access TokenRefresh Token

  3. 發信之前要先取得一個 UserCredential 物件

    string[] scopes = { GmailService.Scope.GmailSend };
    
    var credentialsPath = ".credentials";
    var access_token = "...";
    var refresh_token = "...";
    
    var jsonCredentials = Path.Combine(Path.GetDirectoryName(Util.CurrentQueryPath), "client_secret.json");
    using var stream = new FileStream(jsonCredentials, FileMode.Open, FileAccess.Read);
    var flow = new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
    {
        ClientSecrets = GoogleClientSecrets.FromStream(stream).Secrets,
        Scopes = scopes,
        DataStore = new FileDataStore(credentialsPath)
    });
    
    var myToken = new TokenResponse
    {
        AccessToken = access_token,
        RefreshToken = refresh_token
    };
    
    var credential = new UserCredential(flow, Environment.UserName, myToken);
    
  4. 準備郵件內容

    這裡我使用 MailKit 套件來準備郵件內容,程式碼如下:

    var fromName = "寄件者名稱";
    var fromEmail = "abc@gmail.com";
    var toName = "收件者名稱";
    var toEmail = "target@gmail.com";
    var subject = "這是一封測試郵件";
    var body = @"這是一封測試郵件,請勿回覆。";
    
    var message = new MimeMessage();
    message.From.Add(new MailboxAddress(fromName, fromEmail));
    message.To.Add(new MailboxAddress(toName, toEmail));
    message.Subject = subject;
    
    var builder = new BodyBuilder();
    builder.TextBody = body;
    message.Body = builder.ToMessageBody();
    
  5. 準備 RFC2822 格式的郵件內容

    我們可以透過 MimeMessage 型別下的 WriteTo 方法將內容先寫入到 MemoryStream 之中,再透過 Encoding.UTF8.GetString() 取得完整的內容。

    using var mem = new MemoryStream();
    message.WriteTo(mem);
    var rfc2822 = Encoding.UTF8.GetString(mem.ToArray());
    
  6. 準備一個 Google.Apis.Gmail.v1.Data.Message 物件

    其實 Gmail API 有點難用,他必須要你自己準備好 RFC2822 格式的郵件內容,然後用 Base64 編碼後才能送出郵件!

    string Base64UrlEncode(string input)
    {
        var inputBytes = System.Text.Encoding.UTF8.GetBytes(input);
        return Convert.ToBase64String(inputBytes).Replace("+", "-").Replace("/", "_").Replace("=", "");
    }
    
    var msg = new Google.Apis.Gmail.v1.Data.Message();
    msg.Raw = Base64UrlEncode(rfc2822); // 一定要用 Base64 編碼過
    
  7. 建立一個 Gmail API 服務物件 (即 GmailService 實例)

    這裡要帶入 credential 物件到 HttpClientInitializer 屬性中,他會在呼叫 RESTful API 時自動加入必要的 client_idclient_secret 資訊:

    var service = new GmailService(new BaseClientService.Initializer()
    {
        HttpClientInitializer = credential,
        ApplicationName = "Gmail Sender",
    });
    
  8. 建立一個 GmailService 實例

    var service = new GmailService(new BaseClientService.Initializer()
    {
        HttpClientInitializer = credential,
        ApplicationName = "My App",
    });
    
  9. 透過 Gmail API 發送郵件

    // 這裡的 "me" 代表著通過 OAuth 驗證的那個使用者
    service.Users.Messages.Send(msg, "me").Execute();
    

相關連結

留言評論