Collectives™ on Stack Overflow

Find centralized, trusted content and collaborate around the technologies you use most.

Learn more about Collectives

Teams

Q&A for work

Connect and share knowledge within a single location that is structured and easy to search.

Learn more about Teams

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

RFC 4549 Sec 4.3.1 gives a way to get the new messages. You can add a variable called LastSeenUid and update it everytime you fetch messages. Then your next fetch will be from the LastSeenUid+1 to the newest message. – Samuel Mar 27, 2019 at 22:05

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);
                    Thanks for your reply, but by using the suggested method for QRESYNC, i need to retrieve all messages ids stored in local db?
    – ʞᴉɯ
                    Jan 17, 2019 at 18:14
                    So, if the mailbox contains 100.000 messages, i need to pass all of them ids to the command?
    – ʞᴉɯ
                    Jan 17, 2019 at 18:26
            

    Thanks for contributing an answer to Stack Overflow!

    • Please be sure to answer the question. Provide details and share your research!

    But avoid

    • Asking for help, clarification, or responding to other answers.
    • Making statements based on opinion; back them up with references or personal experience.

    To learn more, see our tips on writing great answers.