I am developing a c# add-in for Outlook 2016. In some procedures I need to remove the mail item category and user properties. I am trying to do this by removing properties from inbox/sent folder simultaneously. For this I start 2 new tasks:
Task.Factory.StartNew(() => RunRemovingExistingVisualIndicationFromInboxFolder(), GlobalEnvironmentMethods.removeExistingVisualIndicationFromInboxFolderCancellationToken.Token);
Task.Factory.StartNew(() => RunRemovingExistingVisualIndicationFromSentFolder(), GlobalEnvironmentMethods.removeExistingVisualIndicationFromInboxFolderCancellationToken.Token);
In my code below, is the function which removes these properties (for example, I attach only processing mail items in my Inbox folder. Note: the same code is used for my Sent folder. I only changed the Outlook folder to use the "Sent" folder.)
public static void RunRemovingExistingVisualIndicationFromInboxFolder()
{
NLogMethods.GetInstance().WriteInfoLog("[ADDITIONALVISUALINDICATIONSETTINGSFORM -> RUNREMOVEEXISTINGVISUALINDICATION()]: Processing " + "\"" + "Inbox" + "\"" + " folder started.");
Outlook.Items folderItemsInbox = null;
folderItemsInbox = OutlookMethods.currentOAppNameSpace.Stores[JSONMethods.ReadAddInSetting().AddInPreferredMailBoxStoreName].GetDefaultFolder(Outlook.OlDefaultFolders.olFolderInbox).Items;
if (folderItemsInbox.Count > 0)
{
for (int i = 1; i <= folderItemsInbox.Count; i++)
{
totalProcessedInboxMailItems++;
if (GlobalEnvironmentMethods.removeExistingVisualIndicationFromInboxFolderCancellationToken.IsCancellationRequested)
{
GlobalEnvironmentMethods.globalProcessingRemovingVisualIndicationTaskState = false;
GlobalEnvironmentMethods.removeExistingVisualIndicationFromInboxFolderCancellationToken.Dispose();
GlobalEnvironmentMethods.removeExistingVisualIndicationFromInboxFolderCancellationToken = null;
GlobalEnvironmentMethods.ServiceProcedures.ReleaseComObject(new[] { folderItemsInbox });
totalProcessedInboxMailItems = 0;
NLogMethods.GetInstance().WriteWarnLog("[ADDITIONALVISUALINDICATIONSETTINGSFORM -> RUNREMOVEEXISTINGVISUALINDICATION()]: The task was cancelled by user.");
return;
}
else
{
Outlook.MailItem mailItemObjectInbox = null;
mailItemObjectInbox = folderItemsInbox[i] as Outlook.MailItem;
if (mailItemObjectInbox != null)
{
if (mailItemObjectInbox.Class == Outlook.OlObjectClass.olMail)
{
bool mailItemChanged = false;
if (mailItemObjectInbox.Categories != null)
{
if (mailItemObjectInbox.Categories.Contains(AddInEnvironmentMethods.CRM_CATEGORY_NAME.ToLower()) || mailItemObjectInbox.Categories.Contains(AddInEnvironmentMethods.CRM_CATEGORY_NAME.ToUpper()))
{
//When mail item has many categories, our need remove from string only our "CRM" category, other categories, include char "," will be applied correctly by Outlook automatically;
//i.e. if mail item has 3 cat.: Green category, CRM, Yellow category -> after removing "CRM" cat. category text was: Green category, , Yellow category, but don't worry, Outlook will apply this string absolutely correctly;
//Note: Mail item categories is case sensitive;
mailItemChanged = true;
if (mailItemObjectInbox.Categories.Contains(AddInEnvironmentMethods.CRM_CATEGORY_NAME.ToLower()))
{
mailItemObjectInbox.Categories = mailItemObjectInbox.Categories.Remove(mailItemObjectInbox.Categories.IndexOf(AddInEnvironmentMethods.CRM_CATEGORY_NAME.ToLower()), AddInEnvironmentMethods.CRM_CATEGORY_NAME.Length);
}
else if (mailItemObjectInbox.Categories.Contains(AddInEnvironmentMethods.CRM_CATEGORY_NAME.ToUpper()))
{
mailItemObjectInbox.Categories = mailItemObjectInbox.Categories.Remove(mailItemObjectInbox.Categories.IndexOf(AddInEnvironmentMethods.CRM_CATEGORY_NAME.ToUpper()), AddInEnvironmentMethods.CRM_CATEGORY_NAME.Length);
}
}
}
if (mailItemObjectInbox.UserProperties.Find(AddInEnvironmentMethods.CRM_COLUMN_NAME) != null)
{
mailItemChanged = true;
mailItemObjectInbox.UserProperties.Find(AddInEnvironmentMethods.CRM_COLUMN_NAME).Delete();
}
if (mailItemChanged)
{
mailItemObjectInbox.Save();
}
GlobalEnvironmentMethods.ServiceProcedures.ReleaseComObject(new[] { mailItemObjectInbox });
}
else
{
GlobalEnvironmentMethods.ServiceProcedures.ReleaseComObject(new[] { mailItemObjectInbox });
}
}
}
}
}
else
{
GlobalEnvironmentMethods.ServiceProcedures.ReleaseComObject(new[] { folderItemsInbox });
}
GlobalEnvironmentMethods.globalProcessingRemovingVisualIndicationTaskState = false;
GlobalEnvironmentMethods.removeExistingVisualIndicationFromInboxFolderCancellationToken.Dispose();
GlobalEnvironmentMethods.removeExistingVisualIndicationFromInboxFolderCancellationToken = null;
NLogMethods.GetInstance().WriteInfoLog("[ADDITIONALVISUALINDICATIONSETTINGSFORM -> RUNREMOVEEXISTINGVISUALINDICATION()]: Processing " + "\"" + "Inbox" + "\"" + " folder completed.");
}
The count of processing mail items in my Inbox folder = 988 items.
But, sometimes this function catches an exception "Out of memory".
I don't understand why this occurs...
Note: Exception occurred on this line of code:
if (mailItemObjectInbox.Categories != null) //Out of memory ex...
What am I doing incorrect? Thanks
Related
I have this code to delete all contacts from Microsoft Outlook that have a specific user property:
public void PurgePTSContacts(ref long rlCount)
{
int iLastPercent = 0;
rlCount = 0;
try
{
// Get the MAPI namespace object (not sure exactly what this is)
Outlook.NameSpace MAPI = _OutlookApp.GetNamespace("MAPI");
if (MAPI != null)
{
// Now get the default Outlook Contacts folder
Outlook.MAPIFolder oFolder = MAPI.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderContacts);
if (oFolder != null)
{
// Ensure it is a calendar folder. This test is not strictly required.
if (oFolder.DefaultItemType == Outlook.OlItemType.olContactItem)
{
// Get the collect of items from the calendar folder
Outlook.Items oFolderItems = oFolder.Items;
if (oFolderItems != null)
{
int iNumItems = oFolderItems.Count;
// Progress to do
int iItem = 0;
foreach (object oItem in oFolderItems)
{
iItem++;
if(oItem is Outlook.ContactItem )
{
Outlook.ContactItem oContact = (oItem as Outlook.ContactItem);
int iPercent = ((iNumItems - iItem) + 1) * 100 / iNumItems;
if (iPercent >= iLastPercent + 5 || iPercent == 100)
{
iLastPercent = iPercent;
// Show progress
}
Outlook.UserProperties oContactDetails = oContact.UserProperties;
if (oContactDetails != null)
{
if (oContactDetails.Find("xxxxx") != null)
{
oContact.Delete();
rlCount++;
}
}
oContact = null;
}
}
}
}
}
}
}
catch (Exception e)
{
MessageBox.Show(e.ToString());
}
}
Now, what i don't understand is that when I import the contacts, there are 428. But when I delete the contacts, it only does just over 200. Then I have to repeatedly call my PurgePTSContacts method and it purges some more.
With each subsequent call less and less are purged until it is 0.
Why can't I purge all 400x in one function call?
Do not use foreach when you are modifying the collection - use a down loop:
for (int i = FolderItems.Count; i >= 1; i--)
{
object oItem = FolderItems[i];
Outlook.ContactItem oContact = (oItem as Outlook.ContactItem);
if (oContact != null)
{
...
Marshal.ReleaseComObject(oContact);
}
Marshal.ReleaseComObject(oItem);
}
UPDATE. Per OP request, an example that shows how to delete all items in a folder using Redemption (I am its author):
RDOSession session = new RDOSession();
session.MAPIOBJECT = MAPI.MAPIOBJECT; //an instance of the Namespace object from your snippet above
RDOFolder rFolder = session.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderContacts); //or you can use RDOSession.GetFolderFromID if you already have an Outlook.MAPIFolder
rFoldder.EmptyFolder ();
If you want to only delete contacts (but not distribution lists), you can retreive their entry ids first using RDOFolder.Items.MAPITable.ExecSQL("SELECT EntryID from Folder where MessageClass LIKE 'IPM.Contact%' ") (ExecSQL returns an instance of the ADODB.Recordset COM object), use it to to build an array of entry ids, and pass it to RDOFolder.Items.RemoveMultiple().
To create an instance of the RDOSession object, you can also use the RedemptionLoader class if you don't want to register redemption.dll in the registry using regsvr32.exe (you can just place redemption.dll alongside your executable).
latly i've been struggeling with getting and setting data from/to UI-Objects.
I know it's possible to do this from a BackgroundWorker-Thread, by using Invoking. Sadly i've only find on invoking method and it worked fine for setting labels and a bunch of other stuff, but it failed when it came to DataGridViews, Combo- and TextBoxes. This is the invoking "code" i'm talking about:
this.uiObject.Invoke((MethodInvoker)delegate
{
this.uiObject.Text = "Hello World";//Setting Label Text from BackgroundWorker
});
As i said i tried using this on the following code, but it didn't work.
private void loadPlaylists()
{
this.playlistGrid.Rows.Clear();//Invoke on a DataGridView
string filePath = this.genrePath + this.sortBox.SelectedItem.ToString() + ".txt";
if (File.Exists(filePath) && File.ReadAllText(filePath) != "")
{
using (StreamReader sr = new StreamReader(filePath))
{
bool first = false;
string line = "";
while ((line = sr.ReadLine()) != null)
{
if (first && line != "")
{
string[] split = line.Split(new string[] { " // " }, StringSplitOptions.None);
FullPlaylist playList = spotify.GetPlaylist(split[1], split[0]);
this.playlistGrid.Rows.Add(playList.Name, playList.Owner.Id);//Invoke on a DataGridView
}
if (line != "")
first = true;
}
}
}
}
private void loadItems(bool newItem = false, bool deletedItem = false, string name = "")
{
this.sortBox.Items.Clear();//Invoke on a ComboBox
DirectoryInfo dir = new DirectoryInfo(this.genrePath);
foreach (var file in dir.GetFiles("*.txt"))
{
string[] split = file.Name.Split(new string[] { ".txt" }, StringSplitOptions.None);
this.sortBox.Items.Add(split[0]);//Invoke on a ComboBox
}
if (newItem)
{
this.sortBox.SelectedItem = name;//Invoke on a ComboBox
this.mode = 5;
}
if (deletedItem)
{
if (this.sortBox.Items.Count > 0)//Invoke on a ComboBox
{
this.sortBox.SelectedIndex = 0;//Invoke on a ComboBox
this.mode = 5;
}
else
this.playlistGrid.Rows.Clear();//Invoke on a DataGirdView
}
}
private void addPlaylists()
{
string[] split;
string filePath = "";
if (this.sortBox.SelectedIndex != -1)//Invoke on a ComboBox
{
filePath = this.genrePath + this.sortBox.SelectedItem.ToString() + ".txt";//Invoke on a ComboBox
}
else
{
MetroFramework.MetroMessageBox.Show(this, "Select a valid Category first!",
"Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
if (this.playlistLink.Text != "" && this.playlistLink.Text.Contains("/"))//Invoke on a TextBox
{
if (this.playlistLink.Text.Contains("|"))//Invoke on a TextBox
{
split = this.playlistLink.Text.Split('|');//Invoke on a TextBox
}
else
{
split = new string[1];
split[0] = this.playlistLink.Text;//Invoke on a TextBox
}
for (int j = 0; j < split.Length; j++)
{
string[] split2 = split[j].Split('/');
string id = split2[split2.Length - 1], owner = split2[split2.Length - 3];
FullPlaylist playlist = this.spotify.GetPlaylist(owner, id);
string write = id + " // " + owner + " // " + playlist.Name;
this.changeOrAddLine(filePath, write, write);
}
this.playlistLink.Text = "";//Invoke on a TextBox
this.loadPlaylists();//Call to a Methode, where Invoke is needed
}
else if (!this.playlistLink.Text.Contains("/"))//Invoke on a TextBox
{
//Error
this.playlistLink.Text = "";//Invoke on a TextBox
}
else
{
//Error
}
}
Those are the three methods i'm calling from the BackgrounddWorker. The code is fine and works outside of the BackgroundWorker. I marked all Lines where Invoking(if it's even needed) should be used, because there it's doing something with an UI-Project. I hope someone of you guys is able to show me how to do this. I'm not expecting you to take my code and add the fixes, just a overall example on how to do this should work aswell. Thanks in advance!
Anywhere you affect a UI control, wrap that logic in an invoke call. So for instance, rather than:
this.playlistGrid.Rows.Clear(); // No
Do this:
uiObject.Invoke(() => this.playlistGrid.Rows.Clear()); // Yes
This effectively sends a message to the UI thread to perform that operation when the time is right in the UI's message loop. If you wrap every interaction with your UI controls in an invoke like this, the errors should cease, but beware tht you are blocking the background thread while you wait for the result of the Invoke. Be sure the UI thread doesn't in-turn wait for the background thread or you'll have deadlock. You can avoid this by using BeginInvoke but you do need to be aware of the fact that the operation may not happen immediately. For instance, if you wish to read something from a control, not do this:
string localValue = null;
uiObject.BeginInvoke(() => { localValue = this.sortBox.SelectedItem.ToString(); });
// should NOT assume you can use localValue here
You generally are better off gathering all data from your controls in your UI layer before invoking your async action and passing it to the background process. For example:
private void loadPlaylists(string selectedItem)
We have the below code to read the attachments using EWS.
FindItemsResults<Item> foundItems = service.FindItems(FolderId, new ItemView(1000));
var orderItems = from list in foundItems
orderby list.DateTimeReceived
select list;
foreach (EmailMessage item in orderItems)
{
item.Load();//SARANYA
EmailMessage foundEmail = (EmailMessage)item;
EmailMessage message = EmailMessage.Bind(service, new ItemId(item.Id.ToString()), new PropertySet(BasePropertySet.FirstClassProperties, ItemSchema.Attachments, ItemSchema.Body));
if (message.Attachments.Count > 0)
{
FileAttachment[] attachments = null;
attachments = new FileAttachment[message.Attachments.Count];
foreach (Attachment attachment in message.Attachments)
{
try
{
if (attachment is FileAttachment)
{
FileAttachment fileAttachment = attachment as FileAttachment;
// System.Threading.Thread.Sleep(2000);
fileAttachment.Load();
byte[] FolloupMailFileAttachmentContentBytes = fileAttachment.Content;
bool isScreenshot = false;
string ScreenfileName = "";
for (int i = 0; i < imgSrcs.Count; i++)
{
if (imgSrcs[i].ToString() == fileAttachment.Name.ToString())
{
isScreenshot = true;
if (!imgSrcs[i].ToString().Contains(".png"))
ScreenfileName = "cid:" + imgSrcs[i].ToString() + ".png";
else
ScreenfileName = "cid:" + imgSrcs[i].ToString();
break;
}
}
if (FolloupMailFileAttachmentContentBytes != null)
{
if (isScreenshot && RemoveSuccess == 1)
{
InsertMailItemAttachment(ScreenfileName, FolloupMailFileAttachmentContentBytes, caseid);
}
else
InsertMailItemAttachment(fileAttachment.Name.ToString(), FolloupMailFileAttachmentContentBytes, caseid);
}
}
else if (attachment is ItemAttachment)
{
item.Move(unreadmailFolder.Id);
}
}
catch (Exception exe)
{
if (!ReadMoved)
{
item.Move(readmailFolder.Id);
ReadMoved = true;
}
logfile.HandleError(exe, "Attachment Exception \n\nEmailbox - " + EMailBox + "\n\nEmail Subject - " + strSubject + " \n - Could not load the attachment (" + attachment.Name.ToString() + ")");
}
}
}
}
Above code is working when I provide thread.sleep() before fileattachment.load(). when thread.sleep is removed, I get the below exception.
Error Source : Microsoft.Exchange.WebServices
Target Site : Void InternalThrowIfNecessary()
System Message : The specified object was not found in the store.
Stack Trace : at Microsoft.Exchange.WebServices.Data.ServiceResponse.InternalThrowIfNecessary()
at Microsoft.Exchange.WebServices.Data.ServiceResponse.ThrowIfNecessary()
at Microsoft.Exchange.WebServices.Data.MultiResponseServiceRequest`1.Execute()
at Microsoft.Exchange.WebServices.Data.ExchangeService.InternalGetAttachments(IEnumerable`1 attachments, Nullable`1 bodyType, IEnumerable`1 additionalProperties, ServiceErrorHandling errorHandling)
at Microsoft.Exchange.WebServices.Data.ExchangeService.GetAttachment(Attachment attachment, Nullable`1 bodyType, IEnumerable`1 additionalProperties)
at Microsoft.Exchange.WebServices.Data.Attachment.InternalLoad(Nullable`1 bodyType, IEnumerable`1 additionalProperties)
at Microsoft.Exchange.WebServices.Data.Attachment.Load()
at EMT_Office365_MailFetch_Scheduler.Program.FindEmail(Object threadState) in
Experts, Please provide your valuable inputs
Your logic doesn't look correct eg
item.Move(unreadmailFolder.Id);
Should not be inside the Foreach loop if you want to move the message at the end just use a Flag and process it once you have finished processing the attachments(you logic doesn't work if you have two ItemAttachment this would execute twice). The reason sleep is working is mostly likely an Aysnc operation is completing once you have executed the move. Thats why this should be outside of the foreach attachment loop
I try to write a Webservice that can access to my exchange-server and search for names, companys and cities. At the moment i get the names like this:
ExchangeServiceBinding esb = new ExchangeServiceBinding();
esb.UseDefaultCredentials = true;
// Create the ResolveNamesType and set
// the unresolved entry.
ResolveNamesType rnType = new ResolveNamesType();
rnType.ReturnFullContactData = true;
rnType.UnresolvedEntry = "searchname";
// Resolve names.
ResolveNamesResponseType resolveNamesResponse
= esb.ResolveNames(rnType);
ArrayOfResponseMessagesType responses
= resolveNamesResponse.ResponseMessages;
// Check the result.
if (responses.Items.Length > 0 && responses.Items[0].ResponseClass != ResponseClassType.Error)
{
ResolveNamesResponseMessageType responseMessage = responses.Items[0] as
ResolveNamesResponseMessageType;
// Display the resolution information.
ResolutionType[] resolutions = responseMessage.ResolutionSet.Resolution;
foreach (ResolutionType resolution in resolutions)
{
Console.WriteLine(
"Name: " +
resolution.Contact.DisplayName
);
Console.WriteLine(
"EmailAddress: " +
resolution.Mailbox.EmailAddress
);
if (resolution.Contact.PhoneNumbers != null)
{
foreach (
PhoneNumberDictionaryEntryType phone
in resolution.Contact.PhoneNumbers)
{
Console.WriteLine(
phone.Key.ToString() +
" : " +
phone.Value
);
}
}
Console.WriteLine(
"Office location:" +
resolution.Contact.OfficeLocation
);
Console.WriteLine("\n");
}
}
But anybody know how i can serach for Propertys like Company and Street?
EWS only has limited Directory operations if your using OnPrem Exchange then the easiest way to do this is just use LDAP and lookup Active Directory directly. The resolveName operation is meant to be used to resolve a partial number and doesn't work with any other properties. If you have Exchange 2013 then there is the FindPeople operation http://msdn.microsoft.com/en-us/library/office/jj191039(v=exchg.150).aspx which supports using a QueryString which should work if those properties are indexed. eg
EWSProxy.FindPeopleType fpType = new EWSProxy.FindPeopleType();
EWSProxy.IndexedPageViewType indexPageView = new EWSProxy.IndexedPageViewType();
indexPageView.BasePoint = EWSProxy.IndexBasePointType.Beginning;
indexPageView.Offset = 0;
indexPageView.MaxEntriesReturned = 100;
indexPageView.MaxEntriesReturnedSpecified = true;
fpType.IndexedPageItemView = indexPageView;
fpType.ParentFolderId = new EWSProxy.TargetFolderIdType();
EWSProxy.DistinguishedFolderIdType Gal = new EWSProxy.DistinguishedFolderIdType();
Gal.Id = EWSProxy.DistinguishedFolderIdNameType.directory;
fpType.QueryString = "Office";
fpType.ParentFolderId.Item = Gal;
EWSProxy.FindPeopleResponseMessageType fpm = null;
do
{
fpm = esb.FindPeople(fpType);
if (fpm.ResponseClass == EWSProxy.ResponseClassType.Success)
{
foreach (EWSProxy.PersonaType PsCnt in fpm.People)
{
Console.WriteLine(PsCnt.EmailAddress.EmailAddress);
}
indexPageView.Offset += fpm.People.Length;
}
else
{
throw new Exception("Error");
}
} while (fpm.TotalNumberOfPeopleInView > indexPageView.Offset);
Cheers
Glen
I'm using MailSystem.NET and trying to delete a message from the server. The problem is the IndexOnServer property is 0, and I get the following error:
Command "store 0 +flags.silent (\Deleted)" failed : 121031084812790 BAD Error in IMAP command STORE: Invalid messageset
Here's my code:
Imap4Client client = new Imap4Client();
client.Connect(account.Server, account.Username, account.Password);
var inbox = client.SelectMailbox("Inbox");
MessageCollection messages = inbox.SearchParse("SINCE " + DateTime.Now.AddHours(-hours).ToString("dd-MMM-yyyy"));
foreach (Message newMessage in messages)
{
inbox.DeleteMessage(newMessage.IndexOnServer, true);
}
How do I get the correct index of the message so I can delete it?
Edit:
The problem with the suggestions using the standard 1-based loop is that the counter index won't be in sync with the message index, since in my case I'm searching to retrieve a specific subset of messages only (as I understand it).
Thank you.
You can try deleting by the UID which should be more reliable and unique to each message. This has worked well for me in the past.
Edit: Since deleting the message causes all the indexes to shift down by one, you can use two separate counters. One to keep track of when you have iterated through the entire box (messagesLeft) and the other will keep track of the current message index which will be decreased by 1 if a message is deleted (since it would move up one place in line).
Mailbox box = client.AllMailboxes["inbox"];
Fetch fetch = box.Fetch;
int messagesLeft = box.Count;
int msgIndex = 0;
while (messagesLeft > 0)
{
msgIndex++;
messagesLeft--;
Message email = fetch.MessageObject(msgIndex);
if (criteria)
{
box.UidDeleteMessage(fetch.Uid(msgIndex), true);
msgIndex--;
}
}
In response to your comment, here is a fuller example of how you can use the UID for deletion without being concerned with the numeric position / index.
class Email
{
int UID { get; set; }
DateTime Sent { get; set; }
public string Body { get; set; }
// put whichever properties you will need
}
List<Email> GetEmails(string mailbox);
{
Mailbox box = client.AllMailboxes[mailbox];
Fetch fetch = box.Fetch;
List<Email> list = new List<Email>();
for (int x = 1; x <= box.MessageCount; x++)
{
Message msg = fetch.MessageObject(x);
list.Add(new Email() { } // set properties from the msg object
}
return list;
}
void DeleteEmail(Email email, string mailbox)
{
Mailbox box = client.AllMailboxes[mailbox];
box.UidDeleteMessage(email.Uid, true);
}
static void Main()
{
List<Email> emails = GetEmails("inbox");
emails = emails.Where(email => email.Sent < DateTime.Now.AddHours(-hours))
foreach (Email email in emails)
DeleteEmail(email);
}
This is official example from documantation.
instead of inbox.search("all") change it to your search query, etc.
http://mailsystem.codeplex.com/discussions/269304
//action_id is the Message.MessageId of the email
//action_flag is the Gmail special folder to move to (Trash)
//copying to Trash removes the email from the inbox, but it can't be moved back once done, even from the web interface
public static void move_msg_to(string action_id, string action_flag)
{
Imap4Client imap = new Imap4Client();
imap.ConnectSsl("imap.gmail.com", 993);
imap.Login("heythatsme#gmail.com", "heythatsmypassword");
imap.Command("capability");
Mailbox inbox = imap.SelectMailbox("inbox");
int[] ids = inbox.Search("ALL");
if (ids.Length > 0)
{
Message msg = null;
for (var i = 0; i < ids.Length; i++)
{
msg = inbox.Fetch.MessageObject(ids[i]);
if (msg.MessageId == action_id)
{
imap.Command("copy " + ids[i].ToString() + " [Gmail]/" + action_flag);
break;
}
}
}
}
just add 1 to the message index.
foreach (Message newMessage in messages)
{
inbox.DeleteMessage(newMessage.IndexOnServer + 1, true);
}
Try deleting last message first then second last and then come to delete first message.
IF you delete and expunge have been set then message number changes.