get the undeliverable email address with mimekit/mailkit library - c#

I started using #jstedfast Mimekit/Mailkit library, which is great by the way, to pull undeliverables using its subject line for search. I tried using the message.to, message.resentto.
how do i get that information. My first try today. I was able to get the list and the body but I just need the email. I tried using s22.imap but there's no support anymore then I discovered this. I know you're active here #jstedfast I need you help..there's no discussion tab in your github.
Thanks in advance

If you look at the raw message source, does the value of the Content-Type header match something along the lines of multipart/report; report-type=delivery-status? If so, it is very likely that this message will have a sub-part with a Content-Type header with a value of message/delivery-status. It should be the second child part of the multipart/report (the first part should be a human-readable explanation).
If so, you can cast the message/delivery-status MimeEntity to an instance of a MessageDeliveryStatus. You'll notice that the MessageDeliveryStatus class has a property called StatusGroups which is a list of multiple collections of headers (e.g. a list of HeaderList objects).
Each HeaderList contains information about a particular recipient that failed. I think what you want to do is look at the Final-Recipient header which will contain 2 pieces of information:
The address-type (should typically be "rfc822")
The mailbox of the recipient (which is what you want)
Unfortunately, MimeKit does not have any tools to parse the Final-Recipient header, but it should be trivial to locate the end of the address-type parameter in the header value by using IndexOf (';') and then you can use something like MailboxAddress.TryParse() to parse it (or you could probably just Substring() the value w/o parsing).
So, the way this might look in code is this:
string[] GetUndeliverableAddresses (MimeMessage message)
{
var report = message.Body as MultipartReport;
if (report == null)
throw new ArgumentException ("This is not a multipart/report!");
MessageDeliveryStatus status = null;
for (int i = 0; i < report.Count; i++) {
status = report[i] as MessageDeliveryStatus;
if (status != null)
break;
}
if (status == null)
throw new ArgumentException ("Did not contain a message/delivery-status!");
var undeliverables = new List<string> ();
for (int i = 0; i < status.StatusGroups.Count; i++) {
var recipient = status.StatusGroups[i]["Final-Recipient"];
int semicolon = recipient.IndexOf (';');
var undeliverable = recipient.Substring (semicolon + 1).Trim ();
undeliverables.Add (undeliverable);
}
return undeliverables.ToArray ();
}
For more information on message/delivery-status, see https://www.rfc-editor.org/rfc/rfc3464
Hope that helps.

this is what I did
foreach (var uid in inbox.Search(query))
{
var message = inbox.GetMessage(uid);
// Console.WriteLine("Subject: {0}", message.Subject);
//Console.WriteLine("Subject: {0}", message.Headers);
// Console.WriteLine("Subject: {0}", message.BodyParts);
var text = message.BodyParts;
string src = text.ElementAt(1).ToString();
int srcStart = src.IndexOf("RFC822;",0); << i used final-recipient intially
int srcEnd = src.IndexOf("Action", 0);
Console.WriteLine("Email:" + src.Substring(srcStart + 8, srcEnd - (srcStart + 8)));
Console.WriteLine(src);
//foreach (var part in message.BodyParts)
//{
// Console.WriteLine(part);
// // do something
//}
}
let me know if there could be a problem..will I get the rfc822 if the recipient inbox is full? there's no way i can test that.. I tested with emails with nonexistent domain, mailbox does not exist..with live.com, randomdomain.com,yahoo.com and gmail. gmail, on the other hand does not return any undeliverables.

Related

MailKit IMAP fetch only new, not downloaded messages

i'm using MailKit to implement an IMAP email client. In the various examples i've seen that the code to fetch message headers is this one:
var messages = client.Inbox.Fetch (0, -1, MessageSummaryItems.Full | MessageSummaryItems.UniqueId).ToList();
If i have correctly understood, this fetches always ALL messages.
My idea is to save in a local db messages already fetched, and then, for subsequent fetches, getting only differences.
Is there a way to accomplish this?
Thanks
Is there a way to accomplish this?
Yes, of course. The API allows you to request the information for any set of messages you want, whether you want to reference them by index or by UID.
The real question is "how?" and that all depends on two things:
The IMAP extensions supported by your IMAP server
The design of your email client and how you've chosen to populate your cache of message summary information (needed to populate your ListView or TreeView of messages in your UI).
If your IMAP server supports the QRESYNC extension, you'll want to read that specification so that you understand how best to use it as well as taking a look at the ImapFolder.Open (FolderAccess access, uint uidValidity, ulong highestModSeq, IList uids, CancellationToken cancellationToken) method.
If the IMAP server doesn't support QRESYNC, you might want to look into taking advantage of the CONDSTORE extension. You can take advantage of this extension by using any of the Fetch() or FetchAsync() methods that take a modseq value.
In the end, your code will end up looking something like this (untested):
var uidValidity = cache.GetUidValidity ();
var known = cache.GetKnownUids ();
UniqueIdSet missing;
folder.MessageFlagsChanged += OnMessageFlagsChanged;
if (client.Capabilities.HasFlag (ImapCapabilities.QuickResync)) {
var highestModSeq = cache.GetHighestKnownModSeq ();
folder.MessagesVanished += OnMessagesVanished;
// This version of the Open() method will emit MessagesVanished and MessageFlagsChanged
// for all messages that have been expunged or have changed since the last session.
folder.Open (FolderAccess.ReadWrite, uidValidity, highestModSeq, known);
if (folder.UidValidity != uidValidity) {
// our cache is no longer valid, we'll need to start over from scratch
cache.Clear ();
cache.SetUidValidity (folder.UidValidity);
missing = folder.Search (SearchQuery.All);
} else {
// figure out which messages we are missing in our cache
missing = new UniqueIdSet (SortOrder.Ascending);
var all = folder.Search (SearchQuery.All);
foreach (var uid in all) {
if (!known.Contains (uid))
missing.Add (uid);
}
}
} else {
folder.MessageExpunged += OnMessageExpunged;
folder.Open (ImapFolder.ReadWrite);
if (folder.UidValidity != uidValidity) {
// our cache is no longer valid, we'll need to start over from scratch
cache.Clear ();
cache.SetUidValidity (folder.UidValidity);
missing = folder.Search (SearchQuery.All);
} else {
var all = folder.Search (SearchQuery.All);
// purge messages from our cache that have been purged on the remote IMAP server
foreach (var uid in known) {
if (!all.Contains (uid))
cache.Remove (uid);
}
// sync flag changes since our last session
known = cache.GetKnownUids ();
if (known.Count > 0) {
IList<IMessageSummary> changed;
if (client.Capabilities.HasFlag (ImapCapabilities.CondStore)) {
var highestModSeq = cache.GetHighestKnownModSeq ();
changed = folder.Fetch (known, highestModSeq, MessageSummaryItems.Flags | MessageSummaryItems.ModSeq | MessageSummaryItems.UniqueId);
} else {
changed = folder.Fetch (known, MessageSummaryItems.Flags | MessageSummaryItems.UniqueId);
}
foreach (var item in changed) {
// update the cache for this message
cache.Update (item);
}
}
// figure out which messages we are missing in our cache
missing = new UniqueIdSet (SortOrder.Ascending);
foreach (var uid in all) {
if (!known.Contains (uid))
missing.Add (uid);
}
}
}
// fetch the summary information for the messages we are missing
var fields = MessageSummaryItems.Full | MessageSummaryItems.UniqueId;
if (client.Capabilities.HasFlag (ImapCapabilities.CondStore))
fields |= MessageSummaryItems.ModSeq;
var newMessages = folder.Fetch (missing, fields);
foreach (var message in newMessages)
cache.Add (message);
cache.SetHighestModSeq (folder.HighestModSeq);
And then you'd need to have at least the following event handlers:
void OnMessageFlagsChanged (object sender, MessageFlagsChangedEventArgs e)
{
cache.Update (e.Index, e.Flags, e.ModSeq);
}
void OnMessageExpunged (object sender, MessageExpungedEventArgs e)
{
cache.Remove (e.Index);
}
void OnMessagesVanished (object sender, MessagesVanishedEventArgs e)
{
cache.RemoveRange (e.UniqueIds);
}

Find a recipient with a given address in a folder

My company needs an add-in for automatically adding offers to the email when it is the first time we send an email to a recipient.
My question is :
How can I check if this is the first time the user sends an email to the recipients?
I tried this but I receive error that Recipient is unknown property. And I also think that this is not the right approach...
object folderItem;
Boolean AlreadyEmailed = false;
if (mail != null)
{
const string PR_SMTP_ADDRESS =
"http://schemas.microsoft.com/mapi/proptag/0x39FE001E";
Outlook.Recipients recips = mail.Recipients;
foreach (Outlook.Recipient recip in recips)
{
Outlook.PropertyAccessor pa = recip.PropertyAccessor;
string smtpAddress =
pa.GetProperty(PR_SMTP_ADDRESS).ToString();
string filter = "[Recipient] = 'John#foo.com'";
filter = filter.Replace("John#foo.com", smtpAddress);
Debug.WriteLine(filter);
folderItem = items.Restrict(filter);
if(folderItem != null)
{
Debug.WriteLine("We found items that have the filter");
AlreadyEmailed = true;
}
//Debug.WriteLine(recip.Name + " SMTP=" + smtpAddress);
}
if(!AlreadyEmailed)
{
Debug.WriteLine("This is the first time we email ... ");
}
}
The Sent property of the MailItem class returns a Boolean value that indicates if a message has been sent. In general, there are three different kinds of messages: sent, posted, and saved. Sent messages are items sent to a recipient or public folder. Posted messages are created in a public folder. Saved messages are created and saved without either sending or posting.
Also you may use the following Extended MAPI properties that deal with the message state (replied/forwarded):
PR_ICON_INDEX (http://schemas.microsoft.com/mapi/proptag/0x10800003)
PR_LAST_VERB_EXECUTED (the DASL name is http://schemas.microsoft.com/mapi/proptag/0x10810003)
PR_LAST_VERB_EXECUTION_TIME (0x10820040)
To get these values use the PropertyAccessor class (see the corresponding properties of Outlook items).
Be aware, new Outlook items don't have the EntryID property set.
You can Use To/CC/BCC properties in Items.Find/Restrict. Note that it is better to use Find in your case since you only need a single match, not all of them. Also note that Restrict will not return null if no matches are found, but rather an Items collection with Items.Count == 0.
That being said, To/CC/BCC might not include addresses, only names, so search won't help you. You can still loop through all items in the folder and explicitly check the Recipients collection of each item, but that will be hugely inefficient.
On the Extended MAPI level (C++ or Delphi), one can create subrestrictions on message recipients (or attachments), but the Outlook Object Model does not expose that functionality.
If using Redemption is an option (I am its author), its implementation of Find/Restrict does support queries on the Recipients collection:
set Session = CreateObject("Redemption.RDOSession")
Session.MAPIOBJECT = Application.Session.MAPIOBJECT
set YourOutlookFolder = Application.ActiveExplorer.CurrentFolder
set rFolder = Session.GetFolderFromID(YourOutlookFolder.EntryID)
set rItems = rFolder.Items
set rMsg = rItems.Find("Recipients LIKE 'John#foo.com' ")
while not (rMsg Is Nothing)
Debug.print rMsg.Subject
set rMsg = rItems.FindNext
wend
In C# (not tested):
Redemption.RDOSession Session = new Redemption.RDOSession();
Session.MAPIOBJECT = Application.Session.MAPIOBJECT;
set rFolder = Session.GetFolderFromID(YourOutlookFolder.EntryID);
Redemption.RDOMail rMsg = rFolder.Items.Find("Recipients LIKE 'John#foo.com' ") ;
AlreadyEmailed = rMsg != null;

How to extract body from email

Here is my code I am trying to get body of the email from textdata property but it is giving error object reference not set to instance of an object I dont have a clue what to do
IMAPConfig config = new IMAPConfig("imap-mail.outlook.com","name#hotmail.com", "password", true, true, "");
config.CacheFile = "";
IMAPClient client = null;
client = new IMAPClient(config, null, 5);
IMAPFolder f = client.Folders["Inbox"];
// Console.WriteLine(f.GetMessageByID(7049)) ;
int[] msgCount = null;
while (msgCount == null || msgCount.Length == 0)
{
msgCount = f.CheckForNewMessages();
Thread.Sleep(1000);
}
foreach (int id in msgCount)
{
IMAPMessage msg = f.GetMessageByID(id);
string a = null;
a = msg.TextData.TextData;
//MessageBox.Show(msg.TextData.ToString());
msg.MarkAsRead();
}
Seeing as how InterIMAP is a dead project, I would highly recommend switching to MailKit which is not only actively maintained (by me), but also has a lot more features (I mean, InterIMAP's home page says it will get support for copying messages soon and hasn't been updated since 2009), has a lot more users, and a lot fewer bugs. It's also a lot easier to use.
For example, here's how you would do what you are trying to do with MailKit:
using (var client = new ImapClient ()) {
client.Connect ("imap-mail.outlook.com", 993, true);
client.Authenticate ("name#hotmail.com", "password");
client.Inbox.Open (FolderAccess.ReadWrite);
for (int i = 0; i < client.Inbox.Count; i++) {
var message = client.Inbox.GetMessage (i);
var html = message.HtmlBody;
var text = message.TextBody;
// mark the message as read
client.Inbox.AddFlags (id, MessageFlags.Seen, true);
}
client.Disconnect (true);
}
I've also got MSDN-style documentation (that I'm always working on improving) at http://www.mimekit.net/docs that you might find helpful. If you have any questions, my email address can be found on the github project page (the "MailKit" link at the top of my answer).
It looks like you are using InterIMAP for this. If you look at the source code for IMAPMessage.TextData (https://interimap.codeplex.com/SourceControl/latest#InterIMAP/InterIMAP/Objects/IMAPMessage.cs) you can see that it will return null if there is no plain text body part in the message.
/// <summary>
/// The content of the message as plain text (if available)
/// </summary>
[XmlIgnore]
public IMAPMessageContent TextData
{
get
{
//RefreshData(true, false);
foreach (IMAPMessageContent content in _bodyParts)
{
if (content.ContentType.ToLower().Contains("plain"))
return content;
}
return null;
}
//set { _textData = value; }
}

Outlook 2010 Addon - MailItem.ReplyAll with original body

I'm trying to create a small Addin, which should help me to create a Standard-Email for support-cases.
My procedure is looking like this:
private static void CreateAnswer(MailItem item, int pbiId, SupportType st)
{
MailItem answer = item.ReplyAll();
answer.Subject = String.Concat(String.Format("[{0}] ", pbiId), answer.Subject);
Recipient rec = answer.Recipients.Add("Test");
rec.Type = (int)OlMailRecipientType.olCC;
rec.Resolve();
answer.BodyFormat = OlBodyFormat.olFormatHTML;
string ressourceFile = String.Format("OutlookExtender2.{0}.html", st.ToString());
Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(ressourceFile);
StreamReader sr = new StreamReader(stream);
string text = sr.ReadToEnd();
string[] senderParts = item.SenderName.Split(' ');
string rawOrigBody = GetRawHtmlBody(item.HTMLBody);
string htmlBody = ReplaceBodyText(text, senderParts.Last(), pbiId);
htmlBody = htmlBody.Insert(htmlBody.IndexOf("</body>"), rawOrigBody);
answer.HTMLBody = htmlBody;
answer.Importance = MapImportanceToSupportType(st);
answer.Display(false);
}
Nothing to fancy here, but I'd really like to have the standard-answer with the header (From, Sent, To etc.)
As you can see I'm reading out manually the original text and add it, but this isn't working very well. Is there a possibility to make use of the outlook standard function "ReplyAll", which creates the desired original message?
So the original message should look like this (Sent from my private address):
Von: "Matthias Müller" [mailto:testekituks#gmx.ch]
Gesendet: Mittwoch, 26. Februar 2014 14:48
An: Matthias Müller
Betreff: test from gmx
test from gmx
Probably in english or german, doesn't matter that much.
Is there a possibility to let Outlook do this stuff?
Matthias
I don't quite follow what you are trying to do. Do you want to initialize a template with set content whenever you Reply All to a message? So that the body has + ?

EWS managed API. IsNew Always return false and can't use TextBody

I am a newbie developer and I have been stuck with EWS for hours now. I need to read through the most recent emails, get all the unread emails and use the data from them to do some stuff.
At this moment My code looks like this.
static void Main(string[] args)
{
ServicePointManager.ServerCertificateValidationCallback = CertificateValidationCallBack;
ExchangeService service = new ExchangeService(ExchangeVersion.Exchange2013);
service.Credentials = new WebCredentials("support#mycompany.com", "mysupersuperdupersecretpassword");
service.AutodiscoverUrl("support#mycompany.com", RedirectionUrlValidationCallback);
FindItemsResults<Item> findResults = service.FindItems(WellKnownFolderName.Inbox,new ItemView(2));
foreach (Item item in findResults.Items)
{
// works perfectly until here
Console.WriteLine(item.Subject);
Console.WriteLine('\n');
item.Load();
string temp = item.Body.Text;
// I can't seem to get TextBody to work. so I used a RegEx Match match = Regex.Match(temp, "<body>.*?</body>", RegexOptions.Singleline);
string result = match.Value;
result = result.Replace("<body>", "");
result = result.Replace("</body>", "");
Console.Write(result);
Console.WriteLine('\n');
//Now the email boddy is fine but IsNew always returns false.
if (item.IsNew)
{
Console.WriteLine("This message is unread!");
}
else
{
Console.WriteLine("This message is read!");
}
}
}
I have googled and tried and googled some more and I am stuck. How do I now which emails are read, is there a way to get the email body text that's more effective than what I have done ? Any help would be super appreciated.
The MSDN article for usage is pretty good if you haven't already read it.
For your issue, cast your item to an EmailMessage
foreach (Item item in findResults.Items)
{
var mailItem = (EmailMessage)item;
// works perfectly until here
Console.WriteLine(mailItem.Subject);
}
I did notice you are not using Property Sets, but having only used EWS for event notifications and not going through existing mails, it may be different.
UPDATE Additions in light of your changes
use this for your Property Set
new PropertySet(BasePropertySet.FirstClassProperties) {
RequestedBodyType = BodyType.Text
};
Also this reads a little nicer and uses the Body.Text property
foreach (Item myItem in findResults.Items.Where(i=>i is EmailMessage))
{
var mailItem = myItem as EmailMessage;
Console.WriteLine(mailItem.Subject);
mailItem.Load(new PropertySet(BasePropertySet.FirstClassProperties) {
RequestedBodyType = BodyType.Text
}); // Adding this parameter does the trick :)
Console.WriteLine(mailItem.Body.Text);
if(! mailItem.IsRead)
Console.WriteLine("Who is Your Daddy!!!!");
}

Categories

Resources