I'm using EWS in order to retrieve emails, but when I want to retrieve the attachments I have to call the following function for each:
fileAttachment.Load();
Everytime I do that, it goes to the server. Is it possible to retrieve all the attachments at once? Also, is it possible to retrieve all the attachments for several mail items?
The ExchangeService object has a GetAttachments method which basically allows you to do a batch GetAttachment request. So if you want to load the attachments on several messages at once you need to do something like (first call loadpropertiesforitems which does a batch GetItem to get the AttachmentIds)
FindItemsResults<Item> fItems = service.FindItems(WellKnownFolderName.Inbox,new ItemView(10));
PropertySet psSet = new PropertySet(BasePropertySet.FirstClassProperties);
service.LoadPropertiesForItems(fItems.Items, psSet);
List<Attachment> atAttachmentsList = new List<Attachment>();
foreach(Item ibItem in fItems.Items){
foreach(Attachment at in ibItem.Attachments){
atAttachmentsList.Add(at);
}
}
ServiceResponseCollection<GetAttachmentResponse> gaResponses = service.GetAttachments(atAttachmentsList.ToArray(), BodyType.HTML, null);
foreach (GetAttachmentResponse gaResp in gaResponses)
{
if (gaResp.Result == ServiceResult.Success)
{
if (gaResp.Attachment is FileAttachment)
{
Console.WriteLine("File Attachment");
}
if (gaResp.Attachment is ItemAttachment)
{
Console.WriteLine("Item Attachment");
}
}
}
Cheers
Glen
Related
I am getting emails from a mailbox using exchange webservices using a custom code block in C# (I'm not versed in C# at all so forgive code quality!) in UiPath. I am passing the exchange service and folder ID as arguments to the code block. I noticed that when there was a large attachment on the email it took significantly longer. I am not interested in the attachment I just want to be able to access some information about the email. This was my initial code:
//Search for oldest email
ItemView objView = new ItemView(1);
objView.OrderBy.Add(ItemSchema.DateTimeReceived, SortDirection.Ascending);
FindItemsResults<Item> lstItems = objServer.FindItems(in_FolderID, objView);
//Bind to email from result
if(lstItems.Count() == 0)
{
Console.WriteLine("Inbox appears to be empty");
out_ExchangeMessage = null;
out_InternetMessageID = null;
}
else
{
Item objItem = lstItems.ElementAt(0);
Console.WriteLine("Retrieving email: " + objItem.Subject);
PropertySet objPropertySet = new PropertySet(BasePropertySet.FirstClassProperties, ItemSchema.MimeContent, EmailMessageSchema.IsRead,ItemSchema.Attachments,ItemSchema.TextBody);
out_ExchangeMessage = EmailMessage.Bind(objServer,objItem.Id, objPropertySet);
out_InternetMessageID = out_ExchangeMessage.InternetMessageId;
Console.WriteLine("Message Retrieved: " + out_ExchangeMessage.InternetMessageId);
}
I tried removing ItemSchema.Attachments so this line reads as follows. But the email still takes significantly longer to download
PropertySet objPropertySet = new PropertySet(BasePropertySet.FirstClassProperties, ItemSchema.MimeContent, EmailMessageSchema.IsRead,ItemSchema.TextBody);
Is there a way to speed up the retrieving of emails with large attachments?
Because your including ItemSchema.MimeContent in your propertyset that is going to give you the whole MimeStream of the Message including attachments. If you don't require the MimeStream don't request it. Eg you should be able to get all the other properties of the Message eg body,subject,headers etc from other properties so it would only be required if you wanted to save the message.
I am trying to get the sent folders to display but it shows that the folder has no children in it. All folders are empty except inbox. I am using the following code.
using (var client = new ImapClient())
{
client.Connect(credentials.incoming_host, (int)credentials.incoming_port, credentials.incoming_ssl); //for SSL
client.Authenticate(credentials.email, credentials.password);
client.Inbox.Open(FolderAccess.ReadOnly);
var sentFolder= client.GetFolder(MailKit.SpecialFolder.Sent);
var Folders = client.GetFolders(client.PersonalNamespaces[0]);
client.Disconnect(true);
}
I tried sending an email using the same folder, and then append it like:
var sentFolder = imapclient.GetFolder(SpecialFolder.Sent);
sentFolder.Append(message);
My outlook did detect it and added into the sent folder.
From the MailKit README:
If the IMAP server supports the SPECIAL-USE or the XLIST (GMail) extension, you can get ahold of the pre-defined All, Drafts, Flagged (aka Important), Junk, Sent, Trash, etc folders like this:
if ((client.Capabilities & (ImapCapabilities.SpecialUse | ImapCapabilities.XList)) != 0) {
var drafts = client.GetFolder (SpecialFolder.Drafts);
} else {
// maybe check the user's preferences for the Drafts folder?
}
In cases where the IMAP server does not support the SPECIAL-USE or XLIST extensions, you'll have to come up with your own heuristics for getting the Sent, Drafts, Trash, etc folders. For example, you might use logic similar to this:
static string[] CommonSentFolderNames = { "Sent Items", "Sent Mail", "Sent Messages", /* maybe add some translated names */ };
static IFolder GetSentFolder (ImapClient client, CancellationToken cancellationToken)
{
var personal = client.GetFolder (client.PersonalNamespaces[0]);
foreach (var folder in personal.GetSubfolders (false, cancellationToken)) {
foreach (var name in CommonSentFolderNames) {
if (folder.Name == name)
return folder;
}
}
return null;
}
Using LINQ, you could simplify this down to something more like this:
static string[] CommonSentFolderNames = { "Sent Items", "Sent Mail", "Sent Messages", /* maybe add some translated names */ };
static IFolder GetSentFolder (ImapClient client, CancellationToken cancellationToken)
{
var personal = client.GetFolder (client.PersonalNamespaces[0]);
return personal.GetSubfolders (false, cancellationToken).FirstOrDefault (x => CommonSentFolderNames.Contains (x.Name));
}
Another option might be to allow the user of your application to configure which folder he or she wants to use as their Sent folder, Drafts folder, Trash folder, etc.
How you handle this is up to you.
It is necessary to open the folder otherwise it will appear empty.
IMailFolder personal = client.GetFolder(client.PersonalNamespaces[0]);
foreach (IMailFolder folder in personal.GetSubfolders(false, cancellationToken))
{
folder.Open(FolderAccess.ReadOnly);
Console.WriteLine($"folder.Name = {folder.Name}");
Console.WriteLine($"{folder.Name} messages : {folder.Count}");
}
I currently want to download all email messages (regardless in which folder they're in) to my SQL Server database.
Now while I know how to search for email messages or subscribe to streaming notifications, I've yet to learn on how to synchronize all messages from EWS to my database.
var emailMessages = GetItems<MSEmailMessage>(WellKnownFolderName.MsgFolderRoot);
foreach (var emailMessage in emailMessages)
{
Debug.WriteLine(emailMessage.Subject);
}
private IList<T> GetItems<T>(WellKnownFolderName wellKnownFolderName) where T : Item
{
IList<T> result = new List<T>();
Folder folder = Folder.Bind(_exchangeService, wellKnownFolderName);
if (folder.TotalCount > 0)
{
ItemView view = new ItemView(folder.TotalCount);
FindItemsResults<Item> items = _exchangeService.FindItems(wellKnownFolderName, view);
foreach (var resultItem in items.OfType<T>())
{
result.Add(resultItem);
}
}
return result;
}
This returns 0 email messages (it even threw an exception before checking for the folder.TotalCount before initializing a new ItemView...).
While checking for WellKnownFolderName.Inbox returns the email messages from the inbox, it does not allow me to query for sub folders to synchronize the entirety of the messages.
What am I missing?
You can build up a list of folders to search for mail in. Then iterate through each folder and get all the emails in that folder.
In the code snippet below we can create a folderSearchFilter with FolderTraversal set to Deep which will scan all sub folders of the target folder. We can then apply this filter to the two main well-known folders Inbox and SentItems
Once you have a list of folders to index, then you can use your own code to retrieve all the mails from that folder.
var view = new FolderView(int.MaxValue)
{
PropertySet = new PropertySet(BasePropertySet.FirstClassProperties) { FolderSchema.DisplayName }
};
SearchFilter foldersearchFilter = new SearchFilter.IsGreaterThan(FolderSchema.TotalCount, 0);
view.Traversal = FolderTraversal.Deep;
List<Folder> searchFolders;
try
{
searchFolders = new List<Folder>
{
Folder.Bind(ExchangeService, WellKnownFolderName.Inbox),
Folder.Bind(ExchangeService, WellKnownFolderName.SentItems)
};
}
catch (ServiceResponseException e) {}
searchFolders.AddRange(ExchangeService.FindFolders(WellKnownFolderName.Inbox, foldersearchFilter, view).Folders);
searchFolders.AddRange(ExchangeService.FindFolders(WellKnownFolderName.SentItems, foldersearchFilter, view).Folders);
var results = new List<Item>();
foreach (var searchFolder in searchFolders)
{
//Get all emails in this folder
}
Right, the root folder likely has 0 messages in it. When you do FindItems in a folder, the results don't bubble up from subfolders. You need to iterate over every folder if you want to get to their messages.
I want to move/copy my Email Attachments to new folder in outlook ;/ and my code doesn't work properly.
foreach(Item item in findResults.Items)
{
EmailMessage email = EmailMessage.Bind(service, item.Id, new PropertySet(BasePropertySet.FirstClassProperties, ItemSchema.Attachments));
if(false)
{
// OTC Marker HTML Body
}
else
{
if (email.HasAttachments)
{
foreach (Attachment attachment in email.Attachments)
{
EmailMessage emailAttachment = EmailMessage.Bind(service, attachment.Id, new PropertySet(BasePropertySet.FirstClassProperties, ItemSchema.Attachments));
ItemAttachment itemAttachment = attachment as ItemAttachment;
itemAttachment.Load();
EmailMessage mess = itemAttachment.Item as EmailMessage;
moveToTestFolder (mess, #"TestFolder");
}
}
else
{
//to do
}
}
}
And my moveToTestFolder method:
private void moveToTestFolder (EmailMessage item, string folderName)
{
Folder rootfolder = Folder.Bind(service, WellKnownFolderName.MsgFolderRoot);
rootfolder.Load();
var folders = rootfolder.FindFolders(new FolderView(20));
var folderItemToMove = folders.FirstOrDefault(f => f.DisplayName.Equals(folderName, StringComparison.OrdinalIgnoreCase));
item.Move(folderItemToMove.Id);
}
I'am trying to move attachment (if it is an email) to special folder in outlook. Moving the normal message is working now.
That won't work because you can only use the Move and Copy operations to copy an actual Mailbox Item not Attachments (you should be getting an error about and Invalid Id). One workaround for this is to get the MimeContent for the Email Attachment you want to move and then create a New object from that MimeCotent and save it to the folder you want to move the Item to eg
foreach (Attachment Attach in EWSItem.Attachments)
{
if (Attach is ItemAttachment)
{
PropertySet psProp = new PropertySet(BasePropertySet.FirstClassProperties);
psProp.Add(ItemSchema.MimeContent);
((ItemAttachment)Attach).Load(psProp);
if (((ItemAttachment)Attach).Item.MimeContent != null)
{
EmailMessage NewMessage = new EmailMessage(service);
NewMessage.MimeContent = ((ItemAttachment)Attach).Item.MimeContent;
NewMessage.SetExtendedProperty(new ExtendedPropertyDefinition(3591, MapiPropertyType.Integer), "1");
NewMessage.Save(folderItemToMove.Id);
}
}
}
You don't get full Fidelity of all the Exchange properties on the Message with this method as only the MimeContent is copied which is generally not a problem with Email but will be an issue for other objects types like Contacts, Tasks etc.
Cheers
Glen
I'm in the process of writing a simple console app that monitors a particular exchange mailbox, and when emails meeting particular criteria are received, the app will download an XML file attachment, and archive the email.
I've connected to EWS OK, and have been able to loop through any emails, but I'm struggling when it comes to create an EmailMessage object which I can use to access the attachments.
In the example code below, the EmailMessage message = EmailMessage.Bind(...) line executes without error, but doesn't return a valid message so when I access and properties or methods, I get an error: 'Object reference not set to an instance of an object'.
I'm new to C# let alone EWS so I'm struggling to know where to start...
Code Snippet:
public static void FindItems()
{
try
{
ItemView view = new ItemView(10);
view.OrderBy.Add(ItemSchema.DateTimeReceived, SortDirection.Ascending);
view.PropertySet = new PropertySet(
BasePropertySet.IdOnly,
ItemSchema.Subject,
ItemSchema.DateTimeReceived);
findResults = service.FindItems(
WellKnownFolderName.Inbox,
new SearchFilter.SearchFilterCollection(
LogicalOperator.Or,
new SearchFilter.ContainsSubstring(ItemSchema.Subject, "Sales Enquiry")),
view);
log2.LogInfo("Total number of items found: " + findResults.TotalCount.ToString());
foreach (Item item in findResults)
{
log2.LogInfo(item.Id);
EmailMessage message = EmailMessage.Bind(service, item.Id, new PropertySet(BasePropertySet.IdOnly, ItemSchema.Attachments));
Console.WriteLine(message.Subject.ToString());
if (message.HasAttachments && message.Attachments[0] is FileAttachment)
{
FileAttachment fileAttachment = message.Attachments[0] as FileAttachment;
fileAttachment.Load("C:\\temp\\" + fileAttachment.Name);
fileAttachment.Load();
Console.WriteLine("FileName: " + fileAttachment.FileName);
}
}
}
catch (Exception ex)
{
log2.LogError(ex.InnerException);
}
}
My code for accessing the attachments is straight from MSDN so I'm hoping it is there are thereabouts... Any ideas?
I'm afraid I revisited this problem and managed to cure it. Unfortunately I was too pressed at the time to come back here and document the solution. Time having passed, and my memory of what I changed has faded, but as far as I can remember it was a one line change:
EmailMessage message = EmailMessage.Bind(service, item.Id, new PropertySet(BasePropertySet.FirstClassProperties, EmailMessageSchema.Attachments));
The key difference here is that we have specified BasePropertySet.FirstClassProperties as the first parameter of PropertySet, rather than the BasePropertySet.IdOnly that we originally had.
My original code was lifted from an example online that did precisely what I was trying to achieve, so either the example wasn't quite right, or I transcribed it incorrectly or misunderstood some facet of the problem.
foreach(EmailMessage message in findResults)
{
message.Load();
Console.WriteLine(message.Subject.ToString());
if (message.HasAttachments && message.Attachments[0] is FileAttachment)
{
FileAttachment fileAttachment = message.Attachments[0] as FileAttachment;
fileAttachment.Load("C:\\temp\\" + fileAttachment.Name);
fileAttachment.Load();
Console.WriteLine("FileName: " + fileAttachment.FileName);
}
}