Does getting messages from Exchange mark them as read? Can I have two processes monitoring one inbox?

+1 vote
asked Feb 19 by 3P (270 points)

Hi.

Does reading messages from Exchange mark them as read?
Is it possible to have two processes monitoring same inbox and not interfering with each other? I mean I need to be sure they will not pull the same messages if they run at exactly same time.

Applies to: Rebex Secure Mail

1 Answer

+1 vote
answered Feb 19 by Lukas Matyska (55,470 points)

Does reading messages from Exchange mark them as read?

I need to be sure they will not pull the same messages if they run at exactly same time.

It is not possible in no mail protocol (POP3, IMAP, EWS). To do this, the server would be capable of operations like "atomic operation for downloading a message and deleting it" or "atomic operation for downloading an unread message and marking it as read".

So you have to synchronize the two processes by yourself. You can use for example Mutex.

commented Mar 1 by 3P (270 points)
I am connecting to IMAP server to download e-mails. Whenever I connect to this server it always returns me same e-mails. So looks like they are not marked as read. Is this specific to this IMAP server? Maybe it doesn't adhere to some standard? I'm using antespi/docker-imap-devel (https://github.com/antespi/docker-imap-devel) server running in docker because I  want to write automated tests to execute them in my CI.

I will check how it works with other IMAP server later.
commented Mar 1 by Lukas Matyska (55,470 points)
By the RFC 3501 the \Seen flag is implicitly set when client requests BODY part.
This can be disabled by .PEEK:

  BODY.PEEK[<section>]<<partial>>
         An alternate form of BODY[<section>] that does not implicitly set the \Seen flag.

The BODY is requested when downloading messages from IMAP server by Imap.GetMessage/s, Imap.GetMailMessage or Imap.GetMimeMessage methods. It can be also requested by Imap.GetMessageList or Imap.GetMessageInfo methods if ImapListFields.Body argument is specified.

If your server does not set \Seen flag when using one of the mentioned methods, it is violation of the RFC. Please ensure, that you Imap.Settings.UsePeekForGetMessage = true.
You can check what is requested by the client in the communication log, which can be created as described at https://www.rebex.net/kb/logging/
Locate lines like "Command: R00123 UID FETCH 123 (UID BODY[])" or "... UID BODY.PEEK[])"
commented Mar 1 by 3P (270 points)
This is my code:
var imap = new Imap
                {
                    Settings = { SslServerCertificateVerifier = CertificateVerifier.AcceptAll }
                };
                imap.LogWriter = new Rebex.FileLogWriter(@"c:\temp\log.txt", Rebex.LogLevel.Debug);

                imap.Connect(mailServer.Hostname, mailServer.Port, SslMode.None);
                imap.Login("master@mail.localserver.com", "xxx", ImapAuthentication.Auto);
                imap.SelectFolder("Inbox"); // TODO: Setting

                ImapMessageCollection list = imap.Search(ImapListFields.FullHeaders, ImapSearchParameter.Unread);


                var result = list.Select(m => imap.GetMailMessage(m.SequenceNumber));
                imap.Disconnect();

                return result;

it is triggered every few seconds. It downloads same e-mails every time.

Here is log:

2019-03-01 16:07:15.177 Opening log file.
2019-03-01 16:07:15.179 INFO FileLogWriter(1)[7] Info: Assembly: Rebex.Common 2018 R4 for .NET Standard 1.5/1.6
2019-03-01 16:07:15.183 INFO FileLogWriter(1)[7] Info: Platform: Windows 10.0.16299 64-bit; CLR: .NET Core 2.x (27317.07)
2019-03-01 16:07:15.184 DEBUG FileLogWriter(1)[7] Info: Culture: en; windows-1252
2019-03-01 16:07:15.213 DEBUG Imap(1)[7] Info: State changed from 'Disconnected' to 'Connecting'.
2019-03-01 16:07:15.213 INFO Imap(1)[7] Info: Connecting to localhost:143 using Imap.
2019-03-01 16:07:15.213 INFO Imap(1)[7] Info: Assembly: Rebex.Imap 2018 R4 for .NET Standard 1.5/1.6 (Trial)
2019-03-01 16:07:15.213 INFO Imap(1)[7] Info: Platform: Windows 10.0.16299 64-bit; CLR: .NET Core 2.x (27317.07)
2019-03-01 16:07:15.215 DEBUG Imap(1)[7] Info: Culture: en; windows-1252
2019-03-01 16:07:15.216 INFO Imap(1)[7] Info: Connecting to localhost.
2019-03-01 16:07:15.229 DEBUG Imap(1)[7] Proxy: Resolving 'localhost'.
2019-03-01 16:07:15.240 DEBUG Imap(1)[7] Proxy: Connecting to 127.0.0.1:143 (no proxy).
2019-03-01 16:07:15.243 DEBUG Imap(1)[7] Info: Connection succeeded.
2019-03-01 16:07:15.252 DEBUG Imap(1)[7] Info: State changed from 'Connecting' to 'Reading'.
2019-03-01 16:07:15.256 INFO Imap(1)[7] Response: * OK IMAPrev1
2019-03-01 16:07:15.268 DEBUG Imap(1)[7] Info: State changed from 'Reading' to 'Ready'.
2019-03-01 16:07:15.275 DEBUG Imap(1)[7] Info: State changed from 'Ready' to 'Sending'.
2019-03-01 16:07:15.281 INFO Imap(1)[7] Command: R00001 CAPABILITY
2019-03-01 16:07:15.281 DEBUG Imap(1)[7] Info: State changed from 'Sending' to 'Reading'.
2019-03-01 16:07:15.281 INFO Imap(1)[7] Response: * CAPABILITY IMAP4 IMAP4rev1 CHILDREN IDLE QUOTA SORT ACL NAMESPACE RIGHTS=texk
2019-03-01 16:07:15.281 INFO Imap(1)[7] Response: R00001 OK CAPABILITY completed
2019-03-01 16:07:15.281 DEBUG Imap(1)[7] Info: State changed from 'Reading' to 'Ready'.
2019-03-01 16:07:15.290 DEBUG Imap(1)[7] Info: State changed from 'Ready' to 'Sending'.
2019-03-01 16:07:15.291 INFO Imap(1)[7] Command: LOGIN master@mail.localserver.com **********
2019-03-01 16:07:15.291 DEBUG Imap(1)[7] Info: State changed from 'Sending' to 'Reading'.
2019-03-01 16:07:15.293 INFO Imap(1)[7] Response: R00002 OK LOGIN completed
2019-03-01 16:07:15.293 DEBUG Imap(1)[7] Info: State changed from 'Reading' to 'Ready'.
2019-03-01 16:07:15.295 DEBUG Imap(1)[7] Info: State changed from 'Ready' to 'Sending'.
2019-03-01 16:07:15.296 INFO Imap(1)[7] Command: R00003 STATUS Inbox (UNSEEN)
2019-03-01 16:07:15.296 DEBUG Imap(1)[7] Info: State changed from 'Sending' to 'Reading'.
2019-03-01 16:07:15.296 INFO Imap(1)[7] Response: * STATUS "Inbox" (UNSEEN 2)
2019-03-01 16:07:15.296 INFO Imap(1)[7] Response: R00003 OK Status completed
2019-03-01 16:07:15.296 DEBUG Imap(1)[7] Info: State changed from 'Reading' to 'Ready'.
2019-03-01 16:07:15.296 DEBUG Imap(1)[7] Info: State changed from 'Ready' to 'Sending'.
2019-03-01 16:07:15.297 INFO Imap(1)[7] Command: R00004 SELECT Inbox
2019-03-01 16:07:15.297 DEBUG Imap(1)[7] Info: State changed from 'Sending' to 'Reading'.
2019-03-01 16:07:15.297 INFO Imap(1)[7] Response: * 2 EXISTS
2019-03-01 16:07:15.297 INFO Imap(1)[7] Response: * 0 RECENT
2019-03-01 16:07:15.297 INFO Imap(1)[7] Response: * FLAGS (\Deleted \Seen \Draft \Answered \Flagged)
2019-03-01 16:07:15.297 INFO Imap(1)[7] Response: * OK [UIDVALIDITY 1540303896] current uidvalidity
2019-03-01 16:07:15.297 INFO Imap(1)[7] Response: * OK [UNSEEN 60] unseen messages
2019-03-01 16:07:15.297 INFO Imap(1)[7] Response: * OK [UIDNEXT 62] next uid
2019-03-01 16:07:15.297 INFO Imap(1)[7] Response: * OK [PERMANENTFLAGS (\Deleted \Seen \Draft \Answered \Flagged)] limited
2019-03-01 16:07:15.297 INFO Imap(1)[7] Response: R00004 OK [READ-WRITE] SELECT completed
2019-03-01 16:07:15.297 DEBUG Imap(1)[7] Info: State changed from 'Reading' to 'Ready'.
2019-03-01 16:07:15.314 DEBUG Imap(1)[7] Info: State changed from 'Ready' to 'Sending'.
2019-03-01 16:07:15.314 INFO Imap(1)[7] Command: R00005 UID SEARCH UNSEEN
2019-03-01 16:07:15.315 DEBUG Imap(1)[7] Info: State changed from 'Sending' to 'Reading'.
2019-03-01 16:07:15.315 INFO Imap(1)[7] Response: * SEARCH 60 61
2019-03-01 16:07:15.315 INFO Imap(1)[7] Response: R00005 OK UID completed
2019-03-01 16:07:15.315 DEBUG Imap(1)[7] Info: State changed from 'Reading' to 'Ready'.
2019-03-01 16:07:15.320 DEBUG Imap(1)[7] Info: State changed from 'Ready' to 'Sending'.
2019-03-01 16:07:15.320 INFO Imap(1)[7] Command: R00006 UID FETCH 60,61 (UID RFC822.SIZE FLAGS INTERNALDATE BODY.PEEK[HEADER])
2019-03-01 16:07:15.320 DEBUG Imap(1)[7] Info: State changed from 'Sending' to 'Reading'.
2019-03-01 16:07:15.321 INFO Imap(1)[7] Response: * 1 FETCH (UID 60 RFC822.SIZE 688 FLAGS () INTERNALDATE " 3-Dec-2018 15:52:33 +0100" BODY[HEADER] {544}
2019-03-01 16:07:15.323 INFO Imap(1)[7] Response: ...544 bytes...
2019-03-01 16:07:15.323 INFO Imap(1)[7] Response: )
2019-03-01 16:07:15.324 INFO Imap(1)[7] Response: * 2 FETCH (UID 61 RFC822.SIZE 590 FLAGS () INTERNALDATE " 4-Dec-2018 12:12:43 +0100" BODY[HEADER] {625}
2019-03-01 16:07:15.325 INFO Imap(1)[7] Response: ...625 bytes...
2019-03-01 16:07:15.325 INFO Imap(1)[7] Response: )
2019-03-01 16:07:15.325 INFO Imap(1)[7] Response: R00006 OK UID completed
2019-03-01 16:07:15.325 DEBUG Imap(1)[7] Info: State changed from 'Reading' to 'Ready'.
2019-03-01 16:07:15.401 DEBUG Imap(1)[7] Info: State changed from 'Ready' to 'Sending'.
2019-03-01 16:07:15.401 INFO Imap(1)[7] Command: R00007 LOGOUT
2019-03-01 16:07:15.401 DEBUG Imap(1)[7] Info: State changed from 'Sending' to 'Reading'.
2019-03-01 16:07:15.401 INFO Imap(1)[7] Response: * BYE Have a nice day
2019-03-01 16:07:15.401 INFO Imap(1)[7] Response: R00007 OK Logout completed
2019-03-01 16:07:15.401 DEBUG Imap(1)[7] Info: State changed from 'Reading' to 'Ready'.
2019-03-01 16:07:15.403 DEBUG Imap(1)[7] Info: State changed from 'Ready' to 'Disconnected'.

Am I doing something wrong? I'm checking this using second email server.
commented Mar 4 by Lukas Matyska (55,470 points)
The code you provided leads to System.InvalidOperationException: Not connected to the server.

The problem is that the list.Select(m => ...) is deferred execution, which means the lambda in Select() call is evaluated when the first used (lazy evaluation). Please read more at https://www.davidhaney.io/linq-and-deferred-execution/

To fix the problem, evaluate the expression before disconnecting the client, e.g. list.Select(m => ...).ToList()

I have also one suggestion:
Searching with ImapListFields.FullHeaders is really not necessary, since you immediately download the whole message.

Final solution can look like this. Change:
  var list = imap.Search(ImapListFields.FullHeaders, ImapSearchParameter.Unread);
  var result = list.Select(m => imap.GetMailMessage(m.SequenceNumber));
to:
  var list = imap.Search(ImapListFields.UniqueId, ImapSearchParameter.Unread);
  var result = list.Select(m => imap.GetMailMessage(m.UniqueId)).ToList();
commented Mar 4 by 3P (270 points)
Yeah I know what deferred-execution is I just didn't notice I don't resolve those emails -facepalm.

Of course now it works.
...