Peek message on IMAP server

If you are using GetMessageByUID to download email message or GetHeadersByUID to get email headers only, most IMAP servers will automatically mark such messages as seen.

In other words IMAP server adds \SEEN flag to the messages you have just downloaded.

If it’s not expected behavior in your scenario, you can use one of the Peek* methods, such as: PeekHeadersByUID, PeekMessageByUID.

Using Peek methods

Those methods in contrast to their Get* equivalents (GetMessageByUID and GetHeadersByUID) do not set the \SEEN flag – emails are not marked as seen automatically.

The sample below connects to IMAP server and finds all unseen messages, then it downloads them, but does not change their unseen status. Messages are left unseen.

// C#

using(Imap imap = new Imap())
{
    imap.ConnectSSL("imap.example.com");
    imap.UseBestLogin("user", "password");

    imap.SelectInbox();
    List<long> uidList = imap.Search(Flag.Unseen);
    foreach (long uid in uidList)
    {
        IMail email = new MailBuilder()
            .CreateFromEml(imap.PeekMessageByUID(uid));

        string subject = email.Subject;
        string text = email.Text;
    }
    imap.Close();
}
' VB.NET

Using imap As New Imap
    imap.ConnectSSL("imap.example.com")
    imap.UseBestLogin("user", "password")

    imap.SelectInbox()
    Dim uidList As List(Of Long) = imap.Search(Flag.Unseen)

    For Each uid As Long In uidList
        Dim email As IMail = New MailBuilder() _
            .CreateFromEml(imap.PeekMessageByUID(uid))

        Dim subject As String = email.Subject
        Dim text As String = email.Text
    Next
    imap.Close()
End Using

There are Peek methods for downloading headers only (PeekHeadersByUID) and even parts of the email message (PeekTextByUID , PeekDataByUID).

Examine instead of Select

The second approach is to use Examine method instead of Select. Examine puts the folder into read-only state, so no \SEEN flag is added to any message.

The drawback is that you can not delete or upload any message.

// C#

using(Imap imap = new Imap())
{
    imap.ConnectSSL("imap.example.com");
    imap.UseBestLogin("user", "password");

    imap.ExamineInbox(); // -or- imap.Examine("Inbox");

    // ...

    imap.Close();
}
' VB.NET

Using imap As New Imap
    imap.ConnectSSL("imap.example.com")
    imap.UseBestLogin"user", "password")

    imap.ExamineInbox() ' -or- imap.Examine("Inbox")

    ' ...

    imap.Close()
End Using

You can also easily mark message as seen and unseen by using Imap.MarkMessageUnseenByUID and Imap.MarkMessageSeenByUID methods.

You can download Mail.dll IMAP library for .NET here.


Get Mail.dll

OAuth 1.0 with IMAP (deprecated)

OAuth is an open protocol to allow secure API authorization for Mail.dll email client in a simple and standard method from desktop and web applications.

The following code makes several HTTP requests to authenticate your application. It also fires up the web browser, so the user can allow or deny the application to access his emails.

Remember to add reference to Mail.dll .NET email component and appropriate namespaces.

1.

// C#

using Limilabs.Client.Authentication;
using Limilabs.Client.IMAP;

const string consumerKey = "anonymous";
const string consumerSecret = "anonymous";

// Get request token
ParameterList parameters1 = OAuth.ForUrl(
        "https://www.google.com/accounts/OAuthGetRequestToken")
    .Consumer(consumerKey, consumerSecret)
    .AddParameter("scope", "https://mail.google.com/")
   .AddParameter(OAuthParameterName.OAuthCallback, "oob")
    .Sign()
    .ExecuteWebRequest();

// Authorize token
string url2 = OAuth.ForUrl(
        "https://www.google.com/accounts/OAuthAuthorizeToken")
   .Consumer(consumerKey, consumerSecret)
   .Token(parameters1.GetValue(OAuthParameterName.OAuthToken))
   .TokenSecret(parameters1.GetValue(OAuthParameterName.OAuthTokenSecret))
   .Sign()
   .GetUrl();

// Fire up the browser.
Process.Start(url2);
// You can use Response.Redirect(url) in ASP.NET

2.
The user needs now to log-in to Gmail account (note that user does not enter credentials in your application):

3.
Then he needs to allow your application to access Gmail:

4.
If you don’t specify callback parameter, user will have to manually copy&paste the token to your application:

In case of a web project, instead of oob value as OAuthCallback parameter, you can specify a web address on your website. oauth_verifier will be included as the redirection url parameter.

After the redirection, your website/application needs to read oauth_verifier query parameter:

5.

// C#

Console.WriteLine("Please enter the key: ");
string oauth_verifier = Console.ReadLine();
// You can use Request["oauth_verifier"].ToString() in ASP.NET

// Get access token
ParameterList parameters3 = OAuth.ForUrl(
        "https://www.google.com/accounts/OAuthGetAccessToken")
   .Consumer(consumerKey, consumerSecret)
   .Token(parameters1.GetValue(OAuthParameterName.OAuthToken))
   .TokenSecret(parameters1.GetValue(OAuthParameterName.OAuthTokenSecret))
   .AddParameter("oauth_verifier", oauth_verifier)
   .Sign()
   .ExecuteWebRequest();

// Log-in to IMAP server using XOAuth
using (Imap client = new Imap())
{
    client.ConnectSSL("imap.gmail.com");

    string imapUrl = string.Format(
        "https://mail.google.com/mail/b/{0}/imap/", userEmailAccount);

    string oauthImapKey = OAuth.ForUrl(imapUrl)
        .Consumer(consumerKey, consumerSecret)
        .Token(parameters3.GetValue(OAuthParameterName.OAuthToken))
        .TokenSecret(parameters3.GetValue(OAuthParameterName.OAuthTokenSecret))
        .Sign()
        .GetXOAuthKey();

    client.LoginOAUTH(oauthImapKey);

    // Now you can access user's emails.
    //...

    client.Close();
}

Here’s the VB.NET version of the code samples:

Remember to add reference to Mail.dll and appropriate namespaces.

1.

' VB.NET

import Limilabs.Client.Authentication
import Limilabs.Client.IMAP

Const  consumerKey As String = "anonymous"
Const  consumerSecret As String = "anonymous"

' Gget request token
Dim parameters1 As ParameterList = OAuth _
	.ForUrl("https://www.google.com/accounts/OAuthGetRequestToken") _
	.Consumer(consumerKey, consumerSecret) _
	.AddParameter("scope", "https://mail.google.com/") _
	.AddParameter(OAuthParameterName.OAuthCallback, "oob") _
	.Sign() _
	.ExecuteWebRequest()

' Authorize token
Dim url2 As String = OAuth _
	.ForUrl("https://www.google.com/accounts/OAuthAuthorizeToken") _
	.Consumer(consumerKey, consumerSecret) _
	.Token(parameters1.GetValue(OAuthParameterName.OAuthToken)) _
	.TokenSecret(parameters1.GetValue(OAuthParameterName.OAuthTokenSecret)) _
	.Sign() _
	.GetUrl()

' Fire up the browser
Process.Start(url2)
' You can use Response.Redirect(url) in ASP.NET

2.
First the user needs to log in to Gmail account (note that user does not enter credentials in your application):

3.
Then he needs to allow your application to access Gmail:

4.
If you don’t specify callback parameter, user will have to manually copy&paste the token to your application:

In case of a web project, instead of oob value as OAuthCallback parameter, you can specify a web address on your website. oauth_verifier will be included as the redirection url parameter.

After the redirection, your website/application needs to read oauth_verifier query parameter:

5.

' VB.NET

Console.WriteLine("Please enter the key: ")
Dim oauth_verifier As String = Console.ReadLine().Trim()
' You can use Request("oauth_verifier").ToString() in ASP.NET

' Third: get access token
Dim parameters3 As ParameterList = OAuth _
	.ForUrl("https://www.google.com/accounts/OAuthGetAccessToken") _
	.Consumer(consumerKey, consumerSecret) _
	.Token(parameters1.GetValue(OAuthParameterName.OAuthToken)) _
	.TokenSecret(parameters1.GetValue(OAuthParameterName.OAuthTokenSecret)) _
	.AddParameter("oauth_verifier", oauth_verifier) _
	.Sign() _
	.ExecuteWebRequest()

' Log-in to IMAP server using XOAuth
Using client As New Imap()
	client.ConnectSSL("imap.gmail.com")

	Dim imapUrl As String = String.Format("https://mail.google.com/mail/b/{0}/imap/", userEmailAccount)

	Dim oauthImapKey As String = OAuth.ForUrl(imapUrl) _
		.Consumer(consumerKey, consumerSecret) _
		.Token(parameters3.GetValue(OAuthParameterName.OAuthToken)) _
		.TokenSecret(parameters3.GetValue(OAuthParameterName.OAuthTokenSecret)) _
		.Sign() _
		.GetXOAuthKeyForImap()

	client.LoginOAUTH(oauthImapKey)

	' Now you can access user's emails.
	' ...

	client.Close()
End Using

IMAP: LIST, XLIST and LSUB

In IMAP protocol there are two commands to retrieve a folder list from the remote server. Most servers use LIST command, some servers support XLIST command (e.g. Gmail).

XLIST provides additional folder flags such as Inbox or Spam (which are very useful if folder names are localized to know the purpose of the folder).

Mail.dll automatically uses XLIST if it is supported by the server. You can read more about XLIST here:
/blog/localized-gmail-imap-folders

Unfortunately when you ask for subscribed folders with LSUB command, those additional flags are not send by the server. There is no such thing as XLSUB command.

The only way to workaround this limitation is to first download all folders with flags using XLIST, then use LSUB to get subscribed folders and finally match the flags by name.

Simple JSON .NET formatter

StringWalker class:

public class StringWalker
{
    private readonly string _s;

    public int Index { get; private set; }
    public bool IsEscaped { get; private set; }
    public char CurrentChar { get; private set; }

    public StringWalker(string s)
    {
        _s = s;
        this.Index = -1;
    }

    public bool MoveNext()
    {
        if (this.Index == _s.Length - 1)
            return false;

        if (IsEscaped == false)
            IsEscaped = CurrentChar == '\\';
        else
            IsEscaped = false;
        this.Index++;
        CurrentChar = _s[Index];
        return true;
    }
};

IndentWriter class:

public class IndentWriter
{
    private readonly StringBuilder _result = new StringBuilder();
    private int _indentLevel;

    public void Indent()
    {
        _indentLevel++;
    }

    public void UnIndent()
    {
        if (_indentLevel > 0)
            _indentLevel--;
    }

    public void WriteLine(string line)
    {
        _result.AppendLine(CreateIndent() + line);
    }

    private string CreateIndent()
    {
        StringBuilder indent = new StringBuilder();
        for (int i = 0; i < _indentLevel; i++)
            indent.Append("    ");
        return indent.ToString();
    }

    public override string ToString()
    {
        return _result.ToString();
    }
};

JSON formatter class:


public class JsonFormatter
{
    private readonly StringWalker _walker;
    private readonly IndentWriter _writer = new IndentWriter();
    private readonly StringBuilder _currentLine = new StringBuilder();
    private bool _quoted;

    public JsonFormatter(string json)
    {
        _walker = new StringWalker(json);
        ResetLine();
    }

    public void ResetLine()
    {
        _currentLine.Length = 0;
    }

    public string Format()
    {
        while (MoveNextChar())
        {
            if (this._quoted == false && this.IsOpenBracket())
            {
                this.WriteCurrentLine();
                this.AddCharToLine();
                this.WriteCurrentLine();
                _writer.Indent();
            }
            else if (this._quoted == false && this.IsCloseBracket())
            {
                this.WriteCurrentLine();
                _writer.UnIndent();
                this.AddCharToLine();
            }
            else if (this._quoted == false && this.IsColon())
            {
                this.AddCharToLine();
                this.WriteCurrentLine();
            }
            else
            {
                AddCharToLine();
            }
        }
        this.WriteCurrentLine();
        return _writer.ToString();
    }

    private bool MoveNextChar()
    {
        bool success = _walker.MoveNext();
        if (this.IsApostrophe())
        {
            this._quoted = !_quoted;
        }
        return success;
    }

    public bool IsApostrophe()
    {
        return this._walker.CurrentChar == '"' && this._walker.IsEscaped == false;
    }

    public bool IsOpenBracket()
    {
        return this._walker.CurrentChar == '{'
            || this._walker.CurrentChar == '[';
    }

    public bool IsCloseBracket()
    {
        return this._walker.CurrentChar == '}'
            || this._walker.CurrentChar == ']';
    }

    public bool IsColon()
    {
        return this._walker.CurrentChar == ',';
    }

    private void AddCharToLine()
    {
        this._currentLine.Append(_walker.CurrentChar);
    }

    private void WriteCurrentLine()
    {
        string line = this._currentLine.ToString().Trim();
        if (line.Length > 0)
        {
            _writer.WriteLine(line);
        }
        this.ResetLine();
    }
};

Few samples:

Console.WriteLine(new JsonFormatter(
	@"{""parameter"" : ""value"" , { ""parameter2"" : ""value2"" },{ ""parameter3"" : ""value3"" } }").Format());
Console.WriteLine(new JsonFormatter(
	@"{""parameter"":[""value1"",""value2"",""value3""] }").Format());
Console.WriteLine(new JsonFormatter(
	@"{""parameter"": ""value with {brackets}"" }").Format());
Console.WriteLine(new JsonFormatter(
    @"{ ""hello"" : ""value with quotes \""{brackets} and back slash: \\"" }").Format());

…and results:

{
    "parameter" : "value" ,
    {
        "parameter2" : "value2"
    },
    {
        "parameter3" : "value3"
    }
}

{
    "parameter":
    [
        "value1",
        "value2",
        "value3"
    ]
}

{
    "parameter": "value with {brackets}"
}

{
    "hello" : "value with quotes \"{brackets} and back slash: \\"
}

Free Yahoo! Mail via IMAP

Update: Mail.dll issues custom ID command automatically, when it recognizes you are accessing yahoo server.

It is possible to get direct Yahoo! IMAP access.
Yahoo! operates IMAP servers (imap.mail.yahoo.com in particular), which are globally accessible.
However they require a specific, but non-standard IMAP command to be sent before login is done. The command is: “ID (“GUID” “1”)”

// C#
imap.SendCommand(@"ID (""GUID"" ""1"")");
' VB
imap.SendCommand("ID (""GUID"" ""1"")")

You can also use IMAP over SSL on the standard port 993.

Here’s the full C# version of the code:

using (Imap imap = new Imap())
{
	imap.ConnectSSL("imap.mail.yahoo.com");

	// Not needed as Mail.dll is going to issue this command automatically
	// imap.SendCommand(@"ID (""GUID"" ""1"")");

	imap.Login("user", "password");

	imap.SelectInbox();
	List<long> uidList = imap.Search(Expression.All());
	foreach (long uid in uidList)
	{
		IMail email = new MailBuilder()
    			.CreateFromEml(imap.GetMessageByUID(uid));
		Console.WriteLine(email.Subject);
	}
	imap.Close();
}

and VB.NET version:

Using imap As New Imap()
	imap.ConnectSSL("imap.mail.yahoo.com")

	' Not needed as Mail.dll is going to issue this command automatically
	'imap.SendCommand("ID (""GUID"" ""1"")")

	imap.Login("user", "password")

	imap.SelectInbox()
	Dim uidList As List(Of Long) = imap.Search(Expression.All())
	For Each uid As Long In uidList
		Dim email As IMail = New MailBuilder() _
			.CreateFromEml(imap.GetMessageByUID(uid))
		Console.WriteLine(email.Subject)
	Next
	imap.Close()
End Using

Yahoo’s IMAP server differences:
– non-standard command ID command is required before any other command,
– Examine explicitly requires CLOSE command (Imap.CloseCurrentFolder), otherwise subsequent SELECT (Imap.Select) has no effect and mailbox is still in read-only stat,
-Yahoo does not support IDLE command,
– SELECT for not existing folder does not create new folder.

You can download the latest version of Mail.dll .NET IMAP component here