I am using the PDF Sharp library to create a custom PDF. I want to be able to email this custom PDF as an attachment without saving a local copy first. The way I am trying to achieve this is by converting the generated PDF Sharp document to a byte array as follows:
byte[] pdfBuffer = null;
using (MemoryStream ms = new MemoryStream())
{
document.Save(ms, false);
pdfBuffer = ms.ToArray();
}
This part seems to be working, however the part I am hving problems with is converting the byte array back a PDF file. With the code below the PDF is being attached to the email but when the attachment is opened it is a blank file. This is the code I am using to do that:
//Add PDF attachment.
Stream stream = new MemoryStream(attachmentData);
mailMessage.Attachments.Add(new Attachment(stream, attachmentFilename, "application/pdf"));
//Setup up SMTP details.
smtpClient = new SmtpClient("************.com");
smtpUserInfo = new System.Net.NetworkCredential("****#****.com", "*****", "*****.com");
smtpClient.Credentials = smtpUserInfo;
smtpClient.UseDefaultCredentials = false;
smtpClient.DeliveryMethod = SmtpDeliveryMethod.Network;
//Send the email.
smtpClient.Send(mailMessage);
Can anyone please explain a correct way of converting the PDF stream back to a valid PDF and send is an email attachment?
The reason the document was appearing blank when I converted the stream back to a PDF is that when using document.Save(memoryStream, false);, it is neccessary to call document.Close(); after, i.e.:
document.Save(memoryStream, false);
document.Close();
WebClient User = new WebClient();
Byte[] FileBuffer = User.DownloadData(strPDFPath);
Stream stream = new MemoryStream(FileBuffer);
ContentType ct = new ContentType(MediaTypeNames.Application.Pdf);
pdf = new Attachment(stream, ct);
pdf.ContentType.MediaType = MediaTypeNames.Application.Pdf;
msg.Attachments.Add(pdf);
pdf.ContentType.Name = Path.GetFileName(strPDFPath);
Related
This question already has answers here:
Send inline image in email
(13 answers)
Closed 5 years ago.
I'm using the following code (C#) to embed an image into an html email:
Bitmap image = new Bitmap();
// set something to image
MemoryStream ms = new MemoryStream();
image.Save(ms, ImageFormat.Jpeg);
ContentType contentType = new ContentType(MediaTypeNames.Image.Jpeg);
ms.Seek(0, SeekOrigin.Begin);
Attachment attachment = new Attachment(ms, contentType);
attachment.ContentId = "image#email";
MailMessage mail = new MailMessage();
mail.From = new MailAddress("...");
mail.To.Add("...");
mail.Subject = "...";
mail.Body = body; // view below
mail.IsBodyHtml = true;
mail.Attachments.Add(attachment);
smtpClient.Send(mail);
ms.Close();
here the relevant part of the html body:
<img align='center' alt='' src='cid:image#email' width='100' style='padding-bottom: 0; display: inline !important; vertical-align: bottom;'>";
Viewing the email on the gmail website it looks correct: i.e. the image is embedded into the html body.
Instead, using Thunderbird the image is received as an attachment, but not recognized as jpg. The attachment is named "Part 1.2". If I double click on it the image is opened with the default image viewer installed on the computer.
What I need to add in order to correctly send embedded images in a way that is compatible with the most common email readers?
UPDATE
To name the attached image is enough to add the following:
attachment.Name = "name.jpg";
Still Thunderbird refuses to show it inside the html.
As Scarnet pointed out, this is the right way to place an embedded image into an html email:
AlternateView view = AlternateView.CreateAlternateViewFromString(body, null, MediaTypeNames.Text.Html);
LinkedResource inline = new LinkedResource(filename, MediaTypeNames.Image.Jpeg);
inline.ContentId = "image#email";
view.LinkedResources.Add(inline);
MailMessage mail = new MailMessage();
mail.From = new MailAddress(username);
mail.To.Add(address);
mail.Subject = subject;
mail.IsBodyHtml = true;
mail.AlternateViews.Add(view);
await smtpClient.SendMailAsync(mail);
I am using EWS to receive/send emails. I am sending embedded images in the email to navigate users to another page. However, when sending to other emails clients like gmail and yahoo, the images are not being set as html, but text. I created a console app below to demonstrate:
static void Main(string[] args)
{
var service = getExchangeService(NetworkCreds.Credentials);
// Create the HTML body with the content identifier of the attachment.
string html = #"<html>
<head>
</head>
<body>
<img width=100 height=100 id=""1"" src=""cid:Party.jpg"">
</body>
</html>";
// Create the email message.
EmailMessage email = new EmailMessage(service);
email.Subject = "Test Email";
email.Body = new MessageBody(BodyType.HTML, html);
email.ToRecipients.Add("test#gmail.com");
// Add the attachment to the local copy of the email message.
string file = #"C:\projects\Party.jpg";
email.Attachments.AddFileAttachment("Party.jpg", file);
email.Attachments[0].IsInline = true;
email.Attachments[0].ContentId = "Party.jpg";
// Save a copy of the email, add the attachment, and then send the email. This method results in three calls to EWS.
email.SendAndSaveCopy();
}
When I receive the message in outlook it works fine but in gmail it only has the following text in the body: [cid:Party.jpg] with the file attachment below. I know Gmail and other clients have extensive processing of images and that may the case of why it's not displaying correctly. My question is if inline images can be sent from exchange web services to other clients and be processed correctly?
This is how you fix this kind of thing
bodyText = bodyText.Replace("cid:" + fileAttachment.ContentId, fileAttachment.Name);
Be sure to attach the file as "Image 001.png" or whatever it is, then you can resend this e-mail with images in tact :)
I know that i am a bit late but maybe someone else will stuck at a similar problem and this could help solving the problem.
You could set the image as base64 encoded image.
In my case i wanted to load a preview of an existing email in a browser control.
Everything worked except showing the images. But replacing them to base64 encoded strings worked perfectly.
In this example following code should work as described:
EmailMessage email = new EmailMessage(service);
email.Subject = "Test Email";
email.Body = new MessageBody(BodyType.HTML, html);
email.ToRecipients.Add("test#gmail.com");
// Add the attachment to the local copy of the email message.
string file = #"C:\projects\Party.jpg";
MemoryStream stream = new MemoryStream();
email.Attachments.AddFileAttachment("Party.jpg", file).Load(stream);
byte[] imageBytes = stream.ToArray();
// Convert byte[] to Base64 String
string base64String = Convert.ToBase64String(imageBytes);
stream.close();
email.Attachments[0].IsInline = true;
email.Attachments[0].ContentId = "Party.jpg";
string html = #"<html>
<head>
</head>
<body>
<img width=100 height=100 id=""1"" src=""data: image/jpg;base64, " + base64String + """>
</body>
</html>";
Has anyone got any sample code to send MHT using SmtpClient and MailMessage in C#?
I have found tantalising references to using Alternate Views but I cannot figure out how to make this work with my SSRS generated MHT.
If anyone has any working code they are willing to share I would be greatly appreciative.
So it appears that .NET MailMessage does not support this out of the box. I have found a couple of approaches that can be used with varying degrees of success.
MHTML -> HTML Decoder
David Benko wrote https://github.com/DavidBenko/MHTML-to-HTML-Decoding-in-C-Sharp/blob/master/MHTMLParser.cs and while it works, I was unable to make the images appear when I viewed the resulting email. The HTML looked right, it just did not work in Outlook 2010.
Convert MimeKit MimeMessage to .NET Mail Message
As Jeffrey points out this is a start https://github.com/jstedfast/MimeKit/issues/140. I had problems again with the images not being displayed in the resulting message.
Use MimeKit & MailKit
I ended up using https://github.com/jstedfast/MimeKit to do the job. This can be achieved as follows:
MimeMessage messageMimeKit = MimeMessage.Load(Request.MapPath("~/Blah/378921.mht"));
messageMimeKit.From.Add(new MailboxAddress("Dev", "developer#gmail.com"));
messageMimeKit.To.Add(new MailboxAddress("Homer", "homer#gmail.com"));
messageMimeKit.Subject = "Another subject line";
using (var client = new MailKit.Net.Smtp.SmtpClient())
{
client.Connect("smtp.gmail.com", 465, true);
client.Authenticate("homer#gmail.com", "*****");
client.Send(messageMimeKit);
client.Disconnect(true);
}
Using System.Net.Http.Formatting
My approach leverages the ReadAsMultipartAsync extension methods on HttpContent found in the System.Net.Http.Formatting library. (part of the Microsoft.AspNet.WebApi.Client nuget package)
The only tricky bit is getting the correct content type out of the file. In order for the ReadAsMultipartAsync method to work, it needs the ContentType header on the stream content set to the correct type ("multipart/related"), including the correct boundary parameter on the content type value:
multipart/related;
boundary="----=_NextPart_01C35DB7.4B204430"
Please note, since my solution was only written for sending content from SSRS reports exported in the MTHML format, my code assumes the multipart content has only one text/html part with the remaining parts being images, but the technique is sound and could be adjusted to handle multipart content in a more generic way.
const string ContentTypePrefix = "Content-Type: ";
AlternateView htmlView = null;
var linkedResources = new List<LinkedResource>();
var content = File.ReadAllText("report.mhtml");
var startIndex = content.IndexOf(ContentTypePrefix, StringComparison.OrdinalIgnoreCase) + ContentTypePrefix.Length;
var endIndex = content.IndexOf("\"", startIndex, StringComparison.Ordinal) + 1;
var contentType = content.Substring(startIndex, endIndex);
using (var memoryStream = new MemoryStream(Encoding.UTF8.GetBytes(content)))
{
using (var stream = new StreamContent(memoryStream))
{
stream.Headers.ContentType = MediaTypeHeaderValue.Parse(contentType);
if (!stream.IsMimeMultipartContent())
{
throw new Exception("Not correct format.");
}
var parts = stream.ReadAsMultipartAsync().ConfigureAwait(false).GetAwaiter().GetResult();
foreach (var part in parts.Contents)
{
part.Headers.ContentType.CharSet = part.Headers.ContentType.CharSet?.Replace("\"", string.Empty); // Needed since the SSRS report defaults the charset to \"utf-8\", instead of utf-8 and the code fails to find the correct encoder
var compressedContent = part.ReadAsStringAsync().ConfigureAwait(false).GetAwaiter().GetResult();
if (!part.Headers.ContentType.MediaType.Contains("image"))
{
var encoding = Encoding.GetEncoding(part.Headers.ContentType.CharSet);
var partContent = encoding.GetString(Convert.FromBase64String(compressedContent));
htmlView = AlternateView.CreateAlternateViewFromString(partContent, encoding, part.Headers.ContentType.MediaType);
}
else
{
linkedResources.Add(new LinkedResource(new MemoryStream(Convert.FromBase64String(compressedContent)), part.Headers.ContentType.MediaType)
{
ContentId = part.Headers.ContentDisposition.FileName.Replace("\"", string.Empty)
});
}
}
}
}
foreach (var linkedResource in linkedResources)
{
htmlView?.LinkedResources.Add(linkedResource);
}
using (var message = new MailMessage("from#mail.com", "to#mail.com", "Subject", string.Empty))
{
message.BodyEncoding = Encoding.UTF8;
message.AlternateViews.Add(htmlView);
using (var client = new SmtpClient("smtpserver.com", 25))
{
client.Send(message);
}
}
I have an ASP.NET MVC application that has to send an email to a list of recipients with an attachment detailing a specific "Project". I get the details for this from a report by making use of SQL Server Reporting Services (SSRS).
I've never really used SSRS before, but I received some code from a colleague where he used it. What he does with the report is he downloads it in the browser to the client's machine. So, if I don't do that I sit with a byte array containing the report data.
Is there a way I can send this as an attachment without first physically writing the file to the filesystem of the server? The report will either be in excel format or a pdf.
Edit: I am using SmtpClient to send the email's.
Get the file data in a byte[]
byte[] binaryFile = // get your file data from the SSRS ...
string filename = "SSRS.pdf";
Prepare a list or array of the destination addresses:
string[] addresses = // get addresses somehow (db/hardcoded/config/...)
sending smtp message through SmtpClient:
MailMessage mailMessage= new MailMessage();
mailMessage.From = new MailAddress("sender email address goes here");
// Loop all your clients addresses
foreach (string address in addresses)
{
mailMessage.To.Add(address);
}
mailMessage.Subject = "your message subject goes here";
mailMessage.Body = "your message body goes here";
MemoryStream memoryStream = new MemoryStream(binaryFile);
mailMessage.Attachments.Add( new Attachment( memoryStream, filename , MediaTypeNames.Application.Pdf ));
SmtpClient smtpClient = new SmtpClient();
smtpClient.Send(mailMessage);
to do this you would need to leverage off the SSRS ReportManager API as follows.
First read in the report from the Web Service with SSRS
Read the file into memory rather than saving to the server or client
Send the MemoryStream object straight to the email server.
Reporting services: Get the PDF of a generated report
How to send an email with attachments using SmtpClient.SendAsync?
string strReportUser = "RSUserName";
string strReportUserPW = "MySecretPassword";
string strReportUserDomain = "DomainName";
string sTargetURL = "http://SqlServer/ReportServer?" +
"/MyReportFolder/Report1&rs:Command=Render&rs:format=PDF&ReportParam=" +
ParamValue;
HttpWebRequest req =
(HttpWebRequest)WebRequest.Create( sTargetURL );
req.PreAuthenticate = true;
req.Credentials = new System.Net.NetworkCredential(
strReportUser,
strReportUserPW,
strReportUserDomain );
HttpWebResponse HttpWResp = (HttpWebResponse)req.GetResponse();
Stream fStream = HttpWResp.GetResponseStream();
HttpWResp.Close();
//Now turn around and send this as the response..
ReadFullyAndSend( fStream );
ReadFullyAnd send method.
NB: the SendAsync call so your not waiting for the server to send the email completely before you are brining the user back out of the land of nod.
public static void ReadFullyAndSend( Stream input )
{
using ( MemoryStream ms = new MemoryStream() )
{
input.CopyTo( ms );
MailMessage message = new MailMessage("from#foo.com", "too#foo.com");
Attachment attachment = new Attachment(ms, "my attachment",, "application/vnd.ms-excel");
message.Attachments.Add(attachment);
message.Body = "This is an async test.";
SmtpClient smtp = new SmtpClient("localhost");
smtp.Credentials = new NetworkCredential("foo", "bar");
smtp.SendAsync(message, null);
}
}
I want to send an encrypted and signed mail without using any third-party API.
If I send only the alternate view with the signature, Windows Mail can validate it. If I send only with alternate view with encrypted data, Windows Mail can decipher it. But if I send both, Windows Mail gets 2 attachements. If I sign the encryptedBytes and add those signed bytes to the alternative view it only validates the signature and the message is empty.
Any idea?
MailMessage message = new MailMessage();
message.From = new MailAddress(lblMail.Text);
message.Subject = txtSubject.Text;
string body = "Content-Type: text/plain\r\nContent-Transfer-Encoding: 7Bit\r\n\r\n" + structForm();
byte[] messageData = Encoding.ASCII.GetBytes(body);
ContentInfo content = new ContentInfo(messageData);
EnvelopedCms envelopedCms = new EnvelopedCms(content);
message.To.Add(new MailAddress(provMail));
CmsRecipient recipient = new CmsRecipient(SubjectIdentifierType.SubjectKeyIdentifier, this.certificate);
envelopedCms.Encrypt(recipient);
byte[] encryptedBytes = envelopedCms.Encode();
SignedCms Cms = new SignedCms(new ContentInfo(encryptedBytes));
CmsSigner Signer = new CmsSigner(SubjectIdentifierType.IssuerAndSerialNumber, new X509Certificate2(#"c:\serv.pfx","123"));
Cms.ComputeSignature(Signer);
byte[] SignedBytes = Cms.Encode();
MemoryStream encryptedStream = new MemoryStream(encryptedBytes);
AlternateView encryptedView = new AlternateView(encryptedStream, "application/pkcs7-mime; smime-type=signed--data;name=smime.p7m");
message.AlternateViews.Add(encryptedView);
MemoryStream signedStream = new MemoryStream(SignedBytes);
AlternateView signedView = new AlternateView(signedStream, "application/pkcs7-mime; smime-type=signed-data;name=sig.p7m");
message.AlternateViews.Add(signedView);
System.Net.NetworkCredential SMTPUserInfo = new System.Net.NetworkCredential("emailaddress#xpto.com", "XXXXXX");
SmtpClient client = new SmtpClient("smtp.xpto.com");
client.UseDefaultCredentials = false;
client.Credentials = SMTPUserInfo;
client.Send(message);
Label2.Text = "Assinado e cifrado!";
You should sign first, then encrypt.
While the original CMS and S/MIME specifications allow you to do the operations in either order, later work pointed out that signing a document that you can't read is a really bad idea. The signature should be over the plain-text.
The resulting MIME message should only have a single part, which should be S/MIME enveloped-data. Your message has two parts, and the encrypted part is mis-labeled with a signed-data content-type. Create and sign the SignedCms object. Encode it, and use the encoded value as the content of an EnvelopedCms object. Encrypt that, and use its encoded value as the content of your MailMessage, with a content type of "application/pkcs7-mime; smime-type=enveloped-data".