I'm using MailKit library to read emails from MS Exchange mailbox. Here is my code
var client = new ImapClient();
///...
client.Authenticate(saslMechanism);
client.Inbox.Open(FolderAccess.ReadWrite);
var uids = client.Inbox.Search(SearchQuery.NotSeen);
var items = client.Inbox.Fetch(uids, MessageSummaryItems.All | MessageSummaryItems.BodyStructure);
foreach(var item in items) {
// Here I had wron decoded characters in some emails
Console.WriteLine(item.Envelope.Subject);
}
/// ...
The encoding is wrong in some messages.
Is there any way to force the mailkit to re-encode messages in a certain encoding when received from the server
Unfortunately there is not.
The Envelope data structure does not keep the raw byte data received from the server, so it cannot try deciding it again using a different charset encoding.
What you can do is modify your Fetch request to get the Subject header (in addition to or instead of the Envelope) and then use:
int index = item.Headers.IndexOf(HeaderId.Subject);
Header header = item.Headers[index];
string subject = header.GetValue(“charset name goes here”);
Related
I'm working on an issue in our code base where we want to extract all recipients of an email message received by Outlook. I understand that if recipients are on the BCC line, the receivers of that email don't know about those recipients, but if you're on the BCC line, you do know (it's in the email headers). So I have a message that looks like this:
From: FirstName LastName<name1#host1.tld>
Date: Thu, 16 Jul 2020 09:48:10 +0100
Message-ID: <CAE+EE4GS7RtDKgPGOUbTFQ3=7i9+QiKB++1cx7qqLd_09PRZFg#mail.gmail.com>
Subject: Testing
To: undisclosed-recipients:;
Bcc: name2#host2.tld
Content-Type: multipart/alternative;
Using the Redemption RDOMail.Recipients, I would expect that it would have one item and that it would be name2#host2.tld (since that's all the information I have about recipients of this email). When I actually loop through the recipients, I get a count of 0. Shouldn't it return any BCC addresses it does know about?
Example Code (might be some syntax errors as I'm simplyfing live code):
public IEnumerable<string> GetRecipients(RDOMail mailItem)
{
var recipients = mailItem.Recipients;
for (var i = 0; i < recipients.Count; i++) // never enters as Count == 0
{
var recipient = recipients[i];
var smtpAddress = GetSmtpAddress(recipient);
yield return smtpAddress;
}
}
There is a BCC property of RDOMail, but it is also blank for received mail. The documentation does note: This property contains the display names only. The Recipients collection should be used to modify the BCC recipients. Which leads me to believe that I can use the Recipients collection for Read operations as well.
Yes, I realize I might be able to assume that if the current address isn't on the TO or CC lines, it's on the BCC line. But this doesn't work for shared mailboxes... or rather, I'd have to do a lot of digging to understand where the received mail item is coming from in order to make the correct assumption.
Updated with Solution
I ended up working with Eugene's answer and combined it with looking at the SMTP BCC header for received email as well. There are still some cases (internal email where you're on the BCC header might not contain SMTP headers at all in an On-Prem Exchange Environment), but at this point, I think it's pretty complete. So I still use the above code for GetRecipients(RDOMail mailItem), but after I do that, I also call:
Some Constants
public const string PR_TRANSPORT_MESSAGE_HEADERS = "http://schemas.microsoft.com/mapi/proptag/0x007D001F"
public const string PID_TAG_RECEIVED_REPRESENTING_SMTP_ADDRESS = "http://schemas.microsoft.com/mapi/proptag/0x5D08001F"
Test if the receiver is in the BCC
private bool IsReceiverInBcc(RDOMail mailItem)
{
if (mailItem == null) return false;
var receiver = ConvertReceiverToContact(mailItem);
var messageHeaders = mailItem.Fields(Consts(PR_TRANSPORT_MESSAGE_HEADERS)?.ToString();
if (!string.IsNullOrWhiteSpace(messageHeaders)
{
var bccHeaderRegex = new Regex($"{Environment.NewLine}Bcc: .*{Environment.NewLine}", RegexOptions.IgnoreCase);
foreach (Match headerMatch in bccHeaderRegex.Matches(messageHeaders))
{
if (headerMatch?.Value?.IndexOf(receiver.address, CompareOptions.IgnoreCase) >= 0
{
return true;
}
}
}
return false;
}
Extract the current user from the BCC header
private EmailContact ConvertReceiverToContact(RDOMail mailItem)
{
var contact = new EmailContact
{
Address = mailItem.Fields(Consts.PID_TAG_RECEIVED_REPRESENTING_SMTP_ADDRESS),
Name = mailItem.ReceivedByName
};
return contact;
}
The Outlook object model (as well as Extended MAPI) doesn't provide any information in the Recipients collection. The Count property returns 0 in this scenario. In the case of Extended MAPI the IMessage::GetRecipientTable method returns an empty table. Here is what MSDN states:
The IMessage::GetRecipientTable method returns a pointer to the message's recipient table, which includes information about all of the recipients for the message. There is one row for every recipient.
So, there are no issues in the Redemption library.
Based on my research performed with a sample email message recieved with the following content in the internet header:
To: undisclosed-recipients:;
Bcc: eugene#somedomain.com
In the case of OOM, you can use the MailItem.ReceivedByName property which returns a string representing the display name of the true recipient for the mail message.
In the case of Extended MAPI (Redemption is a wrapper around this API), you can use the PidTagReceivedRepresentingSmtpAddress property which contains the SMTP email address of the user represented by the receiving mailbox owner. The DASL property name is http://schemas.microsoft.com/mapi/proptag/0x5D08001F.
No, even BCC recipients won't be in the BCC header. You can generate the BCC header when saving in the MIME format, but it won't be present in messages received from a POP3 or IMAP4 server.
Generally, when a message is sent through SMTP, the server does not look at the MIME headers to figure out the recipients. The recipients are determined by the RCPT TO SMTP command. And no email client specifies BCC header in the outgoing messages - that would be a bug.
Hi We are trying to develop a mail sending system using MailKit. We have a set of Email Templates which are created using WORD and saved as MHTML files. The whole thing is working fine, when we use MailKit to create a MimeMessage from the MHT file.
But post creating this message, I am not able to see a way of adding attachments to this.
Currently we are trying the following.
private void SendEmail(string templatePath, List<string> attachments)
{
// Load the MHT Template
var mimeMessage = MimeMessage.Load(templatePath);
mimeMessage.From.Add(new MailBoxAddress("test#Test.com"));
mimeMessage.To.Add(new MailBoxAddress("test#Test.com"));
foreach (var attachment in attachments)
{
var fileAttachment = new MimePart()
{
ContentObject = new ContentObject(File.OpenRead(Path.Combine(attachment), ContentEncoding.Default),
ContentDisposition = new ContentDisposition(ContentDisposition.Attachment),
ContentTransferEncoding = ContentEncoding.Binary,
FileName = Path.GetFileName(attachment)
};
// Attachments is a read only Enumerable here.
mimeMessage.Attachments.Add
}
}
I add the attachment by the BodyBuilder:
BodyBuilder _body = new BodyBuilder
{
HtmlBody = message
};
_body.Attachments.Add(_fileName, _stream);
_email.Body = _body.ToMessageBody();
See this post stackoverflow
You will need to traverse the MIME tree structure of the message until you find the Multipart that you would like to add the "attachment" to and then use the Multipart.Add() method.
Keep in mind that a message is a nested tree structure and not a well-defined structure which has only 1 message body (or even just 2) and a list of attachments. It's a whole lot more complicated than that, so there's literally no way for MimeMessage.Attachments to "do the right thing".
For the general case, you can probably get away with something like this:
var message = MimeMessage.Load(fileName);
var attachment = new MimePart("application", "octet-stream") {
FileName = attachmentName,
ContentTransferEncoding = ContentEncoding.Base64,
Content = new MimeContent(attachmentStream)
};
if (!(message.Body is Multipart multipart &&
multipart.ContentType.Matches("multipart", "mixed"))) {
// The top-level MIME part is not a multipart/mixed.
//
// Attachments are typically added to a multipart/mixed
// container which tends to be the top-level MIME part
// of the message (unless it is signed or encrypted).
//
// If the message is signed or encrypted, though, we do
// do not want to mess with the structure, so the correct
// thing to do there is to encapsulate the top-level part
// in a multipart/mixed just like we are going to do anyway.
multipart = new Multipart("mixed");
// Replace the message body with the multipart/mixed and
// add the old message body to it.
multipart.Add(message.Body);
message.Body = multipart;
}
// Add the attachment.
multipart.Add(attachment);
// Save the message back out to disk.
message.WriteTo(newFileName);
I've been going thumbing through the documentation and searching the internet to find documenation on how to add attachments to created templates. I'm using darrencauthon's CSharp-Sparkpost to handle the API calls. So far what I have is not working. Does anyone have a working solution (possible?) or a better solution for C#? I'm not opposed to using a different library. This is the link to CSharp-Sparkpost
Here's what I've got so far:
var t = new Transmission();
t.Content.From.Email = "from#thisperson.com";
t.Content.TemplateId = "my-template-email";
new Recipient
{
Address = new Address { Email = recipient }
}
.Apply(t.Recipients.Add);
new Attachment
{
Data = //CSVDATA,
Name = "Table.csv",
Type = "text/csv"
}.Apply(t.Content.Attachments.Add);
var client = new SparkPost.Client(Util.GetPassword("sparkpostapikey"));
client.Transmissions.Send(t).Wait();
I've verified that I can send this attachment without a template and also verified that I can send this template without the attachment. So... the Email is getting sent; however, the content received is only the template and substitution data. No attachment with the template email.
Using Darren's library, and combining the requirements for my project, this is the solution I've come up with. I'm just making an additional API call to grab the template Html so I can build the transmission without having to send the template_id. Still using the CSharp-Sparkpost library to make all of the calls. I modified Darren's example SendInline program as such:
static async Task ExecuteEmailer()
{
var settings = ConfigurationManager.AppSettings;
var fromAddr = settings["fromaddr"];
var toAddr = settings["toaddr"];
var trans = new Transmission();
var to = new Recipient
{
Address = new Address
{
Email = toAddr
},
SubstitutionData = new Dictionary<string, object>
{
{"firstName", "Stranger"}
}
};
trans.Recipients.Add(to);
trans.SubstitutionData["firstName"] = "Sir or Madam";
trans.Content.From.Email = fromAddr;
trans.Content.Subject = "SparkPost sending attachment using template";
trans.Content.Text = "Greetings {{firstName or 'recipient'}}\nHello from C# land.";
//Add attachments to transmission object
trans.Content.Attachments.Add(new Attachment()
{
Data = Convert.ToBase64String(System.IO.File.ReadAllBytes(#"C:\PathToFile\ExcelFile.xlsx")),
Name = "ExcelFile.xlsx",
Type = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
});
Console.Write("Sending mail...");
var client = new Client(settings["apikey"]);
client.CustomSettings.SendingMode = SendingModes.Sync;
//retrieve template html and set Content.Html
var templateResponse = await client.Templates.Retrieve("template-email-test");
trans.Content.Html = templateResponse.TemplateContent.Html;
//Send transmission
var response = client.Transmissions.Send(trans);
Console.WriteLine("done");
}
Oh actually, I see now -- you are talking about adding attachments to templates, not attachments.
My answer to that is that back when I developed this library, attachment on templates was not supported by SparkPost itself.
My library allows you to try it, but that's because every template and non-template emails are considered "transmissions." So if you create a transmission, it has the option of adding attachments... but if you send the transmission with a template id, the attachment is ignored.
I could throw an error, or somehow design the API around this limitation, but what if they stopped ignoring the attachment but my library threw an error? I designed the library for flexibility as the SparkPost web API grew, and I didn't want my library to get in the way.
If you want to test if you're sending the attachment right, send your transmission without a transmission id, and instead with a subject and email body. If the email goes through and you get an attachment, then you know it's because of this template/attachment restriction from SparkPost.
NOTE: I'm putting this answer on Stack Overflow, and it's possible that this dated message will no longer be valid in the future.
I'm Darren Cauthon, the primary author of this library.
I have attachment support in my acceptance tests, which are run before each release. The link is below, but the code should be as simple as:
// C#
var attachment = File.Create<Attachment>("testtextfile.txt");
transmission.Content.Attachments.Add(attachment);
https://github.com/darrencauthon/csharp-sparkpost/blob/3a8cb1efbb8c9a0448c71c126ce7f88759867fb0/src/SparkPost.Acceptance/TransmissionSteps.cs#L56
I only need header and metadata of attachments from Gmail, I don't want to get email body or the content of the attachments, because they may be very big and I don't need them.
Is there a way to do so through AE.Net.Mail or gmail api? It seems only come with one option of of headers only, which will not fetch the attachment metadata for me.
MailMessage[] GetMessages(string startUID, string endUID, bool headersonly = true, bool setseen = false);
Eventually, I gave up on AE.Net.Mail and switched to Gmail API instead. And I am able to get attachment meta data from the Message Get Request, without getting the actual attachment file.
https://developers.google.com/gmail/api/v1/reference/users/messages/get
If you use MailKit instead of AE.Net.Mail, you can get the metadata of attachments using the following code snippet:
using (var client = new ImapClient ()) {
client.Connect ("imap.gmail.com", 993, true);
client.Authenticate ("username", "password");
client.Inbox.Open (FolderAccess.ReadWrite);
var summaries = client.Fetch (0, -1, MessageSummaryItems.UniqueId |
MessageSummaryItems.BodyStructure | // metadata for mime parts
MessageSummaryItems.Envelope); // metadata for messages
foreach (var summary in summaries) {
// Each summary item will have the UniqueId, Body and Envelope properties
// filled in (since that's what we asked for).
var uid = summary.UniqueId.Value;
Console.WriteLine ("The UID of the message is: {0}", uid);
Console.WriteLine ("The Message-Id is: {0}", summary.Envelope.MessageId);
Console.WriteLine ("The Subject is: {0}", summary.Envelope.Subject);
// Note: there are many more properties, but you get the idea...
// Since you want to know the metadata for each attachment, you can
// now walk the MIME structure via the Body property and get
// whatever details you want to get about each MIME part.
var multipart = summary.Body as BodyPartMultipart;
if (multipart != null) {
foreach (var part in multipart.BodyParts) {
var basic = part as BodyPartBasic;
if (basic != null && basic.IsAttachment) {
// See http://www.mimekit.net/docs/html/Properties_T_MailKit_BodyPartBasic.htm
// for more details on what properties are available.
Console.WriteLine ("The size of this attachment is: {0} bytes", basic.Octets);
Console.WriteLine ("The file name is: {0}", basic.FileName);
// If you want to download just this attachment, you can download it like this:
var attachment = client.Inbox.GetBodyPart (uid, basic);
}
}
}
}
client.Disconnect (true);
}
Keep in mind that since MIME is a tree structure and not a flat list, you'll realistically want to walk multiparts recursively.
I use free .net library to read email and I release: If I want to view body message, all free .net email library download body message and attachments. If attachments have a big size, I wait for a long time. Example: I use AE.NET.Mail to read the lastest email:
var dt = DateTime.Now;
Console.WriteLine(dt.ToLongTimeString());
// Connect to the IMAP server. The 'true' parameter specifies to use SSL
// which is important (for Gmail at least)
var ic = new ImapClient("imap.gmail.com", "yourEmail", "yourPassword",
ImapClient.AuthMethods.Login, 993, true);
// Select a mailbox. Case-insensitive
var mailCount = ic.GetMessageCount();
ic.SelectMailbox("INBOX");
var message = ic.GetMessage(mailCount - 1);
var body = message.Body;
Console.WriteLine(body);
ic.Disconnect();
ic.Dispose();
Console.WriteLine(DateTime.Now.ToLongTimeString());
Console.WriteLine((DateTime.Now - dt).TotalSeconds);
result: 478,6s with attachment size 23mb.
How can I do if I want to view only body message with fastest speed?
I am giving you link please follow it and try another open source mail library It helps you to consume less time . TRY THIS Then put the code as shown below
MailRepository rep = new MailRepository("imap.gmail.com", 993, true, #"username", "password");
foreach (ActiveUp.Net.Mail.Message email in rep.GetUnreadMails("Inbox"))
{
System.Web.HttpContext.Current.Response.Write(string.Format("<p>{0}: {1}</p><p>{2}</p>", email.From, email.Subject, email.BodyHtml.Text));
}