Getting unique emails EWS Managed Web API - c#

I am trying to retrieve the emails from the Exchange server using below code:
ExchangeService service = new ExchangeService(ExchangeVersion.Exchange2007_SP1);
service.Credentials = new WebCredentials("username", "somepassword");
service.TraceEnabled = true;
service.TraceFlags = TraceFlags.All;
service.AutodiscoverUrl("username", RedirectionUrlValidationCallback);
FindItemsResults<Item> findResults = service.FindItems(WellKnownFolderName.Inbox, new ItemView(10));
ServiceResponseCollection<GetItemResponse> items =
service.BindToItems(findResults.Select(item => item.Id), new PropertySet(BasePropertySet.FirstClassProperties, EmailMessageSchema.From, EmailMessageSchema.ToRecipients));
return items.Select(item =>
{
return new MailItem()
{
From = ((Microsoft.Exchange.WebServices.Data.EmailAddress)item.Item[EmailMessageSchema.From]).Address,
Recipients = ((Microsoft.Exchange.WebServices.Data.EmailAddressCollection)item.Item[EmailMessageSchema.ToRecipients]).Select(recipient => recipient.Address).ToArray(),
Subject = item.Item.Subject,
Body = item.Item.Body.ToString(),
};
}).ToArray();
I need to save the subject and body in my database . But i need unique emails becasue i don't want redundant emails to display on my system.
Means every time i synchronize my system with the exchange server , i will get new emails which i hadn't synchronized yet.

If I understand you right, you save the emails obtained by EWS in a Database.
Later you obtain the emails again and so you get the email you already have plus the new ones?
How about working with timestamps?
Get also the CreationTime (or ReceivedTime) of the MailItem and save it in the database too.
After that search in EWS only for mailitems that have CreationTime (or ReceivedTime) later than the last CreationTime (or ReceivedTime) in your Database.
So you only get the new emails.

A possible solution is to move the emails that you processed to the DeletedItems folder by calling
emailMessage.Delete(DeleteMode.MoveToDeletedItems);
Please note that I didn't have to keep a copy of the processed emails within my inbox, so this was a viable solution for me. If you for some reason have to keep a copy within your inbox folder this will not work for you.

Related

How to get notification when I'm getting a new message in my inbox(using IMAP and C#)?

I have a code that reads incoming messages to the mail, but I want to create a trigger so that when some message comes to me, some action occurs. For example, you can simply create a console application and the trigger will be Console.WriteLine("New message");
Of course, ideally, it would also be to read this message, but I can already do this myself later. I can't figure out how to implement the notification.
I will add the code on which I worked, but in this case, it does not seem to help. I'm new to Imap. I would be grateful for any help and advice.
static void GetItems()
{
ExchangeService service;
String pattern = "file:///C:/Users/";
service = new ExchangeService
{
Credentials = new WebCredentials("mail", "password")
};
FolderView view = new
FolderView(10);
view.PropertySet = new PropertySet(BasePropertySet.IdOnly);
view.PropertySet.Add(FolderSchema.DisplayName);
service.Url = new Uri("https://outlook.office365.com/EWS/Exchange.asmx");
SearchFilter searchFilter = new SearchFilter.IsGreaterThan(FolderSchema.TotalCount, 0);
view.Traversal = FolderTraversal.Deep;
FindFoldersResults findFolderResults = service.FindFolders(WellKnownFolderName.Inbox, searchFilter, view);
foreach (Folder f in findFolderResults)
{
Console.WriteLine("Handling folder: " + f.DisplayName);
SearchFilter sf = new SearchFilter.SearchFilterCollection(LogicalOperator.And, new SearchFilter.IsEqualTo(EmailMessageSchema.IsRead, true));
ItemView view1 = new ItemView(1);
FindItemsResults<Item> findEmailResults = service.FindItems(f.Id, sf, view1);
foreach (Item i in findEmailResults)
{
Console.WriteLine("Processing email: " + i.Subject);
}
}
}
I'd recommend switching to using Graph API instead of EWS, Use the Microsoft Graph API to get change notifications.
EWS does support notifications. It allows for push, pull, and streaming notifications, whatever makes sense in your particular case.
Start at Notification subscriptions, mailbox events, and EWS in Exchange.
So, I made my own research about this mail-notification system and found a really good article with the code.
This is the article:
MailKit : Obtaining new mail notification from IMAP server
This is the code for mail-notification:
https://github.com/jstedfast/MailKit/blob/6c813d02617edc3e3de5481a413b1e2fb43bfe8c/samples/ImapIdle/ImapIdle/Program.cs
Everything is working fine, I already used this. You just need to download some libraries.

How to get the MessageId from all exchange items

Hello I recently got into development around EWS. One of the issue came up to me is that a client ask me to import emails into database and he wants to detect the duplicate based on InternetMessageID this way he doesn't have to import the duplicate emails and my code came up to this point.
private static string GetInternetMessageID(Microsoft.Exchange.WebServices.Data.Item email)
{
EmailMessage emailMsg = email as EmailMessage;
string returnId = string.Empty;
if ((emailMsg != null)) {
try {
emailMsg.Load();
//loads additional info, without calling this ToRecipients (and more) is empty
} catch (ArgumentException ex) {
//retry
email.Load();
}
returnId = emailMsg.InternetMessageId;
} else {
//what to do?
}
return returnId;
}
I can handle regular emails, but for special exchange objects such as contact, Calendar, Posts etc it does not work because it could not cast it to an EmailMessage object.
And I know you can extract the internetMessageId from those objects. Because the client used to have another software that extract this ID for them, maybe the property is not called internetMessageID, I think I probally have to extract it from the internetMessageHeader. However when ever I try to get it from the item object it just throws me an error. How do I get the internet messageID from these "Special" exchange items?
PS i am aware of item.id.UniqueID however that is not what I want as this id changes if I move items from folder to another folder in exchange
Only objects that have been sent via the Transport service will have an InternetMessageId so things like Contacts and Tasks because they aren't messages and have never been routed via the Transport service will never have an Internet MessageId. You probably want to look at using a few properties to do this InternetMessageId can be useful for messages PidTagSearchKey https://msdn.microsoft.com/en-us/library/office/cc815908.aspx is one that can be used (if you good this there are various examples of using this property).
If your going to use it in Code don't use the method your using to load the property on each item this is very inefficient as it will make a separate call for each object. Because these I'd's are under 256 Kb just retrieve then when using FindItems. eg
ExtendedPropertyDefinition PidTagSearchKey = new ExtendedPropertyDefinition(0x300B, MapiPropertyType.Binary);
ExtendedPropertyDefinition PidTagInternetMessageId = new ExtendedPropertyDefinition(0x1035, MapiPropertyType.String);
PropertySet psPropSet = new PropertySet(BasePropertySet.IdOnly);
psPropSet.Add(PidTagSearchKey);
psPropSet.Add(PidTagInternetMessageId);
ItemView ItemVeiwSet = new ItemView(1000);
ItemVeiwSet.PropertySet = psPropSet;
FindItemsResults<Item> fiRess = null;
do
{
fiRess = service.FindItems(WellKnownFolderName.Inbox, ItemVeiwSet);
foreach (Item itItem in fiRess)
{
Object SearchKeyVal = null;
if (itItem.TryGetProperty(PidTagSearchKey, out SearchKeyVal))
{
Console.WriteLine(BitConverter.ToString((Byte[])SearchKeyVal));
}
Object InternetMessageIdVal = null;
if (itItem.TryGetProperty(PidTagInternetMessageId, out InternetMessageIdVal))
{
Console.WriteLine(InternetMessageIdVal);
}
}
ItemVeiwSet.Offset += fiRess.Items.Count;
} while (fiRess.MoreAvailable);
If you need larger properties like the Body using the LoadPropertiesForItems Method https://blogs.msdn.microsoft.com/exchangedev/2010/03/16/loading-properties-for-multiple-items-with-one-call-to-exchange-web-services/

EWS error when retrieving complex property

We have an Exchange Web Services application that is throwing errors when an email is referenced that doesn't have a subject.
Our automated process needs to use the subject of the email, so the code is trying to reference it. However, when the subject is missing for an email in the inbox, instead of throwing an error, I want to change the behaviour.
Here is my code:
//creates an object that will represent the desired mailbox
Mailbox mb = new Mailbox(common.strInboxURL);
//creates a folder object that will point to inbox fold
FolderId fid = new FolderId(WellKnownFolderName.Inbox, mb);
... code removed fro brevity ...
// Find the first email message in the Inbox that has attachments. This results in a FindItem operation call to EWS.
FindItemsResults<Item> results = service.FindItems(fid, searchFilterCollection, view);
if (results.Count() > 0)
{
do
{
// set the prioperties we need for the entire result set
view.PropertySet = new PropertySet(
BasePropertySet.IdOnly,
ItemSchema.Subject,
ItemSchema.DateTimeReceived,
ItemSchema.DisplayTo, EmailMessageSchema.ToRecipients,
EmailMessageSchema.From, EmailMessageSchema.IsRead,
EmailMessageSchema.HasAttachments, ItemSchema.MimeContent,
EmailMessageSchema.Body, EmailMessageSchema.Sender,
ItemSchema.Body) { RequestedBodyType = BodyType.Text };
// load the properties for the entire batch
service.LoadPropertiesForItems(results, view.PropertySet);
so in that code, the error is being thrown on the complex property get on the line service.LoadPropertiesForItems(results, view.PropertySet); at the end.
So, I know I am going to have to do something like a Try..Catch here, however, I would need to check that the Subject property of the mail item exists before I can reference it to see what it is - some kind of chicken and egg problem.
If there is no subject, I need to mark the email as read, send a warning email off to a team, and then gon onwith the next unread email in the mailbox.
Any suggestions about the best way to approach this would be appreciated.
Thanks
Is the Subject not set or is it blank ?
You should be able to isolate any of these type of Emails with a SearchFitler eg use an Exists Search filter for the Subject property and then negate it so it will return any items where the Subject is not set
SearchFilter sfSearchFilteri = new SearchFilter.Exists(ItemSchema.Subject);
SearchFilter Negatesf = new SearchFilter.Not(sfSearchFilteri);
service.FindItems(WellKnownFolderName.Inbox, Negatesf, new ItemView(1000));
Then just exclude those items from your LoadPropertiesForItems
Cheers
Glen

Sending email to multiple recipients via Google

I am working on an C# project where I am building my own SMTP server. It is basically working but I am now trying to get multiple recipients being sent but I am receiving an error.
I am getting the MX record from the senders domain and I am then using MX Record to try and send an email to multiple recipients. If I do two recipients with the same domain it works fine, if the two recipients have different domains, I then get the following response:
Failed to send email. General Exception: Error in processing. The server response was: 4.3.0 Multiple destination domains per transaction is unsupported. Please
There is nothing after please that's the end of the response.
Below is how I am getting the MX Record:
string[] mxRecords = mxLookup.getMXRecords(Classes.CommonTasks.getDomainFromEmail(domain));
public string[] getMXRecords(string domain)
{
DnsLite dl = new DnsLite(library);
ArrayList dnsServers = getDnsServers();
dl.setDnsServers(dnsServers);
ArrayList results = null;
string[] retVal = null;
results = dl.getMXRecords(domain);
if (results != null)
{
retVal = new string[results.Count];
int counter = 0;
foreach (MXRecord mx in results)
{
retVal[counter] = mx.exchange.ToString();
counter++;
}
}
return retVal;
}
Below is how I am sending the email.
if (mxRecords != null)
{
MailMessage composedMail = new MailMessage();
composedMail.From = new MailAddress(message.EmailFromAddress);
//MailAddressCollection test = new MailAddressCollection();
//composedMail.To = test;
composedMail = addRecipientsToEmail(composedMail, message.emailRecipients);
composedMail.Subject = message.subject;
composedMail.Body = message.EmailBody;
if (message.contentType.ToString().Contains("text/html"))
{
composedMail.IsBodyHtml = true;
}
SmtpClient smtp = new SmtpClient(mxRecords[0]);
smtp.DeliveryMethod = SmtpDeliveryMethod.Network;
smtp.Port = 25;
if (Configuration.emailConfig.useSmtpMaxIdleTime)
{
smtp.ServicePoint.MaxIdleTime = 1;
}
library.logging(methodInfo, string.Format("Sending email via MX Record: {0}", mxRecords[0]));
smtp.Send(composedMail);
updateEmailStatus(message.emailID, EmailStatus.Sent);
library.logging(methodInfo, string.Format("Successfully sent email ID: {0}", message.emailID));
}
else
{
string error = string.Format("No MX Record found for domain: {0}", domain);
library.logging(methodInfo, error);
library.setAlarm(error, CommonTasks.AlarmStatus.Warning, methodInfo);
}
This looks as if it is something that Google is restricting from being done but I can't find a way to work around out, other than to send the emails separately for each recipient.
If it's of any use, the two domains are google app domains.
Thanks for any help you can provide.
It seems you are not alone. Check this out.
:
"Based on my investigation and research, I believe what's happening is your system is connecting directly to the delivery server (aspmx.l.google.com). As this is the delivery server, it does not allow:
Delivery to accounts that are not provisioned on Google (i.e., unauthenticated relaying).
Delivery to multiple different domains within the same SMTP session.
The second one is the one that is important to us. As of the beginning of this month(May2012), adjustments were made to our server settings that mean that our delivery server is strictly enforcing the multiple-domain-not-allowed rule. There are two ways to get around this. The first is to send to separate domains on separate smtp sessions, and the second is to use smtp.gmail.com in place of aspmx.l.google.com."
http://productforums.google.com/forum/#!topic/apps/jEUrvTd1S_w
Sine you can send an email with a single recipient through google your issue is not in resolving the mx-record. The Mx record tells an IP-address but doesn't tell about the functionality/behavior of the service behind that ip.
You can resolve the mx-record, so far so good. But you don't need to to resolve the mx on your own as the smtp-client does it on your behalve, just supllying the hostname will do. But note that this was a great excersise to learn more about DNS. No time waste :-)
As far as I recall, sending mail through google in the way you intend you need a google account. Authenticate with the smtp-server with the credentials for that account can open a new perspective

Exchange EWS Managed API - unexpected token in XML

Im trying to write a simple sample program which checks for any new mail on an Exchange 2010 server. As far as I can tell, the code I've got should work fine.
I setup the service as follows:
ExchangeService service = new ExchangeService();
service.Credentials = new WebCredentials("user#domain.co.uk", "password");
service.Url = new Uri("https://address/owa");
Upon executing the following code:
int unreadMail = 0;
// Add a search filter that searches on the body or subject.
List<SearchFilter> searchFilterCollection = new List<SearchFilter>();
searchFilterCollection.Add(new SearchFilter.ContainsSubstring(ItemSchema.Subject, "Defense"));
// Create the search filter.
SearchFilter searchFilter = new SearchFilter.SearchFilterCollection(LogicalOperator.Or, searchFilterCollection.ToArray());
// Create a view with a page size of 50.
ItemView view = new ItemView(50);
// Identify the Subject and DateTimeReceived properties to return.
// Indicate that the base property will be the item identifier
view.PropertySet = new PropertySet(BasePropertySet.IdOnly, ItemSchema.Subject, ItemSchema.DateTimeReceived);
// Order the search results by the DateTimeReceived in descending order.
view.OrderBy.Add(ItemSchema.DateTimeReceived, SortDirection.Descending);
// Set the traversal to shallow. (Shallow is the default option; other options are Associated and SoftDeleted.)
view.Traversal = ItemTraversal.Shallow;
// Send the request to search the Inbox and get the results.
FindItemsResults<Item> findResults = service.FindItems(WellKnownFolderName.Inbox, searchFilter, view);
// Process each item.
foreach (Item myItem in findResults.Items)
{
if (myItem is EmailMessage)
{
if (myItem.IsNew) unreadMail++;
}
}
I get this error (on the FindItemResults line):
'>' is an unexpected token. The expected token is '"' or '''. Line 1, position 63.
This appears to be an error in the XML the API is actually generating, I've tried a few different lots of code (all along the same lines) and not found anything that works.
Any ideas? At a bit of a loss when it comes directly from the API!
Cheers, Daniel.
Nevermind, fixed it - needed to point my service to:
https://servername/ews/Exchange.asmx
and provide regular domain login details such as "username", "password" in order to connect!

Categories