使用 MimeKit、C# Winforms 和 Google API 的 Gmail 草稿(HTML 带附件)

Gmail Draft (HTML with Attachment) with MimeKit, C# Winforms and Google API

我正在尝试使用 C# 在 winforms 应用程序中生成 Gmail 草稿邮件。消息草稿需要采用 HTML 格式并且能够包含附件。

我能够使用 AE.Net.Mail 生成带有附件的草稿,但草稿消息是纯文本的(我不知道如何编码 AE.Net.Mail 给我一个 HTML Gmail 邮件草稿)。

为了将消息转换为 HTML 格式,我使用 MimeKit 获取 System.Net.Mail 消息并将其转换为 MimeMessage 消息。但是,我无法弄清楚如何按照 Gmail 草案规范的要求将 MIME 消息放入 RFC 2822 格式和 URL-安全的 base64 编码字符串中。

这是来自 MimeKit 转换尝试的代码

var service = new GmailService(new BaseClientService.Initializer()
{
    HttpClientInitializer = credential,
    ApplicationName = ApplicationName,
});

MailMessage msg = new MailMessage(); //System.Net.Mail
msg.IsBodyHtml = true;
msg.Subject = "HTML Email";
msg.Body = "<a href = 'http://www.yahoo.com/'>Enjoy Yahoo!</a>";
msg.Attachments.Add(file);

MimeMessage message = MimeMessage.CreateFromMailMessage(msg); //MimeKit conversion

//At this point I cannot figure out how to get the MIME message into 
//an RFC 2822 formatted and URL-safe base64 encoded string
//as required by the Gmail draft specification
//See working code below for how this works in AE.Net.Mail

下面是使用 AE.Net.Mail 的代码,它可以正常工作,但会将 Gmail 草稿的正文生成为纯文本(基于 Jason Pettys 的 this article ):

 var service = new GmailService(new BaseClientService.Initializer()
 {
     HttpClientInitializer = credential,
     ApplicationName = ApplicationName,
 });

 var msg = new AE.Net.Mail.MailMessage //msg created in plain text not HTML format
 {
     Body = "<a href = 'http://www.yahoo.com/'>Enjoy Yahoo!</a>"
 };

 var bytes = System.IO.File.ReadAllBytes(filePath);
 AE.Net.Mail.Attachment file = new AE.Net.Mail.Attachment(bytes, @"application/pdf", FileName, true);
 msg.Attachments.Add(file);

 var msgStr = new StringWriter();
 msg.Save(msgStr);
 Message m = new Message();
 m.Raw = Base64UrlEncode(msgStr.ToString());

 Draft draft = new Draft(); //Gmail draft
 draft.Message = m;

 service.Users.Drafts.Create(draft, "me").Execute();

 private static string Base64UrlEncode(string input)
 {
     var inputBytes = System.Text.Encoding.ASCII.GetBytes(input);
     // Special "url-safe" base64 encode.
     return Convert.ToBase64String(inputBytes)
       .Replace('+', '-')
       .Replace('/', '_')
       .Replace("=", "");
 }

有没有办法将 MimeKit 的 MimeMessage 消息转换为 RFC 2822 格式和 URL 安全的 base64 编码字符串,以便将其生成为 Gmail 草稿?如果做不到这一点,有没有办法在编码之前以 HTML 格式创建 AE.Net.Mail 消息?非常感谢所有帮助。

做你想做的最简单的方法是这样的:

static string Base64UrlEncode (MimeMessage message)
{
    using (var stream = new MemoryStream ()) {
        message.WriteTo (stream);

        return Convert.ToBase64String (stream.GetBuffer (), 0, (int) stream.Length)
            .Replace ('+', '-')
            .Replace ('/', '_')
            .Replace ("=", "");
    }
}

但更有效的方法是实现我们自己的 UrlEncoderFilter 来替换您的“.Replace (...)”逻辑:

using MimeKit;
using MimeKit.IO;
using MimeKit.IO.Filters;

// ...

class UrlEncodeFilter : IMimeFilter
{
    byte[] output = new byte[8192];

    #region IMimeFilter implementation
    public byte[] Filter (byte[] input, int startIndex, int length, out int outputIndex, out int outputLength)
    {
        if (output.Length < input.Length)
            Array.Resize (ref output, input.Length);

        int endIndex = startIndex + length;

        outputLength = 0;
        outputIndex = 0;

        for (int index = startIndex; index < endIndex; index++) {
            switch ((char) input[index]) {
            case '\r': case '\n': case '=': break;
            case '+': output[outputLength++] = (byte) '-'; break;
            case '/': output[outputLength++] = (byte) '_'; break;
            default: output[outputLength++] = input[index]; break;
            }
        }

        return output;
    }

    public byte[] Flush (byte[] input, int startIndex, int length, out int outputIndex, out int outputLength)
    {
        return Filter (input, startIndex, length, out outputIndex, out outputLength);
    }

    public void Reset ()
    {
    }
    #endregion
}

你的使用方式是这样的:

static string Base64UrlEncode (MimeMessage message)
{
    using (var stream = new MemoryStream ()) {
        using (var filtered = new FilteredStream (stream)) {
            filtered.Add (EncoderFilter.Create (ContentEncoding.Base64));
            filtered.Add (new UrlEncodeFilter ());

            message.WriteTo (filtered);
            filtered.Flush ();
        }

        return Encoding.ASCII.GetString (stream.GetBuffer (), 0, (int) stream.Length);
    }
}

Failing that, is there a way to create an AE.Net.Mail message in HTML format prior to encoding it?

你可以试试

msg.ContentType = "text/html"; 

在你的AE.Net.Mail.MailMessage

这是我用来创建带有附件的 gmail 草稿的 C# 代码...

using System;
using Google.Apis.Auth.OAuth2;
using Google.Apis.Gmail.v1;
using Google.Apis.Services;
using Google.Apis.Util.Store;
using System.IO;
using System.Threading;
using System.Net.Mail;

namespace SendStatusReportsAddin1
{
    class Program
    {
        // If modifying these scopes, delete your previously saved credentials
        // at ~/.credentials/gmail-dotnet-quickstart.json
        //static string[] Scopes = { GmailService.Scope.GmailReadonly };
        static string[] Scopes = { GmailService.Scope.MailGoogleCom };
        static string ApplicationName = "Gmail API .NET Quickstart";

        static void Main(string[] args)
        {
            //Authorization
              UserCredential credential;

              using (var stream =
                  new FileStream("client_secret2.json", FileMode.Open, FileAccess.Read))
              {
                  string credPath = System.Environment.GetFolderPath(
                      System.Environment.SpecialFolder.Personal);
                  credPath = Path.Combine(credPath, ".credentials3/gmail-dotnet.json");

                  credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
                      GoogleClientSecrets.Load(stream).Secrets,
                      Scopes,
                      "user",
                      CancellationToken.None,
                      new FileDataStore(credPath, true)).Result;
                  Console.WriteLine("Credential file saved to: " + credPath);
              }

            //Create Gmail API service.
              var service = new GmailService(new BaseClientService.Initializer()
              {
                  HttpClientInitializer = credential,
                  ApplicationName = ApplicationName,
              });

            //Create mail message
              MailMessage mailmsg = new MailMessage();
              {
                  mailmsg.Subject = "My test subject";
                  mailmsg.Body = "<b>My smart message </b>";
                  mailmsg.From = new MailAddress("joe.blow@hotmail.com");
                  mailmsg.To.Add(new MailAddress("jeff.jones@gmail.com"));
                  mailmsg.IsBodyHtml = true;
              }

            //add attachment
              string statusreportfile =
                        @"C:\Users\ey96a\Google Drive Status-Vacation-Expense\Status Reports\UUM RewriteStatus Report.pdf";

              Attachment data = new Attachment(statusreportfile);
              mailmsg.Attachments.Add(data);

            //Make mail message a Mime message
              MimeKit.MimeMessage mimemessage = MimeKit.MimeMessage.CreateFromMailMessage(mailmsg);

            //Use Base64URLEncode to encode the Mime message
              Google.Apis.Gmail.v1.Data.Message finalmessage = new Google.Apis.Gmail.v1.Data.Message();
              finalmessage.Raw = Base64UrlEncode(mimemessage.ToString());

            //Create the draft email
              var mydraft = new Google.Apis.Gmail.v1.Data.Draft();
              mydraft.Message = finalmessage;

              var resultdraft = service.Users.Drafts.Create(mydraft, "me").Execute();

            //Send the email (instead of creating a draft)
              var resultsend = service.Users.Messages.Send(finalmessage, "me").Execute();

            //Open the SendStatusReports form
              aOpenForm.myForm1();

        }  //end of Main

        //Base64 URL encode
        public static string Base64UrlEncode(string input)
        {
            var inputBytes = System.Text.Encoding.UTF8.GetBytes(input);
            // Special "url-safe" base64 encode.

            return System.Convert.ToBase64String(inputBytes)
                .Replace('+', '-')
                .Replace('/', '_')
                .Replace("=", "");
        }

    } //end of class Program

}