2

I am attempting to generate a Gmail draft message in a winforms application using C#. The draft message needs to be in HTML format and able to contain an attachment.

I was able to generate a draft with an attachment using AE.Net.Mail, but the draft message was in plain text (I couldn't figure out how to code AE.Net.Mail to give me an HTML Gmail draft message).

In an attempt to get the message into an HTML format I used MimeKit to take a System.Net.Mail message and convert it into a MimeMessage message. However, I cannot figure out how to get the MIME message into in an RFC 2822 formatted and URL-safe base64 encoded string as required by the Gmail draft specification.

Here is the code from the MimeKit Conversion attempt:

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

Here is the code using AE.Net.Mail that works, but generates the body of the Gmail draft as plain text (based on this article by Jason Pettys):

 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("=", "");
 }

Is there a way to convert MimeKit's MimeMessage message into an RFC 2822 formatted and URL-safe base64 encoded string so that it can be generated as a Gmail draft? Failing that, is there a way to create an AE.Net.Mail message in HTML format prior to encoding it? All help is greatly appreciated.

joeschwa
  • 3,083
  • 1
  • 21
  • 41
  • You have several posts about Drafts API Gmail. did you tried download Draft or Message in EML format? – Kiquenet Mar 21 '17 at 09:02
  • @Kiquenet you asked a question in a previous comment below this question, I answered in a reply comment, and then both were deleted. (Perhaps by you?) Now you post the above question in a comment. My question in this post was resolved more than a year ago. I do not know why you are asking further questions about whether or not I tried the EML format. My questions have been answered. I did post three different questions. Two were answered and one remains open (which I should close and will put on my to do list). Thank you for your interest, but I no longer need help with this specific question. – joeschwa Mar 21 '17 at 16:55

3 Answers3

2

The simplest way of doing what you want is something like this:

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 ("=", "");
    }
}

but a more efficient way of doing this would require implementing our own UrlEncoderFilter to replace your ".Replace (...)" logic:

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
}

And the way you'd use this is like this:

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);
    }
}
jstedfast
  • 35,744
  • 5
  • 97
  • 110
  • Thank you! Makes perfect sense to pass the `MimeMessage` and write it to a stream. I ended up using my "replace" logic as I was not able to get the `UrlEncoderFilter` to work. All HTML I attempted to place in the email body did not encode correctly and was placed as strings of gobbledygook. If time allows I will work on this some more to see if I can figure out where I am going wrong. Very much appreciate your help and thank you for creating MimeKit! – joeschwa Feb 28 '16 at 05:37
2

Here is the C# code I used to create a gmail draft with an attachment...

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\10 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

}
Ken
  • 41
  • 3
0

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

You can try

msg.ContentType = "text/html"; 

in your AE.Net.Mail.MailMessage

Felix
  • 1
  • 1