HTML looks okay in browser but not in email - c#

I'm having some trouble with email encoding. I am reading an HTML file from disk and sending in through Gmail. When I open the HTML in the browser it looks great. When I copy the HTML string from Visual Studio and save it as an HTML file, it looks great. When I receive the email it contains a bunch of invalid characters. Even the list bullets are messed up! I'm sure this is an issue with encoding, but the file is encoded as UTF-8 and looks good until it's converted to RAW and sent through Gmail.
Here is the process. We read from a docx using the OpenXML SDK then we use the HtmlConverter to save the document as HTML. Later the HTML is read in from the file, converted to RAW formatting and sent through the GMail API.
Here are some relevant code snips:
This is where we save our HTML file using HtmlConverter.
HtmlConverterSettings settings = new HtmlConverterSettings()
{
AdditionalCss = "body { margin: 1cm auto; max-width: 20cm; padding: 0; }",
FabricateCssClasses = true,
RestrictToSupportedLanguages = false,
RestrictToSupportedNumberingFormats = false,
};
XElement htmlElement = HtmlConverter.ConvertToHtml( wdWordDocument, settings );
var html = new XDocument(
new XDocumentType( "html", null, null, null ),
htmlElement );
var htmlString = html.ToString( SaveOptions.DisableFormatting );
File.WriteAllText( destFileName.FullName, htmlString, Encoding.UTF8 );
This is where we read the stored HTMl and convert it for sending via Gmail. (We use Mimekit for the conversion.)
// Create the message using MimeKit/System.Net.Mail.MailMessage
MailMessage msg = new MailMessage();
msg.Subject = strEmailSubject; // Subject
msg.From = new MailAddress( strUserEmail ); // Sender
msg.To.Add( new MailAddress( row.email ) ); // Recipient
msg.BodyEncoding = Encoding.UTF8;
msg.IsBodyHtml = true;
// We need to loop through our HTML Document and replace the images with a CID so that they will display inline
var vHtmlDoc = new HtmlAgilityPack.HtmlDocument();
vHtmlDoc.Load( row.file ); // Read the body, from HTML file
...
msg.Body = vHtmlDoc.DocumentNode.OuterHtml;
// Convert our System.Net.Mail.MailMessage to RAW with Base64 encoding for Gmail
MimeMessage mimeMessage = MimeMessage.CreateFromMailMessage( msg );
Google.Apis.Gmail.v1.Data.Message message = new Google.Apis.Gmail.v1.Data.Message();
message.Raw = Base64UrlEncode( mimeMessage.ToString() );
var result = vGMailService.Users.Messages.Send( message, "me" ).Execute();
And this is how we are base64 encoding:
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( "=", "" );
}
The email ends up as "Content-Type: multipart/mixed" with two alternatives. One is
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: quoted-printable
and the other is
Content-Type: text/html; charset=UTF-8
Content-Transfer-Encoding: quoted-printable
The both the plain text and the HTML contain strings like =C3=A2=E2=82=AC=E2=84=A2 for an apostrophe and the HTML portion contains an HTML header that contains weird "3D" characters in it.
<meta charset=3D"UTF-8"><title></title><meta name=3D"Generator"=
content=3D"PowerTools for Open XML">
None of this weirdness was in the HTML prior to converting to Base64 and sending.
Any ideas what the problem could be? Does this have anything to do with UTF8 and Mimekit?

This is what your code should look like to get the "raw" message data for use with Google's API's:
using (var stream = new MemoryStream ()) {
message.WriteTo (stream);
var buffer = stream.ToArray ();
var base64 = Convert.ToBase64String (buffer)
.Replace( '+', '-' )
.Replace( '/', '_' )
.Replace( "=", "" );
message.Raw = base64;
}
As brandon927 pointed out, the content of the text/html mime part has been quoted-printable encoded. This is a MIME encoding used for transport to make sure that it fits within the 7bit ascii range.
You will need to decode this in order to get the original HTML.
With MimeKit, this is done for you if you either use mimeMessage.HtmlBody or if you cast the MimeEntity representing the text/html part into a TextPart and access the Text property.

The answer to your question is: there is no problem. This is simply how Raw is presented, with quoted-printable encoding. This is how Gmail also presented it if you send and email and look at the source of it.

Related

Adding Content-Description to email in .NET

I'm trying to set a MIME Content-Description field in an email. I set the Content-Type and the Content-Tranfer-Encoding using an AlternateView, but I can find no way to add Content-Description.
What I can do is set Content-Description using a custom header, but this appears to insert a single Content-Description for the entire email, rather than for the MIME object.
using( MailMessage mailMessage = new MailMessage() )
{
mailMessage.From = new MailAddress( _configuration.FromAddress );
mailMessage.BodyEncoding = Encoding.UTF8;
mailMessage.To.Add( to );
mailMessage.Subject = subject;
// add the MIME data
var irisB2View = AlternateView.CreateAlternateViewFromString( body );
irisB2View.ContentType = new ContentType( "Application/EDI-consent" ); // Content-Type
irisB2View.TransferEncoding = TransferEncoding.Base64; // Content-Tranfer-Encoding
mailMessage.AlternateViews.Add( irisB2View );
// this adds Content-Description, but it appears before the MIME data
mailMessage.Headers.Add( "Content-Description", "IRIS/B2/Z" );
client.Send( mailMessage );
}
This results in an email of the form below, with Content-Description before the MIME object:
Content-Description: IRIS/B2/Z
MIME-Version: 1.0
From: xxxxxx#xxxxxx.xx
To: xxxxxx#xxxxxx.xx
Date: 28 Sep 2018 13:49:51 +0200
Subject: subject
Content-Type: Application/EDI-consent
Content-Transfer-Encoding: base64
Does anybody know how I can manage to get the Content-Description into the MIME content?
Equally, the mail has no boundaries (I only have one MIME object), so does it actually matter that the Content-Description is before the MIME object?
Thanks in advance.
RFC 2045 specifies that in a single part message the MIME header fields are used in the context of a regular RFC 822 header. RFC 822 does not impose a sequence on header fields. Therefore the placement of the Content-Description field does not matter.

Why won't the Gmail API send email messages as HTML from C#?

I am trying to use the Gmail API to send a HTML email from C#. The email gets sent but Gmail refuses to acknowledge that it should be an HTML email.
This is the code I am using:
var template = #"from: {1}{4}to: {0}{4}subject: {2}{4}MIME-Version: 1.0{4}Content-Type: text/html; charset=UTF-8{4}Content-Transfer-Encoding: base64{4}{4}{3}";
body = HttpUtility.HtmlEncode(body);
var result = string.Format(template, to, from, subject, body, "\r\n");
result = Convert.ToBase64String(Encoding.UTF8.GetBytes(result));
var gMessage = new Message()
{
Raw = result
};
service.Users.Messages.Send(gMessage, "me").Execute();
This is what the result string looks like before encoding to base64:
from: test#test.com
to: test#test2.com
subject: testSubject
MIME-Version: 1.0
Content-Type: text/html; charset=UTF-8
Content-Transfer-Encoding: base64
&lt;html&gt;&lt;head&gt;
&lt;title&gt;Push Email&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt; blah
&lt;/body&gt;&lt;/html&gt;
(The app actually uses real email addresses which I have replaced with "test#..." in the above example for privacy.)
I tried every possible combination of header arrangement, content-transfer-encoding (base64, 7bit, 8bit, etc), content-type charset (ascii, utf8, etc), I tried using UrlEncode instead of HtmlEncode but the email body is either just displayed as unrendered HTML or it is displayed as an encoded url string (depending on whether I use html encode or url encode and what contet transfer encoding I specify).
The point is, the mail is working, the body is being sent but it just stubbornly refuses to render the HTML. I either get this:
<html><head> <title>Push Email</title> </head> <body> blah </body></html>
Or this:
%3chtml%3e%3chead%3e%0d%0a%0d%0a%3ctitle%3ePush+Email%3c%2ftitle%3e+++%0d%0a+%0d%0a%3c%2fhead%3e++++%0d%0a%3cbody%3e+blah++%0d%0a++++%0d%0a%3c%2fbody%3e%3c%2fhtml%3e
Or this:
<html><head> <title>Push Email</title> </head> <body> blah </body></html>
I would just send an SMTP email but, probably for security, Google won't allow it if you have 2 factor auth for the account (which I have and don't plan on disabling).
Also, I am just building my MIME message as a regular string. This may have something to do with it but I don't know. I don't plan on using any third party nuget packages / libraries such as MimeKit. I just want a C# solution only.
Finally, I need to be able to send HTML emails so that I may send links as per my app business logic.
Any advice?
I finally got it. First of all, the body of the mail must not be escaped but the whole MIME string should. But, as mentioned previously, if I leave the body unencoded the API complains about an invalid byte string.
The problem is that the resulting base64 string should be encoded in a URL safe manner. The python code on the Gmail API guide uses a method called urlsafe_b64encode which is different from the normal base 64 method in that the resulting string is URL safe.
I thought I could replicate this in C# using HTML or URL encoding and then using the standard Convert.ToBase64String method to convert the MIME string to base64 but I was wrong. After searching the MSDN website I finally found the HttpServerUtility.UrlTokenEncode method which does just what the urlsafe_b64encode python method does which is to encode the string in a URL safe variant and also convert it to base64. The final code thus becomes:
// Template for the MIME message string (with text/html content type)
var template = #"from: {1}{4}to: {0}{4}subject: {2}{4}MIME-Version: 1.0{4}Content-Type: text/html; charset=UTF-8{4}Content-Transfer-Encoding: base64{4}{4}{3}";
// Fill in MIME message fields
var result = string.Format(template, to, from, subject, body, "\r\n");
// Get the bytes from the string and convert it to a URL safe base64 string
result = HttpServerUtility.UrlTokenEncode(Encoding.UTF8.GetBytes(result));
// Instantiate a Gmail API message and assign it the encoded MIME message
var gMessage = new Message()
{
Raw = result
};
// Use the Gmail API Service to send the email
service.Users.Messages.Send(gMessage, "me").Execute();
Your body looks like passed by HtmlEncode twice, i.e. < once passed become < twice passed become &lt; which is what you have. As you didn't posted the original code from where body is being setted before the enconding, all I can say is you have to have this as your result string before enconding to base64:
from: test#test.com
to: test#test2.com
subject: testSubject
MIME-Version: 1.0
Content-Type: text/html; charset=UTF-8
Content-Transfer-Encoding: base64
<html><head>
<title>Push Email</title>
</head>
<body> blah
</body></html>
To get this, you can replace this line
body = HttpUtility.HtmlEncode(body);
by this
body = HttpUtility.HtmlDecode(body);
Really just need the option "IsBodyHtml=true":
using System.Net;
using System.Net.Mail;
namespace ConsoleApp2
{
class Program
{
static void Main(string[] args)
{
var html = "<html><head><body><h1>hello world!</h1></body><head></html>";
var from = "me#here.com";
var to = "them#there.com";
var msg = new MailMessage(from, to)
{
Subject = "My Subject",
Body = html,
IsBodyHtml = true
};
SendGmail("myUserName", "myPassword", msg);
}
public static void SendGmail(string username, string password, MailMessage msg)
{
var client = new SmtpClient("smtp.gmail.com", 587);
client.EnableSsl = true;
client.DeliveryMethod = SmtpDeliveryMethod.Network;
client.Credentials = new NetworkCredential(username, password);
client.Send(msg);
}
}
}
thanks to #user1969903 give the direction, but i'm using .Net6, this work for me
// Template for the MIME message string (with text/html content type)
var mimeString = $"from: {from#gmail.com}\r\n" +
$"to: {to#gmail.com}\r\n" +
$"subject: Test Edm\r\n" +
$"MIME-Version: 1.0\r\n" +
$"Content-Type: text/html; charset=UTF-8\r\n" +
$"Content-Transfer-Encoding: base64\r\n\r\n" +
$"{html_here}";
var inputBytes = Encoding.UTF8.GetBytes(mimeString);
var gMessage = new Google.Apis.Gmail.v1.Data.Message()
{
Raw = Convert.ToBase64String(inputBytes).Replace("+", "-").Replace("/", "_").Replace("=", "")
};
service.Users.Messages.Send(gMessage, "me").Execute();

mimekit outlook show text as attachment

I have a word document and using Aspose.Word to perform a mail merge and save the result to a memory stream as mhtml (part of my code):
Aspose.Words.Document doc = new Aspose.Words.Document(documentDirectory + countryLetterName);
doc.MailMerge.Execute(tempTable2);
MemoryStream outStream = new MemoryStream();
doc.Save(outStream, Aspose.Words.SaveFormat.Mhtml);
Then I use MimeKit (latest version from NuGet) to send my message:
outStream.Position = 0;
MimeMessage messageMimeKit = MimeMessage.Load(outStream);
messageMimeKit.From.Add(new MailboxAddress("<sender name>", "<sender email"));
messageMimeKit.To.Add(new MailboxAddress("<recipient name>", "<recipient email>"));
messageMimeKit.Subject = "my subject";
using (var client = new MailKit.Net.Smtp.SmtpClient())
{
client.Connect(<smtp server>, <smtp port>, true);
client.Authenticate("xxxx", "pwd");
client.Send(messageMimeKit);
client.Disconnect(true);
}
When opening the received email in my mail web client, I see the text (with image) and the image as attachment.
When opening the received email in Outlook (2016), the mail body is empty and I have two attachments, 1 with the text and 1 with the image.
Looking at the mht contents itself, it looks like:
MIME-Version: 1.0
Content-Type: multipart/related;
type="text/html";
boundary="=boundary.Aspose.Words=--"
This is a multi-part message in MIME format.
--=boundary.Aspose.Words=--
Content-Disposition: inline;
filename="document.html"
Content-Type: text/html;
charset="utf-8"
Content-Transfer-Encoding: quoted-printable
Content-Location: document.html
<html><head><meta http-equiv=3D"Content-Type" content=3D"text/html; charset=
=3Dutf-8" /><meta http-equiv=3D"Content-Style-Type" content=3D"text/css" />=
<meta name=3D"generator" content=3D"Aspose.Words for .NET 14.1.0.0" /><titl=
e></title></head><body>
*****body removed *****
</body></html>
--=boundary.Aspose.Words=--
Content-Disposition: inline;
filename="image.001.jpeg"
Content-Type: image/jpeg
Content-Transfer-Encoding: base64
Content-Location: image.001.jpeg
****image content remove****
--=boundary.Aspose.Words=----
Is there some formatting or so I have to do to get this correctly shown in Outlook? Or is it caused by the "3D"-keywords found, like content=3D"xxxx", style=3D"xxxx"?
Thanks in advance.
Edward
The =3D bits are the quoted-printable encoding of the = character. Since the headers properly declare the Content-Transfer-Encoding to be quoted-printable, that's not the problem.
Here are some suggestions on trying to massage the content into something that will work in Outlook (Outlook is very finicky):
MimeMessage messageMimeKit = MimeMessage.Load(outStream);
messageMimeKit.From.Add(new MailboxAddress("<sender name>", "<sender email"));
messageMimeKit.To.Add(new MailboxAddress("<recipient name>", "<recipient email>"));
messageMimeKit.Subject = "my subject";
var related = (MultipartRelated) messageMimeKit.Body;
var body = (MimePart) related[0];
// It's possible that the filename on the HTML body is confusing Outlook.
body.FileName = null;
// It's also possible that the Content-Location is confusing Outlook
body.ContentLocation = null;

SendGrid - Image not showing up in HTML email

I am using the SendGrid v3 API and C# library (v7) to send an email.
In my email I have a header which is a png. The header is embedded like this:
<img src="cid:emailheader"/>
In the C# code I send the image as an attachment with with the same ContentId
var mail = new Mail(from, subject, to, content);
var headerPath = HttpContext.Current.Server.MapPath("~/Resources/email-header.png");
var attachment = new SendGrid.Helpers.Mail.Attachment();
attachment.ContentId = "emailheader";
attachment.Content = Convert.ToBase64String(File.ReadAllBytes(headerPath));
attachment.Type = "image/png";
attachment.Filename = "email-header.png";
mail.AddAttachment(attachment);
var send = sg.client.mail.send.post(requestBody: mail.Get());
Yet when I open the email it says the source is not found, even though the image is correctly displayed in the attachment
I'm not the expert for Sendgrid, but I found on there blog post
that this suggest to do inline encoding in your html directly. this way you don't need to add an attachment. (I'm use this quite a lot)
<img alt="My Image" src="data:image/jpeg;base64,/9j/4S/+RXhpZgAATU0AKgA...more encoding" />
Maybe this is a work around for you.
As an second alternative:
for sending out emails with pictures I'm using
System.Net.Mail
here I do add an AlternateView with a linked resource.
AlternateView htmlView = AlternateView.CreateAlternateViewFromString(html, null, "text/html");
LinkedResource imageResource = new LinkedResource(Imagepath + "Monitoring.png", "image/png")
{
ContentId = "1",
TransferEncoding = System.Net.Mime.TransferEncoding.Base64
};
htmlView.LinkedResources.Add(imageResource);
message.AlternateViews.Add(htmlView);
the syntax in html is the same as you use
<img src="cid:1">
I hope this help.
Butti
node
//imageData= "data:image/png;base64,ine793nfdsf......."
imageb64 = imageData.replace('data:image/png;base64,' , '');
//remove data:image/png;base64,
const msg = {
to: 'example#gmail.com',
from: 'test#gmail.com',
subject: "image attached",
html :'<img src="cid:myimagecid"/>',
attachments: [
{
filename: "imageattachment.png",
content: imageb64,
content_id: "myimagecid",
}
]
};
sgMail.send(msg);

Sign/Encrypt EDIFACT document - malformed - C#

I have to send an EDI message to a government body, which is signed/encrypted in a particular way.
According to https://docs.google.com/document/d/1xOxsZG7nCXdd3ucFKJObheW4G6kFwflGFkzURS_haTY/edit?usp=sharing
I am trying this code, but the encrypted S/MIME isn’t correctly formatted according to the government gateway.
Email reply from them:
The Error Code I am getting is a decryption failure.
You should have signed your EDI message using your Gatekeeper Certificate first.
This produces an S/MIME blob. We call this the “signed” S/MIME .
Then, you take your signed blob and encrypt it using the Customs Gateway Certificate downloaded from our cargo web site.
This produces another S/MIME, we call the “encrypted” S/MIME.
I am signing and encrypting using the correct encryption certificates.
Have also tried 3rd party libraries ActiveUp and Chilkat to no avail so far.
Any help in interpreting the Customs Spec and adjusting where I might have gone wrong much appreciated. I have been working on this issue for more than a week.
public static void SendEmail(string ediMsg, string clientCertificatePath,
string clientCertificatePassword, string sender, string receiver, string subject, SmtpClient smtp,
string customsCertificatePath)
{
//Load the certificate
X509Certificate2 EncryptCert = new X509Certificate2(customsCertificatePath);
X509Certificate2 SignCert =
new X509Certificate2(clientCertificatePath, clientCertificatePassword);
//Build the body into a string
StringBuilder Message = new StringBuilder();
ediMsg = "UNB+IATB:1+6XPPC+LHPPC+940101:0950+1' ...";
/*The EDI document is first formatted as a MIME message [MIME],
* as the EDI Document may contain special characters, non-printable ASCII and binary data. */
byte[] arrayToEncode = System.Text.Encoding.UTF8.GetBytes(ediMsg);
ediMsg = Convert.ToBase64String(arrayToEncode);
/*Within the MIME message, the Content-Transfer-Encoding header must be either “quoted-printable”
* or “base64”, and the Content-Type header should be set to “Application/EDIFACT”. */
Message.AppendLine("Content-Type: Application/EDIFACT");
Message.AppendLine("Content-Transfer-Encoding: base64");
//The file name of the attachment for inbound e-mails (to Customs) must be the same as the Subject Line
//(section 3.3) with the .edi suffix.
Message.AppendLine("Content-Disposition: attachment; filename=\"" + subject + ".edi\"");
/*I have tried this with
* (a) the raw ediMsg,
* (b) the base64 version of (a)
* (c) quoted-printable version of (a)
* (d) base64 version of (c)
*/
Message.AppendLine(ediMsg);
//Text must not be included in the body of the e-mail. EDI documents must be sent as an attachment.
//Convert the body to bytes
byte[] BodyBytes = Encoding.UTF8.GetBytes(Message.ToString());
//sign
var signedBytes = SignMsg(BodyBytes, SignCert);
//Build the e-mail body bytes into a secure envelope
EnvelopedCms Envelope = new EnvelopedCms(new ContentInfo(signedBytes));
CmsRecipient Recipient = new CmsRecipient(
SubjectIdentifierType.IssuerAndSerialNumber, EncryptCert);
Envelope.Encrypt(Recipient);
byte[] EncryptedBytes = Envelope.Encode();
//Create the mail message
MailMessage Msg = new MailMessage();
Msg.To.Add(new MailAddress(receiver));
Msg.From = new MailAddress(sender);
Msg.Subject = subject;
//Attach the encrypted body to the email as and ALTERNATE VIEW
MemoryStream ms = new MemoryStream(EncryptedBytes);
AlternateView av =
new AlternateView(ms,
"application/pkcs7-mime; smime-type=signed-data;name=smime.p7m");
Msg.AlternateViews.Add(av);
//SmtpClient smtp = new SmtpClient(MailServer, 25);
//send the email
smtp.Send(Msg);
}
I'm not sure the issues I'm about to point out are the problem, but they might be worth looking into...
First, Convert.ToBase64String(arrayToEncode); does not wrap lines as needed in MIME. What you'll need to use is this variant with Base64FormattingOptions.InsertLineBreaks.
Secondly, I don't know what SignMsg() does, but make sure that you prepend the proper Content-Type, Content-Transfer-Encoding, and (possibly) Content-Disposition headers as well. The Content-Type should be application/pkcs7-mime; smime-type=signed-data; name=smime.p7s and Content-Transfer-Encoding should be base64 once you've base64 encoded the data.
Thirdly, the Content-Type header you gave to the encrypted outer part is wrong. It should be application/pkcs7-mime; smime-type=enveloped-data; name=smime.p7m.
Fourthly, Make sure that the encrypted data gets base64 encoded and that the AlternativeView gets a Content-Transfer-Encoding of base64.
I'm not sure if adding it as an alternative view will necessarily work or not, I'd have to see the generated MIME to be sure.
Something you might consider using instead of IP*Works which is payware is my Open Source library called MimeKit which already supports generating S/MIME messages. I also have a library called MailKit which supports SMTP which you seem to be using.
Both libraries are easily available via NuGet: MimeKit and MailKit
What you would do is something like this:
// Note: if the email addresses do not match the certificates, you can
// use a SecureMailboxAddress instead, which allows you to specify the
// Fingerprint (aka Thumbprint) of the certificate to use for signing
// or encrypting.
var recipient = new MailboxAddress ("Receiver Name", "receiver#example.com");
var sender = new MailboxAddress ("Sender Name", "sender#example.com");
var message = new MimeMessage ();
message.To.Add (recipient);
message.From.Add (sender);
message.Subject = subject;
// create the application/edifact MIME part
var edifact = new MimePart ("application", "edifact");
// set the filename of the MIME part (adds a Content-Disposition header
// if not already present)
edifact.FileName = subject + ".edi";
// create the content stream of the MIME part
var content = new MemoryStream (Encoding.UTF8.GetBytes (ediMsg), false);
// set the content of the MIME part (we use ContentEncoding.Default because
// it is not encoded... yet)
edifact.ContentObject = new ContentObject (content, ContentEncoding.Default);
// encode the content using base64 *and* set the Content-Transfer-Encoding header
edifact.ContentTransferEncoding = ContentEncoding.Base64;
using (var ctx = new TemporarySecureMimeContext ()) {
ctx.Import (clientCertificatePath, clientCertificatePassword);
ctx.Import (customsCertificatePath);
// sign and then encrypt the edifact part and then set the result as the
// message body.
message.Body = ApplicationPkcs7Mime.SignAndEncrypt (ctx, sender,
DigestAlgorithm.Sha1, new [] { recipient }, edifact);
}
// MailKit's SMTP API is very similar to System.Net.Mail's SmtpClient API,
// so that shouldn't pose a problem.

Categories