Click here to Skip to main content
Click here to Skip to main content

Understanding the insides of the IMAP mail protocol: Part 3

By , 4 Oct 2013
Rate this:
Please Sign up or sign in to vote.

Introduction

Everyone who has a computer or mobile device has used mail. Mail system is an old, traditional simple protocol. The purpose of this article (Part 3) is to explore the inside of the IMAP protocol and show you how to implement it with C#.

You can get libraries that include Mail, Twitter, Facebook, dropbox, Windows Live at http://higlabo.codeplex.com/.

IMAP

You can select two protocols to receive mail from a mailbox. This article describes about the IMAP protocol. Here is a basic flow to receive mail.

  • Open connection
  • Authenticate
  • SelectFolder
  • Fetch
  • Logout

Open connection

See Open connection section of this post.
http://www.codeproject.com/Articles/399207/Understanding-the-insides-of-the-SMTP-mail-protoco

Authenticate

At first, you must authenticate the mailbox with your username and password.

ImapClient.cs
public ImapCommandResult ExecuteLogin()
{
    if (this.EnsureOpen() == ImapConnectionState.Disconnected) { throw new MailClientException(); }

    String commandText = String.Format(this.Tag + " LOGIN {0} {1}", this.UserName, this.Password);
    String s = this.Execute(commandText, false);
    ImapCommandResult rs = new ImapCommandResult(this.Tag, s);
    if (rs.Status == ImapCommandResultStatus.Ok)
    {
        this._State = ImapConnectionState.Authenticated;
    }
    else
    {
        this._State = ImapConnectionState.Connected;
    }
    return rs;
}

Get folder list

After authenticate, you must select a folder to get the actual mail data. To select a folder, you would like to get the folder list which exists in the mailbox. You can get the folder list by sending a list command to the mail server.

public ListResult ExecuteList(String folderName, Boolean recursive)
{
    this.ValidateState(ImapConnectionState.Authenticated);

    List<ListLineResult> l = new List<ListLineResult>();
    String name = "";
    Boolean noSelect = false;
    Boolean hasChildren = false;
    String rc = "%";
    if (recursive == true)
    {
        rc = "*";
    }
    String s = this.Execute(String.Format(this.Tag + " LIST \"{0}\" \"{1}\"", folderName, rc), false);
    foreach (Match m in RegexList.GetListFolderResult.Matches(s))
    {
        name = NamingConversion.DecodeString(m.Groups["name"].Value);
        foreach (Capture c in m.Groups["opt"].Captures)
        {
            if (c.Value.ToString() == "\\Noselect")
            {
                noSelect = true;
            }
            else if (c.Value.ToString() == "\\HasNoChildren")
            {
                hasChildren = false;
            }
            else if (c.Value.ToString() == "\\HasChildren")
            {
                hasChildren = true;
            }
        }
        l.Add(new ListLineResult(name, noSelect, hasChildren));
    }
    return new ListResult(l);
}

Under the hood, such a command text is sent to the mail server:

tag1 LIST "" "*"

The response text from the server is like this:

* LIST (\HasNoChildren) "/" "INBOX"
* LIST (\HasNoChildren) "/" "Notes"
* LIST (\Noselect \HasChildren) "/" "[Gmail]"
* LIST (\HasNoChildren) "/" "[Gmail]/All Mail"
......
* LIST (\HasNoChildren) "/" "[Gmail]/Trash"
tag1 OK Success

The ListResult, ListLineResult class diagrams are like below.

You can get all folders by calling the GetAllFolders method of the ImapClient class.

MailMessage mg = null;

using (ImapClient cl = new ImapClient("imap.gmail.com"))
{
    cl.Port = 993;
    cl.Ssl = true;
    cl.UserName = "xxxxx";
    cl.Password = "yyyyy";
    var bl = cl.Authenticate();
    if (bl == true)
    {
        //Get all folder
        var l = cl.GetAllFolders();
    }
}

Select folder

Before you receive mail from the server, you must select the folder in your mailbox. To select the folder, you send a select command to the server. Here is the inside of the ExecuteSelect method of the ImapClient class.

public SelectResult ExecuteSelect(String folderName)
{
    this.ValidateState(ImapConnectionState.Authenticated);
    String commandText = String.Format(this.Tag + " Select {0}", NamingConversion.EncodeString(folderName));
    String s = this.Execute(commandText, false);
    var rs = this.GetSelectResult(folderName, s);
    this.CurrentFolder = new ImapFolder(rs);
    return rs;
}

Client send below text to server.

tag1 Select INBOX

Server return response text to client

* FLAGS (\Answered \Flagged \Draft \Deleted \Seen $Forwarded)
* OK [PERMANENTFLAGS (\Answered \Flagged \Draft \Deleted \Seen $Forwarded \*)] Flags permitted.
* OK [UIDVALIDITY 594544687] UIDs valid.
* 223 EXISTS
* 0 RECENT
* OK [UIDNEXT 494] Predicted next UID.
tag1 OK [READ-WRITE] INBOX selected. (Success)
This response text would be represented as SelectResult class.

The ImapClient class has a SelectFolder method that is easy to use.And this method returns an ImapFolder object that is created from SelectResult object inside of SelectFolder method.

public ImapFolder SelectFolder(String folderName)
{
    var rs = this.ExecuteSelect(folderName);
    return new ImapFolder(rs);
}

Here is the ImapFolder class diagram:

Get message

After folder selection, you can get the mail list using the Fetch command. You can get the actual mail message data by calling the GetMessage method of the ImapClient class. Here is a sample code to receive a mail message:

private static void ImapMailReceive()
{
    MailMessage mg = null;

    using (ImapClient cl = new ImapClient("imap.gmail.com"))
    {
        cl.Port = 993;
        cl.Ssl = true;
        cl.UserName = "xxxxx";
        cl.Password = "yyyyy";
        var bl = cl.Authenticate();
        if (bl == true)
        {
            //Select folder
            var folder = cl.SelectFolder("[Gmail]/All Mail");
            //Get all mail from folder
            for (int i = 0; i < folder.MailCount; i++)
            {
                mg = cl.GetMessage(i + 1);
            }
        }
    }
}

You must pass an index to the GetMessage method larger than 1. Please make sure that the first value is 1, not zero.

Client send below text

tag1 FETCH 1 (BODY[])

Server would return below text

* 1 FETCH (BODY[] {2370}
Delivered-To: xxxx@gmail.com
To: = <yyyyy@gmail.com>
Date: Thu, 23 Apr 2009 08:22:21 +0900
From: <xxxxx@gmail.com>
Subject: Test Mail
...Mail body data....

)
tag1 OK Success

Format is same to Pop3Message.ImapClient class extract text and pass it to MailMessage class and create MailMessage object.

Attached file, HTML mail, .eml file

Aattached file, HTML mail, .eml file are the same as for the POP3 protocol. Please see this article: Understanding the insides of the POP3 mail protocol: Part 2.

Delete mail

Here is the delete process on IMAP:

You add a flag as delete to the selected mail that you indicate by mail index. These marked mails will be deleted when you send the EXPUNGE command.

You can delete a mail by using the DeleteMail method of the ImapClient object.

protected void Button1_Click(object sender, EventArgs e)
{
    MailMessage mg = null;
    String htmlText = "";

    using (ImapClient cl = new ImapClient("imap.gmail.com"))
    {
        cl.Port = 993;
        cl.Ssl = true;
        cl.UserName = "xxxxx";
        cl.Password = "yyyyy";
        //Select folder
        var folder = cl.SelectFolder("[Gmail]/All Mail");
        cl.DeleteMail(1, 2, 3);
    }
}

There is no authenticate required since all processes (open connection, authenticate, select folder, expunge, logout) will automatically execute inside of the DeleteMail method. Here is an implementation of the DeleteMail method.

public Boolean DeleteMail(params Int64[] mailIndex)
{
    this.ValidateState(ImapConnectionState.Authenticated, true);
    return this.DeleteMail(this.CurrentFolder.Name, mailIndex);
}
public Boolean DeleteMail(params Int64[] mailIndex)
{
    if (this.EnsureOpen() == ImapConnectionState.Disconnected) { return false; }
    if (this.Authenticate() == false) { return false; }

    for (int i = 0; i < mailIndex.Length; i++)
    {
        var rs = this.ExecuteStore(mailIndex[i], StoreItem.FlagsAdd, @"\Deleted");
        if (rs.Status != ImapCommandResultStatus.Ok) { return false; }
    }
    this.ExecuteExpunge();
    this.ExecuteLogout();
    return true;
}

Manage read or unread

IMAP is a new protocol after POP3 to solve POP3 problem. IMAP has a search command that can receive only unread mail. So you don't have to manage read state in your application. The only thing you must do is to send a search command to the mail server. Here is a sample code to receive unread mail list from a server.

MailMessage mg = null;

using (ImapClient cl = new ImapClient("imap.gmail.com"))
{
    cl.Port = 993;
    cl.Ssl = true;
    cl.UserName = "xxxxx";
    cl.Password = "yyyyy";
    var bl = cl.Authenticate();
    if (bl == true)
    {
        //Select folder
        var folder = cl.SelectFolder("[Gmail]/All Mail");
        //Search Unread
        var list = cl.ExecuteSearch("UNSEEN UNDELETED");
        //Get all unread mail
        for (int i = 0; i < list.MailIndexList.Count; i++)
        {
            mg = cl.GetMessage(list.MailIndexList[i]);
        }
    }
}

Client send below text under the hood.

tag1 SEARCH UNSEEN UNDELETED

Server would return such text to client

* SEARCH 1 2 3 4 9 10 11 12 13 14 17 18 19 20 21 22 30 32 33
tag1 OK SEARCH completed (Success)

This response text is represented as SearchResult class.

You can easily get mail index list by Search command.It is pretty easy compared with POP3.

Draft mail

You can save a draft mail to the mail server by calling the Append command.

MailMessage mg = null;

using (ImapClient cl = new ImapClient("imap.gmail.com"))
{
    cl.Port = 993;
    cl.Ssl = true;
    cl.UserName = "xxxxx";
    cl.Password = "yyyyy";
    var bl = cl.Authenticate();
    if (bl == true)
    {
        //Add Draft
        var smg = new SmtpMessage("xxx@gmail.com", "yyy@hotmail.com", 
            "yyy@hotmail.com", "This is a test mail.", "Hi.Is it correct??");
        cl.ExecuteAppend("GMail/Drafts", smg.GetDataText(), "\\Draft", DateTimeOffset.Now);
    }
}

You can get a draft mail by the same process shown above.

MailMessage mg = null;

using (ImapClient cl = new ImapClient("imap.gmail.com"))
{
    cl.Port = 993;
    cl.Ssl = true;
    cl.UserName = "xxxxx";
    cl.Password = "yyyyy";
    var bl = cl.Authenticate();
    if (bl == true)
    {
        //Select folder
        var folder = cl.SelectFolder("[Gmail]/Drafts");
        //Get all mail from folder
        mg = cl.GetMessage(1);
        //Create SmtpMessage object
        var smg = mg.CreateSmtpMessage();
        //And send mail!!
    }
}

To send a mail, see this article: Understanding the insides of the SMTP Mail protocol: Part 1.

Subscribe

You can subscribe a folder that you want to watch.

MailMessage mg = null;

using (ImapClient cl = new ImapClient("imap.gmail.com"))
{
    cl.Port = 993;
    cl.Ssl = true;
    cl.UserName = "xxxxx";
    cl.Password = "yyyyy";
    var bl = cl.Authenticate();
    if (bl == true)
    {                
        cl.ExecuteSubscribe("CodeProject");
        cl.ExecuteSubscribe("Codeplex");
    }
}

Once you subscribe to a folder, you can get the folder list using the below code.

MailMessage mg = null;

using (ImapClient cl = new ImapClient("imap.gmail.com"))
{
    cl.Port = 993;
    cl.Ssl = true;
    cl.UserName = "xxxxx";
    cl.Password = "yyyyy";
    var bl = cl.Authenticate();
    if (bl == true)
    {                
        var rs = cl.ExecuteLsub("", false);
        foreach (var line in rs.Lines)
        {
            var folder = new ImapFolder(line);
            //Do something...
        }
    }
}

Idle command

Imap has a idle command that enable you to receive a message(new mail,deleted...etc) from server.
using (ImapClient cl = new ImapClient("imap.gmail.com", 993, "user name", "password"))
{
    cl.Ssl = true;
    cl.ReceiveTimeout = 10 * 60 * 1000;//10 minute
    if (cl.Authenticate() == true)
    {
        ImapFolder r = cl.SelectFolder("INBOX");
        using (var cm = cl.CreateImapIdleCommand())
        {
            cm.MessageReceived += 
            (Object o, ImapIdleCommandMessageReceivedEventArgs e) =>
            {
                foreach (var mg in e.MessageList)
                {
                    String text = String.Format("Type is {0} Number is {1}"
                       , mg.MessageType, mg.Number);
                    Console.WriteLine(text);
                }
            };
            cl.ExecuteIdle(cm);

            while (true)
            {
                var line = Console.ReadLine();
                if (line == "done")
                {
                    cl.ExecuteDone(cm);
                    break;
                }
            }
        }
    }
}

When you receive new mail and the count of INBOX folder changed, server will send client a text as response.

* 224 EXISTS

If you delete a mail, server send such text to client.

* 224 EXPUNGE
* 223 EXISTS

You can receive these message by register event handler to MessageReceived event.

cm.MessageReceived += (Object o, ImapIdleCommandMessageReceivedEventArgs e) =>
{
    foreach (var mg in e.MessageList)
    {
        String text = String.Format("Type is {0} Number is {1}"
            , mg.MessageType, mg.Number);
        Console.WriteLine(text);
    }
};

ImapIdleCommandMessageReceivedEventArgs object has MessageList property that is List. ImapIdleCommandMessage has two property MessageType and Number.

You can know what happen in the server and pop up notification window to user with idle command.

Other...

IMAP has so many specifications that I could not cover in this article. I focuses on the most important topics for IMAP beginners to start easily. I hope this article will help you. Thank you for reading.

Related articles are listed here:

History

  • 2012/06/25: First post.
  • 2012/07/06: Modify source code and article
  • 2012/07/24: Modify source code and add some detail description about Idle and other

License

This article, along with any associated source code and files, is licensed under The MIT License

About the Author

Higty
Web Developer
Japan Japan
I'm Working at Software Company in Tokyo.

Comments and Discussions

 
QuestionUpdates about MIME - IMAP ? Pinmemberkiquenet.com24-Jan-14 5:48 
QuestionRECEME and Twitter Pinmemberkiquenet.com14-Nov-13 11:39 
QuestionDrafts Folder, versions Pinmemberkiquenet.com24-Oct-13 4:04 
AnswerRe: Drafts Folder, versions PinmemberHigty24-Oct-13 15:36 
SuggestionRe: Drafts Folder, versions Pinmemberkiquenet.com25-Oct-13 1:01 
GeneralRe: Drafts Folder, versions PinmemberHigty27-Oct-13 13:18 
GeneralRe: Drafts Folder, versions Pinmemberkiquenet.com27-Oct-13 20:44 
GeneralRe: Drafts Folder, versions PinmemberHigty30-Oct-13 16:33 
QuestionRelated to fetch All folders from IMAP. Pinmembershukla dhaval27-Feb-13 19:15 
AnswerRe: Related to fetch All folders from IMAP. PinmemberHigty27-Feb-13 22:45 
GeneralMy vote of 5 Pinmemberjasonlee092721-Oct-12 23:03 
Very nice!
But i have a question,if the mail has a attachment,how to download it?
GeneralRe: My vote of 5 PinmemberHigty21-Oct-12 23:53 
GeneralMy vote of 3 PinmemberThornik6-Jul-12 11:18 
GeneralRe: My vote of 3 PinmemberHigty9-Jul-12 15:17 
GeneralRe: My vote of 3 PinmemberThornik9-Jul-12 22:06 
GeneralRe: My vote of 3 PinmvpDave Kreskowiak24-Jul-12 18:47 
GeneralMy vote of 5 PinmemberPolinia6-Jul-12 1:48 
GeneralRe: My vote of 5 PinmemberHigty8-Jul-12 15:34 
Questionsource code not found PinmemberTridip Bhattacharjee5-Jul-12 20:27 
AnswerRe: source code not found PinmemberHigty6-Jul-12 0:18 
QuestionVote of 5 PinmemberGanesanSenthilvel5-Jul-12 18:48 
AnswerRe: Vote of 5 PinmemberHigty6-Jul-12 0:18 
GeneralMy vote of 5 PinmemberSuresh Suthar5-Jul-12 18:40 
GeneralRe: My vote of 5 PinmemberHigty6-Jul-12 0:21 
GeneralMy vote of 5 Pinmemberacarpio197526-Jun-12 22:01 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.140415.2 | Last Updated 4 Oct 2013
Article Copyright 2012 by Higty
Everything else Copyright © CodeProject, 1999-2014
Terms of Use
Layout: fixed | fluid