Get new emails using IMAP

This article describes how to receive new email messages using Mail.dll .NET IMAP library.

In many situations you can relay on IMAP server to keep track which messages are unseen and just download unseen messages from IMAP server. However when user interaction or other program connecting and downloading emails may remove UNSEEN flag from the message, you may be forced to manually track which messages where already downloaded and which are new.

You could of course download all Unique IDs (UIDs) of the messages that are in the mailbox, but there is a more elegant way.

Unique IDs (UIDs) in IMAP have some very interesting feature – they are assigned in incremental order. This means that new messages always have higher UIDs than old ones.

This is the reason you only need to remember the newest (greatest) UID that was downloaded during a previous session (connection to the IMAP server). On the next session you need to search IMAP for UIDs that are greater than the one remembered.

First we need to load largest uid from the previous session and connect to the IMAP server. In this exmaple we select INBOX, but you can use Select method to select a different folder.

// C#

long? largestUID = LoadLargestFromPreviousRun();

using (Imap imap = new Imap ())
{
    imap.Connect("imap.example.com");  // or ConnectSSL for SSL
    imap.UseBestLogin("user", "password");

    imap.SelectInbox();
' VB.NET

Dim largestUID As System.Nullable(Of Long) = LoadLargestFromPreviousRun()

Using imap As New Imap ()
    imap.Connect("imap.example.com")   ' or ConnectSSL for SSL
    imap.UseBestLogin("user", "password")

    imap.SelectInbox()

Next step is to select folder, download unique ids (UIDs) larger than largestUID value that was stored on the previous run. This way we obtain uids of the new emails.

// C#

List<long> uids;
if (largestUID == null)
{
    uids = imap.GetAll();
}
else
{
    uids = imap.Search().Where(
        Expression.UID(Range.From(largestUID.Value)));
    uids.Remove(largestUID);
}

' VB.NET

Dim uids As List(Of Long)
If largestUID Is Nothing Then
	uids = imap.GetAll()
Else
	uids = imap.Search().Where(Expression.UID(Range.From(largestUID.Value)))
	uids.Remove(largestUID)
End If

You should note uids.Remove in the code above. It is used because Range.From creates a range of uids greater or equal to the specified value and * represents the largest value in the mailbox, so the largest uid is always included.

Finally we’ll iterate through the UID list and use GetMessageByUID method to download each message from the server. You can use MailBuilder class to parse those email messages, extract attachments and process them anyway you like. The last step is to save the last UID that was processed:

// C#

foreach (long uid in uids)
{
    var eml = imap.GetMessageByUID(uid);
    IMail email = new MailBuilder().CreateFromEml(eml);

    Console.WriteLine(email.Subject);
    Console.WriteLine(email.Text);
   
    SaveLargestUID(uid.Value);
}
' VB.NET

For Each uid As Long In uids
	Dim eml = imap.GetMessageByUID(uid)
	Dim email As IMail = New MailBuilder().CreateFromEml(eml)

	Console.WriteLine(email.Subject)
	Console.WriteLine(email.Text)

	SaveLargestUID(uid.Value)
Next

SaveLargestUID, LoadLargest… methods

You need to write those methods on your own. They should save/load largest uid from/to your preferred storage (such as database, file, registry).

// C#

private void SaveLargestUID(long uid)
{
    // Your code that saves the uid.
}

private long  LoadLargestFromPreviousRun()
{
    // Your code that loads the largest uid (null on the first run).
}
' VB.NET

Private Sub SaveLargestUID(uid As Long)
    ' Your code that saves the uid.
End Sub

Private Function LoadLargestFromPreviousRun() As Long
    ' Your code that loads the largest uid (null on the first run).
End Sub

UIDs across sessions – UIDValidity

It is advised for IMAP servers to not change UIDs between sessions. There are however situations when server may invalidate all UIDs that were in the mailbox previously.

Consider a user that deletes a folder (mailbox) including all its messages and creates new one with exactly the same name. All uids that were downloaded previously are no longer valid – they simply don’t exist in this folder anymore.

You can recognize such situation by comparing FolderStatus.UIDValidity value. If the FolderStatus.UIDValidity number hasn’t changed, then the UIDs are still valid. Below is the sample showing how to print UIDValidity:

// C#

FolderStatus status = client.SelectInbox();
Console.WriteLine(status.UIDValidity);

' VB.NET

Dim status As FolderStatus = client.SelectInbox()
Console.WriteLine(status.UIDValidity)

Entire samples

Below you can find full samples in both C# and VB.NET:

// C#

using Limilabs.Mail;
using Limilabs.Client.IMAP;

internal class LastRun
{
    public long UIDValidity { get; set; }
    public long LargestUID { get; set; }
}

private static void Main(string[] args)
{
    LastRun last = LoadPreviousRun();

    using (Imap imap = new Imap())
    {
        imap.Connect("imap.example.com"); // or ConnectSSL for SSL
        imap.UseBestLogin("user", "password");

        FolderStatus status = imap.SelectInbox();

        List<long> uids;
        if (last == null || last.UIDValidity != status.UIDValidity)
        {
            uids = imap.GetAll();
        }
        else
        {
            uids = imap.Search().Where(
                Expression.UID(Range.From(last.LargestUID)));
            uids.Remove(last.LargestUID);
        }

        foreach (long uid in uids)
        {
            var eml = imap.GetMessageByUID(uid);
            IMail email = new MailBuilder()
                .CreateFromEml(eml);

            Console.WriteLine(email.Subject);
            Console.WriteLine(email.Text);

            LastRun current = new LastRun
                                  {
                                      UIDValidity = status.UIDValidity, 
                                      LargestUID = uid
                                  };
            SaveThisRun(current);
        }
        imap.Close();
    }
}
' VB.NET

Imports Limilabs.Mail
Imports Limilabs.Client.IMAP

Friend Class LastRun
	Public Property UIDValidity () As Long
		Get
			Return m_UIDValidity 
		End Get
		Set
			m_UIDValidity = Value
		End Set
	End Property
	Private m_UIDValidity As Long
	Public Property LargestUID() As Long
		Get
			Return m_LargestUID
		End Get
		Set
			m_LargestUID = Value
		End Set
	End Property
	Private m_LargestUID As Long
End Class


Private Shared Sub Main(args As String())
    Dim last As LastRun = LoadPreviousRun()

    Using imap As New Imap()
        imap.Connect("imap.example.com")		' or ConnectSSL for SSL
        imap.UseBestLogin("user", "password")

        Dim status As FolderStatus = imap.SelectInbox()

        If last Is Nothing OrElse last.UIDValidity <> status.UIDValidity Then
	        uids = imap.GetAll()
        Else
	        uids = imap.Search().Where(Expression.UID(Range.From(last.LargestUID)))
        	uids.Remove(last.LargestUID)
        End If

        For Each uid As Long In uids
            Dim eml = imap.GetMessageByUID(uid)
            Dim email As IMail = New MailBuilder().CreateFromEml(eml)

            Console.WriteLine(email.Subject)
            Console.WriteLine(email.Text)

            Dim current As New LastRun() With { _
                .UIDValidity = status.UIDValidity, _
                .LargestUID = uid _
            }
            SaveThisRun(current)
        Next
        imap.Close()
    End Using
End Sub

SaveThisRun, LoadPreviousRun methods

You need to write those methods on your own. They should save/load last run data from/to your preferred storage (such as database, file, registry).

// C#

private void SaveThisRun(LastRun run)
{
    // Your code that saves run data.
}

private LastRun LoadPreviousRun()
{
    // Your code that loads last run data (null on the first run).
}
' VB.NET

Private Sub SaveThisRun(uid As LastRun )
    ' Your code that saves run data.
End Sub

Private Function LoadPreviousRun() As LastRun
    ' Your code that loads last run data (null on the first run).
End Function

Tags:    

Questions?

Consider using our Q&A forum for asking questions.

19 Responses to “Get new emails using IMAP”

  1. Matias Says:

    is there a way to know the value of higher uid a folder?
    without traversing all uid and the first execution

  2. Limilabs support Says:

    @Matias,

    To be 100% sure you need to invoke GetAll method and take the last number from the returned list.

  3. Vijay Says:

    How to Get new emails using IMAP with Lesnikowski?
    Is there a way like in Limilabs?

  4. Limilabs support Says:

    @Vijay

    Why don’t you want to use the latest version?
    You can download it at:
    http://www.limilabs.com/mail/download

  5. net123 Says:

    How can I get the function LoadLargestFromPreviousRun()?

  6. Limilabs support Says:

    @net123

    You need to write this method.
    This method should load the largest uid from your preferred storage (file, database), that was saved, during previous application run.
    You should save this uid using SaveLargestUID method – also created by you.

  7. net123 Says:

    can u provide this methods?

  8. Limilabs support Says:

    @net123,

    No, we can’t, it would be impossible to provide implementations for every possible scenario.

    Those methods depend on your infrastructure, database type, version, and what you want to do exactly. Maybe you don’t even require a database. You need to create those methods yourself.

  9. Nick Says:

    Hi,

    We use following code:

    uids = imap.Search().Where(Expression.UID(Range.From(last.LargestUID + 1)

    When there are no new mails, this expression returns uids={ LargestUID }, and not null? Why?

  10. Limilabs support Says:

    @Nick,

    This query should return empty list (not null), when there are no new emails. Please make sure that ‘+1’ is actually there (off-by-one errors are quite common).

    If the problem persists, I’m afraid this may be the server side problem, and the only way would be to create a simple workaround in your code.

  11. ML Says:

    Is there any way to select the top 100 emails for example. I’m having to load in thousands of unread emails and it’s very slow at the moment

  12. Limilabs support Says:

    @ML,

    Search(Flag.Unseen) itself is very fast, it just returns UID list (list of longs).
    This list is sorted from oldest to newest. You can use Linq to get first, last, or any 100 uids you plan to download.

    The other option would be to download only most common email properties (from, to, subject, attachment count and names).

  13. Semir Turgay Says:

    I try to run that code. But I have problem: when I run the code I always get the last mail from my inbox.

  14. Limilabs support Says:

    @Semir

    Are you sure you are adding +1 (last.LargestUID + 1) when you are searching?
    If so, than most likely your server is performing search incorrectly.
    You can simply remove LargestUID from the search results:

    uids.Remove(last.LargestUID);
    
  15. Semir Says:

    Yes I’m adding 1 (last.LargestUID + 1) when I search. Removing the largerst UID may cause a problem (that not see the last mail) with different mail servers.

  16. Limilabs support Says:

    @Semir

    Remove last.LargestUID not the largest UID. This will not cause any problems as last.LargestUID was already processed last time you run the code.

  17. Semir Says:

    okey i got it 🙂 thank you for your interest

  18. Rashmi Ranjan Says:

    I want to get latest 40 emails from my Inbox folder descending order by Date or may be uid. Kindly let me know how can I get this.

    When I am using Flag.All and foreach (long uid in uids.Take(40)). I am getting 40 emails, but all emails are very old. Not the latest emails.

    Kindly redirect me to some C# code sample.

  19. Limilabs support Says:

    @Rashmi Ranjan,

    Use Reverse:

    using (Imap client = new Imap())
    {
        client.ConnectSSL("imap.example.com");
        client.Login("pat@example.com","password");
    
        List<long> uids = client.GetAll();
        uids.Reverse();
        List<long> newest40 = uids.Take(40).ToList();
    
        client.Close();
    }