Search code examples
c#.netgmail-apigoogle-workspace

Signing an email yields dkim=neutral (body hash did not verify) when sending via Google's Gmail Api


We are trying to sign an email message with DKIM. The email is successfully sent to the recipient via Gmail's Api; however, it fails DKIM validation when we sign it with a domain that has a valid DKIM setup. The domain has a valid DKIM setup (https://mxtoolbox.com/SuperTool.aspx?action=dkim%3aunsub.eeunsub.com%3as1&run=toolpage) and the DKIM passes when we send emails using other email providers such as AWS SES (screenshot added, see below).

private static void SendEmail()
{
  ClientSecrets clientSecrets = new ClientSecrets
  {
      ClientId = ClientId,
      ClientSecret = ClientSecret
  };
  
  TokenResponse token = new TokenResponse
  {
      AccessToken = "",
      RefreshToken = _espApiEndpoint.RefreshToken
  };
  
  IAuthorizationCodeFlow flow =
  new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
  {
      ClientSecrets = clientSecrets,
      Scopes = new string[] { GmailService.Scope.GmailSend }
  });
  
  UserCredential credential = new UserCredential(flow, "me", token);
  
  BaseClientService.Initializer intializer = new BaseClientService.Initializer
  {
      ApplicationName = "GmailEspApiClient",
      HttpClientInitializer = credential
  };
  
  var gmail = new GmailService(intializer);
  
  string message = @"From: Xxx <[email protected]>
  Date: Thu, 14 Mar 2024 09:59:20 -0700
  Subject: Gmail EE est
  Message-Id: <[email protected]>
  Reply-To: xxx <[email protected]>
  To: Dan <[email protected]>
  X-EE-RunId: xxx
  List-Unsubscribe: <xxx.com/>, <mailto:>
  List-Unsubscribe-Post: List-Unsubscribe=One-Click
  X-Test-Header: TestHeader
  X-Testing: Testee
  MIME-Version: 1.0
  Content-Type: text/html; charset=utf-8
  
  Hello from <b>Gmail</b>
  ";
  
  using MemoryStream memoryStream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(message));
  MimeMessage mimeMessage = await MimeMessage.LoadAsync(memoryStream);
  
  string privateKey = ""; // Your private key
  byte[] byteArray = Encoding.UTF8.GetBytes(privateKey);
  using MemoryStream memory = new MemoryStream(byteArray);
  
  string domain = "xxx"; // Your Domain
  string selector = "xxx"; // Selector
  DkimSigner signer = new DkimSigner(memory, domain, selector)
  {
      HeaderCanonicalizationAlgorithm = DkimCanonicalizationAlgorithm.Simple,
      BodyCanonicalizationAlgorithm = DkimCanonicalizationAlgorithm.Simple,
      QueryMethod = "dns/txt",
  };
  
  mimeMessage.Prepare(EncodingConstraint.SevenBit);
  
  signer.Sign(mimeMessage, new HeaderId[] { HeaderId.From });
  
  Message finalmessage = new Message();
  finalmessage.Raw = Base64UrlEncode(mimeMessage.ToString());
  var result = await gmail.Users.Messages.Send(finalmessage, "me").ExecuteAsync();
}

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

Screenshots are posted for both Google and Aws SES. Notice how google returns dkim=neutral (body hash did not verify).

Please let us know your thoughts.

Issue previously opened with google https://github.com/googleapis/google-api-dotnet-client/issues/2701


Solution

  • This is my Gmail SMTP Sample with Google Xoauth2 using the Google .net client library

    // Copyright 2023 DAIMTO ([Linda Lawton](https://twitter.com/LindaLawtonDK)) :  [www.daimto.com](http://www.daimto.com/)
    //
    // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
    // the License. You may obtain a copy of the License at
    //
    // http://www.apache.org/licenses/LICENSE-2.0
    //
    // Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
    // an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
    // specific language governing permissions and limitations under the License.
    using Google.Apis.Auth.OAuth2;
    using Google.Apis.Util.Store;
    using MailKit.Net.Smtp;
    using MailKit.Security;
    using MimeKit;
    
    var to = "[email protected]";
    
    // TODO: figure out how to get the users email back from the smtp server without having to request the profile scope. 
    var from = "[email protected]";
    
    var path = @"C:\Development\FreeLance\GoogleSamples\Credentials\Credentials.json";
    var scopes = new[] { "https://mail.google.com/" };   // You can only use this scope to connect to the smtp server.
    
    
    
    var credential = GoogleWebAuthorizationBroker.AuthorizeAsync(GoogleClientSecrets.FromFile(path).Secrets,
        scopes,
        "GmailSmtpUser",
        CancellationToken.None,
        new FileDataStore(Directory.GetCurrentDirectory(), true)).Result;
    
    var message = new EmailMessage()
    {
        From = from,
        To = to,
        MessageText = "This is a test message using https://developers.google.com/gmail/imap/xoauth2-protocol",
        Subject = "Testing GmailSMTP with XOauth2"
    };
    
    try
    {
        using (var client = new SmtpClient())
        {
            client.Connect("smtp.gmail.com", 465, true);
            
            var oauth2 = new SaslMechanismOAuth2 (message.From, credential.Token.AccessToken);
            await client.AuthenticateAsync (oauth2, CancellationToken.None);
            
            client.Send(message.GetMessage());
            client.Disconnect(true);
        }
    
       
    }
    catch (Exception ex)
    {
       Console.WriteLine(ex.Message);
    }
    
    
    public class EmailMessage
    {
        public string To { get; set; }
        public string From { get; set; }
        public string Subject { get; set; }
        public string MessageText { get; set; }
    
        public MimeMessage GetMessage()
        {
            var body = MessageText;
            var message = new MimeMessage();
            message.From.Add(new MailboxAddress("From a user", From));
            message.To.Add(new MailboxAddress("To a user", To));
            message.Subject = Subject;
            message.Body = new TextPart("plain") { Text = body };
            return message;
        }
    }
    

    You need to figure out DKIM the part on your own I have not configured that on my workspace account there are a few questions on stack that might help C# DKIM gmail site:stackoverflow.com