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