Click here to Skip to main content
15,861,125 members
Articles / Programming Languages / C# 4.0

PopClient - A POP3 companion to SmtpClient

Rate me:
Please Sign up or sign in to vote.
4.94/5 (55 votes)
19 Nov 2010CPOL16 min read 220.7K   4.9K   111   116
PopClient is an asynchronous POP3 library with support for SSL and attachments

Image 1

Figure 1 - POP settings dialog

Image 2

Figure 2 - Mail viewer

Image 3

Figure 3 - POP3 chat logging

Introduction

I needed a POP3 library for a .NET project that a friend and I were working on, and while a quick Google-search revealed several free implementations, none of them fully fit my requirements. I wanted a reliable POP3 class with full support for asynchronous fetching, cancellation, SSL, attachments, HTML email, and an uncomplicated and simple interface so that calling applications wouldn't need to do hideous workarounds to integrate it into their existing system. This article explains how this library can be used, talks about the class implementation, and also includes a demo WPF application that shows how a typical app might use this library.  This library's goal is to provide developers with a very simple way to read email from POP3 servers, and with that in mind, it hides the underlying POP3 protocol from calling code. What that means is that this library is not for those who are writing their own POP3 based code or want to extend the POP3 protocol in some way (for example writing a spam filter or an email auto-forwarding application). In some ways this library can be considered as a POP3 analogy to the SmtpClient class from the System.Net.Mail namespace and in fact it actually uses the MailMessage class (from the same namespace) to represent an email (although that had its disadvantages as I will explain in this article).

Safety note about the demo app

The PopClient class has a DeleteMailAfterPop property that's used to specify whether mails should be left on the server or if they should be deleted after they are fetched. Unless it's a throwaway POP account, chances are extremely low that you'd want to delete mails when running the demo app, and so as a precautionary measure, that property has been disabled both in the UI and in the code, so you will not inadvertently delete any of your email.

VS 2010 and .NET 4.0

While the code and the the demo project has been written and tested using VS 2010 and .NET 4.0, you should not have too much difficulty using this from VS 2008 and .NET 3.5. As far as I know, I have not used any 4.0 specific features in any of the code.

Using the library

All the code is in the PopClient assembly, so you need to add a reference to that. And all the public classes are under the

C#
Extra.Mail
namespace, so you may want to add a using declaration in your code for that namespace. The PopClient class is the only class that you need to directly instantiate and use, and it implements IDisposable and it's important that you dispose it off when you are finished with the class. That said, since the class fetches mail asynchronously, you should not dispose off the class until either the mail fetch operation has completed, or you have manually aborted the mail fetch operation. This basically means that it's not the best suited class for an using-block (typically used for IDisposable types). The demo app shows one typical way that the class be be disposed, and I'll also discuss another simple way to dispose off the instance safely. Ironically, after saying all this, the first bit of code I am about to show you does use an using-block, although this is a console application and I take precautions to ensure that the object stays alive until the POP3 operation has completed.

C#
static void Main()
{
    using (var popClient = new PopClient(host, port) { 
        Username = username, Password = password, EnableSsl = true })
    {
        popClient.MailPopCompleted += PopClient_MailPopCompleted;
        popClient.MailPopped += PopClient_MailPopped;
        popClient.QueryPopInfoCompleted += PopClient_QueryPopInfoCompleted;
        popClient.ChatCommandLog += PopClient_ChatCommandLog;
        popClient.ChatResponseLog += PopClient_ChatResponseLog;

        try
        {
            popClient.PopMail();
        }
        catch(PopClientException pce)
        {
            Console.WriteLine("PopClientException caught!");
            Console.WriteLine(
                "PopClientException.PopClientBusy : {0}", pce.PopClientBusy);
        }

        Console.ReadKey();
    }
}

The class is instantiated with the required POP3 settings such as host, username, and password. I have also hooked onto several events which will be fired asynchronously during the POP3 fetch operation. Once this is all done, a call is made to the PopMail method (which immediately returns), and it's called from a try-catch block since the method can throw exceptions. Although the code above only catches a

C#
PopClientException
be aware that it can also throw an
C#
InvalidOperationException
(the demo app handles both). And finally notice the crafty positioning of the Console.ReadKey call to keep the object alive and un-disposed until the fetch is completed. The ChatXXXLog events basically log protocol-chat used for the POP3 connection, with the
C#
ChatCommandLog
firing for all commands we send to the server and the ChatResponseLog firing for all single-line server responses (for usability reasons, I do not log multi-line responses, since that will quickly be unmanageable when fetching large emails with bulky attachments). It's purely optional to handle these events and their use is primarily to diagnose connectivity issues, although you could also follow the chat log to get an idea of how POP3 chat is executed (it's a very simple protocol, so it's doubtful that anyone will want to do it for that purpose more than once or twice).

C#
static void PopClient_ChatResponseLog(object sender, PopClientLogEventArgs e)
{
    Console.WriteLine("<< {0}", e.Line);
}

static void PopClient_ChatCommandLog(object sender, PopClientLogEventArgs e)
{
    Console.WriteLine(">> {0}", e.Line);
}

 The effect of those two handlers is to give you a full chat conversation (with beautiful TCP direction indicators in text). *grin*

>> STAT
<< +OK 7 703466
>> LIST 1
<< +OK 1 243160
>> UIDL 1
<< +OK 1 717619000000008003
>> RETR 1
<< +OK
>> LIST 2
<< +OK 2 2
>> UIDL 2
<< +OK 2 717619000000009001
>> RETR 2
<< +OK
>> LIST 3
<< +OK 3 216991
>> UIDL 3
<< +OK 3 717619000000010001
>> RETR 3
<< +OK
>> LIST 4
<< +OK 4 12
>> UIDL 4
<< +OK 4 717619000000011001
>> RETR 4
<< +OK

The WPF demo app shows a better way to show this information. The other three events are what you really need to handle for reading the fetched emails. The first one to fire will be the QueryPopInfoCompleted event, which gives you the number of emails and their total size. You can potentially use this information to show a progress-bar or to update a summary status UI panel.

C#
static void PopClient_QueryPopInfoCompleted(object sender, MailPopInfoFetchedEventArgs e)
{
    Console.WriteLine("Event Fired: QueryPopInfoCompleted");
    Console.WriteLine("Count: {0}, Total Size: {1} bytes", e.Count, e.Size);
}

Next, the MailPopped event fires once for each mail that's retrieved, so if you've handled the QueryPopInfoCompleted event you will know how many times this will fire (unless you cancel the operation with a call to Cancel()). The Class Reference section details all the information, arguments,  and properties that are available, so I will not explain every single item in the code, although the property names are fairly self descriptive, so anyone writing code to fetch email probably won't need to look at the documentation.

C#
static void PopClient_MailPopped(object sender, MailPoppedEventArgs e)
{
    Console.WriteLine("Event Fired: MailPopped");
    Console.WriteLine("Mail No. {0}", e.Index);
    Console.WriteLine("Size: {0}", e.Size);
    Console.WriteLine("Uidl: {0}", e.Uidl);
    Console.WriteLine("Received: {0}", e.ReceivedTime);
    Console.WriteLine("From: {0}, {1}", e.Message.From.Address, e.Message.From.DisplayName);
    Console.WriteLine("To: {0}", e.Message.To);
    Console.WriteLine("Subject: {0}", e.Message.Subject);
    Console.WriteLine("Attachments: {0}", e.Message.Attachments.Count);
    
    foreach (var attachment in e.Message.Attachments)
    {
        Console.WriteLine("File: {0}", attachment.Name);
    }

    for (int i = 0; i < e.Message.Headers.Count; i++)
    {
        Console.WriteLine("{0} = {1}", 
            e.Message.Headers.GetKey(i), 
            new String(e.Message.Headers[i].Take(40).ToArray()));
    }
}

The MailPoppedEventArgs.Message property is of type

C#
MailMessage
, so anyone who's used that class before will recognize the other properties that I've used there. The developers who wrote
C#
MailMessage
intended it primarily for the SmtpClient class, which meant that it does not have certain properties that you need when you are retrieving mail, such as Uidl, ReceivedTime,
C#
Size
etc. So I had to provide them though the MailPoppedEventArgs class. I realized this only after I had written half the code and while it would not have been terribly difficult to write my custom MailMessage-like class, I decided to continue using MailMessage, largely out of my inconsiderate need to ensure that my class remained analogous and similar to the SmtpClient class which used MailMessage. Developers like familiarity and I believe that using recognizable types will certainly help with that. I also added an extension method to the Attachment class so that you won't have to mess with memory streams and file IO, and instead can just call a nice Save(filePath) method that will save the attachment (the WPF demo app does make use of this). The last event that's fired is the MailPopCompleted event. Note that this is always fired, whether the POP fetch completed harmoniously, or if it got user cancelled, or even if it had to abort because of an exception. In fact it's the only way to be notified of unexpected errors, so you should always handle this event.

C#
static void PopClient_MailPopCompleted(object sender, MailPopCompletedEventArgs e)
{
    Console.WriteLine("Event Fired: MailPopCompleted");
    Console.WriteLine("MailPopCompletedEventArgs.Aborted : {0}", e.Aborted);
    if (e.Exception != null)
    {
        Console.WriteLine(e.Exception.Message);

        PopClientException pce = e.Exception as PopClientException;
        if(pce != null)
        {                    
            Console.WriteLine("PopClientException.PopClientUserCancelled : {0}", 
                pce.PopClientUserCancelled);
        }
    }            
}

This code does not fully demonstrate how the PopClientException can be used to determine POP3 errors (like a bad password, or a POP3 server error), but the demo app does a better job with that.

Retrieving only new messages

  • Support for retrieving only new messages was added in the November 19th 2010 update.

If you've noticed with mail clients that have an option to leave mail on the server, they only retrieve mail that has not been previously fetched. They achieve this by storing the UIDL values for each mail message and then not downloading messages that match an existing UIDL. The PopClient class now has a collection property called UidlsToIgnore. So prior to calling PopMail, the calling client can optionally populate this collection and those messages will be skipped. Changes have been made to the library so that the count and total size reported will be adjusted to accommodate these skipped messages. Be aware though that the index value provided by the MailPoppedEventArgs class will continue to represent the index value of the message in the mail server. So you will potentially see non-contiguous indices when fetching mail if you have opted to skip existing messages. This is not a bug and is correct behavior. Example code showing how skip-UIDLs are added:

C#
popClient.UidlsToIgnore.Add("717619000000008003");
popClient.UidlsToIgnore.Add("717619000000009001");
popClient.UidlsToIgnore.Add("717619000000010001");

Warning : Event handlers and thread context

One very important thing to keep in mind is that all the events except for the MailPopCompleted event are fired on a different thread (the worker thread that the class internally uses for the POP3 connection). So if you are handling UI from these event handlers, you need to take the typical precautions that you need to take when accessing UI controls from auxiliary threads. There are well-known methods to work around this where the events can be made to fire on the same thread that created the PopClient object, but I decided not to go that route for two specific reasons. The first reason is that this forces the PopClient class to be aware of such UI/threading issues and I wanted to keep the class design clean which meant that it had to be kept unaware of such frivolous side effects. A second important reason is that whether the calling code uses Windows Forms or WPF would dictate the need to handle thread context in distinct ways, not to mention that the class may be used from other .NET UI frameworks which may have their own thread related quirks. A third minor reason is that it may actually be a hazardous approach to hide this from the caller by attempting to handle this in the

C#
PopClient
class, since the caller will remain blissfully unaware of these threading issues and will thus be unprepared to deal with any potential consequences arising out of inter-thread access. And finally, as you will see from the demo project, it's quite trivial to handle this in the calling UI code.

The demo application

Image 4

Figure 4 - Saving an attachment

Image 5

Figure 5 - Detailed logging/exception handling

The WPF demo application was written using Visual Studio 2010 and targets the .NET 4.0 framework. I will completely refrain from discussing the XAML code here and will instead focus on how the PopClient class is typically used in a UI application. Those of you who are interested in the XAML/data-binding can go through the project source, and if you have specific questions (unlikely given how simple the code is), please feel free to ask me through the forum for this article. The demo project uses MVVM and all of the PopClient code is in a single View-Model class. Every method that I discuss below will thus be a part of this class.

The View-Model has a PopClient field (needs to be a field, so we can support cancellation and disposing). Note that the socket connection is not established when you instantiate the class (not surprising since it does not have connection info yet).

C#
private PopClient popClient = new PopClient();

The event handlers are hooked up in the V-M constructor.

C#
public MainWindowViewModel()
{
    popClient.QueryPopInfoCompleted += PopClient_QueryPopInfoCompleted;
    popClient.MailPopped += PopClient_MailPopped;
    popClient.MailPopCompleted += PopClient_MailPopCompleted;
    popClient.ChatCommandLog += PopClient_ChatCommandLog;
    popClient.ChatResponseLog += PopClient_ChatResponseLog;

    Application.Current.Exit += Current_Exit;
}

void Current_Exit(object sender, ExitEventArgs e)
{
    popClient.Dispose();
}

In addition, I also handle the application's Exit event so I can dispose off the popClient instance. Technically this can be considered a superficial thing to do since the process will have terminated and all process-specific resources would be released. But it keeps the code clean and encourages this practice in applications where the V-M may be created and destroyed multiple times during the life of an application (in which case, the V-M itself should probably be an IDisposable). It's a little easier with WinForms since all Controls and Forms are

C#
IDisposable
, and thus there's a documented well-established place to dispose the PopClient instance. WPF windows don't do it that way, since the WPF approach of using weak references everywhere makes this unnecessary. One alternate way to dispose off the PopClient instance is in the MailPopCompleted event handler although it is not a very clean approach and is a bit of a hack. I say that because when the event fires, the PopClient instance is still in use, so for a few microseconds you actually have a disposed object that's still executing a method (albeit its last one). Of course since I wrote the class, I know it's safe to do this (as of today) and there's no risk of the GC firing when it's still not done executing a method, but if you run code profilers or memory leak analyzers, they may complain and throw up an agitated warning message. So I wouldn't recommend doing this unless it's in code that you are in full control of.

The following fields are used to propagate information to the UI, namely the main window and the log window. The LogInfo class is merely a convenience class I created to help with data-binding. One important member here is the mainDispatcher field which is used to handle the thread context issues arising from how the events are fired on secondary threads (and not from the main UI thread where the handlers were originally setup from).

C#
private Dispatcher mainDispatcher;

private ObservableCollection<MailPoppedEventArgs> mails
  = new ObservableCollection<MailPoppedEventArgs>();

class LogInfo
{
    public string Line { get; set; }
    public bool Response { get; set; }
}

private ObservableCollection<LogInfo> logs
  = new ObservableCollection<LogInfo>();

The RefreshCommand property is a command object exposed by the V-M that basically performs the mail-fetch operation.

C#
public ICommand RefreshCommand
{
    get
    {
        return refreshCommand ??
          (refreshCommand = new DelegateCommand(Refresh, CanRefresh));
    }
}

public bool CanRefresh()
{
    return !popClient.IsWorking();
}

public void Refresh()
{
    popClient.Host = Settings.Default.Host;
    popClient.Port = Settings.Default.Port;
    popClient.Username = Settings.Default.Username;
    popClient.Password = this.PopPassword;
    popClient.EnableSsl = Settings.Default.EnableSsl;
    popClient.DeleteMailAfterPop = false; // For demo safety!
    popClient.Timeout = Settings.Default.Timeout;

    try
    {
        mainDispatcher = Dispatcher.CurrentDispatcher;
        FetchStatusText = "Fetching...";
        mails.Clear();
        logs.Clear();
        popClient.PopMail();
    }
    catch (InvalidOperationException ex)
    {
        FetchStatusText = String.Format(
          "Connection error - {0}", ex.Message);
    }
    catch (PopClientException ex)
    {
        FetchStatusText = String.Format(
          "POP3 error - {0}", ex.Message);
    }
}

Notice how CanRefresh delegates the call to

C#
PopClient.IsWorking
which will return true if a fetch operation is current under way. I assign the various POP3 properties that are required for a POP3 connection here, although for this particular demo project this was actually a mistake to do this here - since it gets called every time Refresh is called. Originally I had planned to allow the user to change POP settings after a fetch, but eventually I ended up showing the settings dialog only once, at startup. Of course that's a rather insignificant issue, but I thought I'd mention that here in case someone wonders why I did it that way. Notice how I save the current Dispatcher instance in the mainDispatcher field, this is what I'll use later to update my ObservableCollection<>s because they will be bound to the View and will thus need to execute on the UI thread.

Cancelling is fairly straightforward.

C#
public ICommand CancelCommand
{
    get
    {
        return cancelCommand ?? 
          (cancelCommand = new DelegateCommand(Cancel, CanCancel));
    }
}

public bool CanCancel()
{
    return popClient.IsWorking();
}

public void Cancel()
{
    popClient.Cancel();
}

There is a potential race condition where CanCancel returns true, but the fetch operation completes before Cancel is called. But it's safe to call Cancel even in that scenario, so there's no need to handle that race condition.

Here's the code that's used to save an attachment (this is accessible via the context menu on attachment icons).

C#
public ICommand SaveFileCommand
{
    get
    {
        return saveFileCommand ?? 
          (saveFileCommand = new DelegateCommand<Attachment>(SaveFile));
    }
}

public void SaveFile(Attachment attachment)
{
    SaveFileDialog dialog = new SaveFileDialog()
    {
        FileName = attachment.Name
    };

    if (dialog.ShowDialog().GetValueOrDefault())
    {
        attachment.Save(dialog.FileName);
    }
}

The Save method is an extension method I wrote on the

C#
Attachment
class. Here's the code that brings up the Chat-Log window.

C#
public ICommand ShowChatLogCommand
{
    get
    {
        return showChatLogCommand ?? 
          (showChatLogCommand = new DelegateCommand(
            ShowChatLog, CanShowChatLog));
    }
}

private LogWindow logWindow;

public bool CanShowChatLog()
{
    return logWindow == null;
}

public void ShowChatLog()
{
    logWindow = new LogWindow() 
      { DataContext = logs, Owner = Application.Current.MainWindow };
    logWindow.Show();
    logWindow.Closed += (s,e) => logWindow = null;
}

As you can see it uses the logs collection, which is populated via the ChatXXXLog event handlers.

C#
void PopClient_ChatResponseLog(object sender, PopClientLogEventArgs e)
{
    mainDispatcher.Invoke((Action)(() => logs.Add(
      new LogInfo() { Line = e.Line, Response = true })), null);
}

void PopClient_ChatCommandLog(object sender, PopClientLogEventArgs e)
{
    mainDispatcher.Invoke((Action)(() => logs.Add(
      new LogInfo() { Line = e.Line })), null);
}

Notice how I use mainDispatcher to invoke my code on the UI thread. Here are the event handlers for the QueryPopInfoCompleted and MailPopped events.

C#
void PopClient_MailPopped(object sender, MailPoppedEventArgs e)
{
    mainDispatcher.Invoke((Action)(() => mails.Add(e)), null);
}

void PopClient_QueryPopInfoCompleted(
  object sender, MailPopInfoFetchedEventArgs e)
{
    MailStatsText = String.Format(
      "{0} mails, Size = {1}", e.Count, e.Size);
}

And, here's the event handler for the MailPopCompleted event.

C#
void PopClient_MailPopCompleted(object sender, MailPopCompletedEventArgs e)
{
    if (e.Aborted)
    {
        PopClientException popex = e.Exception as PopClientException;
        if (popex == null)
        {
            FetchStatusText = "Aborted!";
        }
        else
        {
            FetchStatusText = popex.PopClientUserCancelled 
              ? "User cancelled!" :  
                String.Format("POP3 error - {0}", popex.Message);
        }
    }
    else
    {
        FetchStatusText = "Done!";
    }

    CommandManager.InvalidateRequerySuggested();
}

The code here is a better example of how the various exceptions are handled (compared to the code I showed earlier). It demonstrates how the application can handle POP3 server errors and display those messages back to the user.

Class Reference

All public types are in the Extra.Mail namespace.

PopClient Class

C#
public class PopClient : IDisposable
{
    // Summary:
    //     Initializes a new instance of the PopClient class.
    public PopClient();

    //
    // Summary:
    //     Initializes a new instance of the PopClient class.
    //
    // Parameters:
    //   host:
    //     The name or IP address of the host server
    public PopClient(string host);

    //
    // Summary:
    //     Initializes a new instance of the PopClient class.
    //
    // Parameters:
    //   host:
    //     The name or IP address of the host server
    //
    //   port:
    //     The port to be used
    public PopClient(string host, int port);

    // Summary:
    //     Specify whether email is deleted after fetch
    public bool DeleteMailAfterPop { get; set; }

    //
    // Summary:
    //     Specify whether the PopClient uses a Secure Sockets Layer connection
    public bool EnableSsl { get; set; }

    //
    // Summary:
    //     Gets or sets the name or IP address of the POP3 host
    public string Host { get; set; }

    //
    // Summary:
    //     Gets or sets the POP3 password
    public string Password { get; set; }

    //
    // Summary:
    //     Gets or sets the port used for the POP3 connection
    public int Port { get; set; }

    //
    // Summary:
    //     Gets or sets a value that specifies the amount of time after the connection
    //     times out.
    public int Timeout { get; set; }

    //
    // Summary:
    //     Gets or sets the POP3 username
    public string Username { get; set; }

    /// <summary>
    /// Gets or sets the collection of UIDLs that are to be ignored when doing a fetch.
    /// </summary>    
    public HashSet<string> UidlsToIgnore { get; private set; }

    // Summary:
    //     Occurs when a POP3 command is sent to the server
    public event EventHandler<PopClientLogEventArgs> ChatCommandLog;

    //
    // Summary:
    //     Occurs when a POP3 response is received from the server
    public event EventHandler<PopClientLogEventArgs> ChatResponseLog;

    //
    // Summary:
    //     Occurs when the asynchronous fetch completes
    public event EventHandler<MailPopCompletedEventArgs> MailPopCompleted;

    //
    // Summary:
    //     Occurs when a mail is fetched
    public event EventHandler<MailPoppedEventArgs> MailPopped;

    //
    // Summary:
    //     Occurs when summary info for the fetch operation is available
    public event EventHandler<MailPopInfoFetchedEventArgs> QueryPopInfoCompleted;

    // Summary:
    //     Cancels the asynchronous fetch operation
    public void Cancel();

    //
    // Summary:
    //     Releases all resources used by the PopClient class.
    public void Dispose();

    //
    // Summary:
    //     Indicates whether a fetch operation is under way
    //
    // Returns:
    //     True if a fetch is on, False otherwise
    public bool IsWorking();

    //
    // Summary:
    //     Begins an asynchronous fetch operation
    public void PopMail();
}

PopClientException Class

C#
[Serializable]
public class PopClientException : Exception
{
    // Summary:
    //     Initializes a new instance of the PopClientException class.
    public PopClientException();
    
    //
    // Summary:
    //     Initializes a new instance of the PopClientException class.
    //
    // Parameters:
    //   message:
    //     The message that describes the error
    public PopClientException(string message);

    // Summary:
    //     Gets a value indicating whether the PopClient is busy.
    public bool PopClientBusy { get; }
    
    //
    // Summary:
    //     Gets a value indicating whether the fetch operation was cancelled by the
    //     user.
    public bool PopClientUserCancelled { get; }

    // Summary:
    //     Initializes a new instance of the PopClientException class.
    //
    // Parameters:
    //   info:
    //     The object that holds the serialized object data
    //
    //   context:
    //     The contextual information about the source or destination
    public override void GetObjectData(SerializationInfo info, StreamingContext context);
}

MailPopInfoFetchedEventArgs Class

C#
public class MailPopInfoFetchedEventArgs : EventArgs
{
    // Summary:
    //     Instantiates a new instance of the MailPopInfoFetchedEventArgs class
    //
    // Parameters:
    //   count:
    //     The number of messages
    //
    //   size:
    //     Total size of all messages
    public MailPopInfoFetchedEventArgs(int count, int size);

    // Summary:
    //     Get the number of messages
    public int Count { get; }
    //
    // Summary:
    //     Gets the total size of all messages
    public int Size { get; }
}

MailPoppedEventArgs Class

C#
public class MailPoppedEventArgs : EventArgs
{
    // Summary:
    //     Instantiates a new instance of the MailPoppedEventArgs class
    //
    // Parameters:
    //   index:
    //     The index of the message
    //
    //   message:
    //     A MailMessage that contains the fetched message
    //
    //   size:
    //     The size of the mail
    //
    //   uidl:
    //     The uidl value of the message
    //
    //   receivedTime:
    //     Time when the message was received by the server
    public MailPoppedEventArgs(
      int index, MailMessage message, int size, string uidl, DateTime receivedTime);

    // Summary:
    //     Gets the index of the message
    public int Index { get; }
    //
    // Summary:
    //     Gets the MailMessage that contains the fetched message
    public MailMessage Message { get; }
    //
    // Summary:
    //     Gets the time when the message was received by the server
    public DateTime ReceivedTime { get; }
    //
    // Summary:
    //     Gets the size of the mail
    public int Size { get; }
    //
    // Summary:
    //     Gets the uidl value of the message
    public string Uidl { get; }
}

MailPopCompletedEventArgs Class

C#
public class MailPopCompletedEventArgs : EventArgs
{
    // Summary:
    //     Instantiates a new instance of the MailPopCompletedEventArgs class
    public MailPopCompletedEventArgs();
    //
    // Summary:
    //     Instantiates a new instance of the MailPopCompletedEventArgs class
    //
    // Parameters:
    //   ex:
    //     Any exception that was thrown during the asynchronous fetch
    public MailPopCompletedEventArgs(Exception ex);

    // Summary:
    //     Gets a value indicating whether the fetch operation was aborted
    public bool Aborted { get; }
    //
    // Summary:
    //     Gets any exception that was thrown during the asynchronous fetch
    public Exception Exception { get; }
}

PopClientLogEventArgs Class

C#
public class PopClientLogEventArgs : EventArgs
{
    // Summary:
    //     Instantiates a new instance of the PopClientLogEventArgs class
    //
    // Parameters:
    //   line:
    //     A string representing a message log line
    public PopClientLogEventArgs(string line);

    // Summary:
    //     Gets a string representing a message log line
    public string Line { get; }
}

Implementation details

This class is based on the POP3 protocol which is standardized via RFC1939 (Post Office Protocol v3).

One of the design intentions was to hide the implementation details from the public interface and this meant that I could opt to selectively implement the minimal protocol needed to fetch email from a POP3 server without losing out on any POP3 functionality. The internal class PopConnection is used to establish a socket connection and to send/receive POP3 commands and responses from a server. It's a very thin socket based class that uses a

C#
TcpClient
object to communicate with a POP server.

C#
internal class PopConnection : IDisposable
{
    private char[] endMarker;
    
    private Stream stream;
    
    private TcpClient tcpClient;

    public PopConnection(PopClient popClient);

    private bool CheckForEndOfData(StringBuilder sb);

    public void Dispose();

    protected virtual void Dispose(bool disposing);

    public string ReadMultiLineResponseString();

    public int ReadResponse(out byte[] bytes);

    public string ReadResponseString();

    private string SendCommandInternal(string command);

    public string SendDele(int messageId);

    public string SendList();

    public string SendList(int messageId);

    public string SendPass(string pass);

    public string SendQuit();

    public string SendRetr(int messageId);

    public string SendStat();

    public string SendUidl();

    public string SendUidl(int messageId);

    public string SendUser(string user);
}

Here's a partial list of the SendXXX-method implementations.

C#
private string SendCommandInternal(string command)
{
    byte[] bytes = Encoding.UTF8.GetBytes(command);
    stream.Write(bytes, 0, bytes.Length);
    return command;
}

public string SendQuit()
{
    return SendCommandInternal("QUIT\r\n");
}

public string SendStat()
{
    return SendCommandInternal("STAT\r\n");
}

public string SendList(int messageId)
{
    return SendCommandInternal(String.Format("LIST {0}\r\n", messageId));
}

public string SendDele(int messageId)
{
    return SendCommandInternal(String.Format("DELE {0}\r\n", messageId));
}

It's pretty straightforward socket code, specially since the POP3 protocol is quite uncomplicated. The receive methods are also quite straightforward.

C#
public string ReadResponseString()
{
    byte[] bytes;
    int count = this.ReadResponse(out bytes);
    return bytes.GetString(count);
}

public string ReadMultiLineResponseString()
{
    StringBuilder sb = new StringBuilder();

    byte[] bytes;

    do
    {
        int count = this.ReadResponse(out bytes);
        sb.Append(bytes.GetString(count));                
    }
    while( !CheckForEndOfData(sb) );

    return sb.ToString();
}

private char[] endMarker = { '\r', '\n', '.', '\r', '\n' };

private bool CheckForEndOfData(StringBuilder sb)
{
    if (sb.Length < 5)
        return false;

    char[] compare = new char[5];
    sb.CopyTo(sb.Length - 5, compare, 0, 5);

    return (endMarker as IStructuralEquatable).Equals(
        compare, EqualityComparer<char>.Default);
}

Every POP3 command has an associated command object, and all command objects implement the IPopCommand interface.

C#
internal interface IPopCommand
{
    event EventHandler<PopClientDirectionalLogEventArgs> PopClientLog;

    void Execute(params object[] arguments);

    bool IsMultiLineResponse { get; }
}

There is an abstract BasePopCommand class that implements some of the common stuff and makes it easy to implement command classes.

C#
internal abstract class BasePopCommand : IPopCommand
{
    private EventHandler<PopClientDirectionalLogEventArgs> PopClientLog;

    public event EventHandler<PopClientDirectionalLogEventArgs> PopClientLog;

    public BasePopCommand(PopConnection popConnection);
    private void CheckForErrorResponse();
    public void Execute(params object[] arguments);
    private void Execute(object argument);
    protected abstract string ExecuteInternal(object argument);
    protected void OnPopClientLog(string line, bool isServerResponse);
    protected virtual void ParseInternal();

    public virtual bool IsMultiLineResponse { get; }
    protected string MultiLineResponse { get; private set; }
    protected PopConnection PopConnection { get; private set; }
    protected string Response { get; private set; }
}

Here are some of the more important method implementations.

C#
public void Execute(params object[] arguments)
{
    this.Execute(arguments.FirstOrDefault());
}

private void Execute(object argument)
{
    this.OnPopClientLog(ExecuteInternal(argument), false);
    this.Response = this.PopConnection.ReadResponseString();
    this.OnPopClientLog(this.Response, true);
    this.CheckForErrorResponse();
    if (this.IsMultiLineResponse)
    {
        this.MultiLineResponse = 
          this.PopConnection.ReadMultiLineResponseString();
    }

    this.ParseInternal();
}

protected virtual void ParseInternal()
{            
}

private void CheckForErrorResponse()
{
    if (this.Response.StartsWith("-ERR"))
    {
        throw new PopClientException(
          new String(this.Response.Skip(4).ToArray()).Trim());
    }
}

Derived classes now just need to implement ExecuteInternal and optionally ParseInternal (if they need to parse the response stream). Here's how the User command is implemented, it's one of the more simple ones as it does not do custom parsing on the response.

C#
internal class UserPopCommand : BasePopCommand
{
    public UserPopCommand(PopConnection popConnection)
        : base(popConnection)
    {
    }

    protected override string ExecuteInternal(object argument)
    {
        return PopConnection.SendUser(argument as string);
    }
}

Here's a slightly more involved derived class that implements the Stat command.

C#
internal class StatPopCommand : BasePopCommand
{
    public StatPopCommand(PopConnection popConnection)
        : base(popConnection)
    {
    }

    protected override string ExecuteInternal(object argument)
    {
        return PopConnection.SendStat();
    }

    private int count;

    public int Count
    {
        get { return count; }
    }

    private int size;

    public int Size
    {
        get { return size; }
    }

    protected override void ParseInternal()
    {
        string[] parts = this.Response.Trim().Split();

        if (parts.Length != 3 || !Int32.TryParse(parts[1], out count) 
          || !Int32.TryParse(parts[2], out size))
        {
            throw new PopClientException("Unknown STAT response from server.");
        }
    }
}

One of the most important commands is the Retr command, and here's how that's been implemented.

C#
internal class RetrPopCommand : BasePopCommand
{
  public MailMessage Message { get; private set; }

  public DateTime ReceivedTime  { get; private set; }

  public RetrPopCommand(PopConnection popConnection)
      : base(popConnection)
  {
  }

  protected override string ExecuteInternal(object argument)
  {
      return PopConnection.SendRetr((int)argument);
  }

  protected override void ParseInternal()
  {
      var converter = new CDOMessageConverter(this.MultiLineResponse);
      this.Message = converter.ToMailMessage();
      this.ReceivedTime = converter.ReceivedTime;
  }

  public override bool IsMultiLineResponse
  {
      get
      {
          return true;
      }
  }
}

Notice the use of the CDOMessageConverter class. CDO (Collaboration Data Objects) is a COM component that's been part of Windows OSes since Windows 2000, and it has built in functionality to parse MIME based email (including full support for attachments). CDO has no idea about the

C#
MailMessage
class, so I wrote a converter that will take a MIME string, construct a CDO message from it, and then convert the CDO message into a
C#
MailMessage
object. It's fairly straightforward COM-interop, so I won't go into the details. The two classes do not have a one-to-one structural equivalence, so I had to make certain compromises, but as long as I was able to obtain all fields required to read an email message with attachments, I was not too concerned about superficial differences between the types.

The PopChat class provides a single point interface from where the PopClient class can invoke POP commands. Here's a snipped code listing that shows what the class does. In addition to exposing properties for each command, it also makes sure that all commands have their log events subscribed to via a common event handler.

C#
internal class PopChat : IDisposable
{
  //...
  
  internal NothingPopCommand Nothing { get; private set; }
  internal UserPopCommand User { get; private set; }
  internal PassPopCommand Pass { get; private set; }
  internal StatPopCommand Stat { get; private set; }
  //...

  public PopChat(PopClient client)
  {
      //...

      new IPopCommand[] 
      { 
          Nothing = new NothingPopCommand(popConnection),
          User = new UserPopCommand(popConnection),
          Pass = new PassPopCommand(popConnection),
          Stat = new StatPopCommand(popConnection),
          Retr = new RetrPopCommand(popConnection),
          Quit = new QuitPopCommand(popConnection),
          List = new ListPopCommand(popConnection),
          Uidl = new UidlPopCommand(popConnection),
          Dele = new DelePopCommand(popConnection),
          ListAll = new ListAllPopCommand(popConnection),
          UidlAll = new UidlAllPopCommand(popConnection)
      }.ToList().ForEach(pc => pc.PopClientLog += PopCommand_PopClientLog);
  }

  private void PopCommand_PopClientLog(
    object sender, PopClientDirectionalLogEventArgs e)
  {
      this.client.LogLine(e.Line, e.IsServerResponse);   
  }
}

And finally, the actual POP chat is invoked from the BackgroundWorker thread that's used by the PopClient class.

private void Worker_DoWork(object sender, DoWorkEventArgs e)
{
    using (PopChat chat = new PopChat(this))
    {
        chat.Nothing.Execute();
        chat.User.Execute(this.Username);
        chat.Pass.Execute(this.Password);
        chat.Stat.Execute();
        chat.ListAll.Execute();
        chat.UidlAll.Execute();

        int count = chat.Stat.Count;
        int size = chat.Stat.Size;

        if (this.UidlsToIgnore.Count > 0)
        {
            var skipList = chat.UidlAll.Uidls.Values.Intersect(this.UidlsToIgnore);

            count -= skipList.Count();

            foreach (var uidl in skipList)
            {
                size -= chat.ListAll.MessageSizes[chat.UidlAll.Indices[uidl]];
            }
        }

        this.OnMailPopInfoFetched(count, size);

        for (int fetchIndex = 1; fetchIndex <= chat.Stat.Count; fetchIndex++)
        {
            if (worker.CancellationPending)
            {
                e.Cancel = true;
                break;
            }

            if (!this.UidlsToIgnore.Contains(chat.UidlAll.Uidls[fetchIndex]))
            {
                chat.Retr.Execute(fetchIndex);
                this.OnMailPopped(fetchIndex, chat.Retr.Message, 
                  chat.ListAll.MessageSizes[fetchIndex], 
                  chat.UidlAll.Uidls[fetchIndex], chat.Retr.ReceivedTime);

                if (this.DeleteMailAfterPop)
                {
                    chat.Dele.Execute(fetchIndex);
                }
            }
        }

        chat.Quit.Execute();
    }
}

Conclusion

If you run into specific issues with a POP server, the chat log (easily accessible via the event handlers) should tell you exactly what's going on. As always, feedback and criticism is welcome, and please feel free to post your comments through the discussion forum for this article. Although if you post something nasty or vote anything less than a 5, I have a Yaqui curse that I recently learned all ready to apply on you! *grin*

History

  • November 8th, 2010 - Article first published.
  • November 19th, 2010
    • Default POP port was incorrectly set to the SMTP port. This is now fixed.
    • Added support for skippable UIDLs. This lets you only retrieve mail that was not fetched earlier.
    • New command objects added for UIDL and LIST that supplement the existing versions, except that these do an index-less full fetch.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
Nish Nishant is a technology enthusiast from Columbus, Ohio. He has over 20 years of software industry experience in various roles including Chief Technology Officer, Senior Solution Architect, Lead Software Architect, Principal Software Engineer, and Engineering/Architecture Team Leader. Nish is a 14-time recipient of the Microsoft Visual C++ MVP Award.

Nish authored C++/CLI in Action for Manning Publications in 2005, and co-authored Extending MFC Applications with the .NET Framework for Addison Wesley in 2003. In addition, he has over 140 published technology articles on CodeProject.com and another 250+ blog articles on his WordPress blog. Nish is experienced in technology leadership, solution architecture, software architecture, cloud development (AWS and Azure), REST services, software engineering best practices, CI/CD, mentoring, and directing all stages of software development.

Nish's Technology Blog : voidnish.wordpress.com

Comments and Discussions

 
QuestionGet new emails only Pin
KEL32-Nov-14 22:40
KEL32-Nov-14 22:40 
AnswerRe: Get new emails only Pin
Garth J Lancaster2-Nov-14 22:57
professionalGarth J Lancaster2-Nov-14 22:57 
GeneralRe: Get new emails only Pin
KEL32-Nov-14 23:57
KEL32-Nov-14 23:57 
GeneralRe: Get new emails only Pin
Garth J Lancaster3-Nov-14 0:00
professionalGarth J Lancaster3-Nov-14 0:00 
GeneralMy vote of 5 Pin
Amir Mohammad Nasrollahi7-Aug-13 5:46
professionalAmir Mohammad Nasrollahi7-Aug-13 5:46 
GeneralMy vote of 5 Pin
bobishkindaguy24-May-13 14:26
bobishkindaguy24-May-13 14:26 
QuestionFails on line 88 of PopConnection.cs, trying to read response Pin
bobishkindaguy24-May-13 14:22
bobishkindaguy24-May-13 14:22 
AnswerRe: Fails on line 88 of PopConnection.cs, trying to read response Pin
Nish Nishant26-May-13 13:42
sitebuilderNish Nishant26-May-13 13:42 
GeneralRe: Fails on line 88 of PopConnection.cs, trying to read response Pin
bobishkindaguy27-May-13 9:33
bobishkindaguy27-May-13 9:33 
GeneralRe: Fails on line 88 of PopConnection.cs, trying to read response Pin
Nish Nishant27-May-13 14:31
sitebuilderNish Nishant27-May-13 14:31 
QuestionQuestion about old messages Pin
Hugo021031-Mar-13 21:58
Hugo021031-Mar-13 21:58 
AnswerRe: Question about old messages Pin
Amir Mohammad Nasrollahi7-Aug-13 5:52
professionalAmir Mohammad Nasrollahi7-Aug-13 5:52 
QuestionAmbiguous Call Extra.Mail.ByteArrayExtensions.GetString(byte[], int) Pin
Hugo021030-Mar-13 19:26
Hugo021030-Mar-13 19:26 
AnswerRe: Ambiguous Call Extra.Mail.ByteArrayExtensions.GetString(byte[], int) Pin
Nish Nishant31-Mar-13 5:55
sitebuilderNish Nishant31-Mar-13 5:55 
GeneralRe: Ambiguous Call Extra.Mail.ByteArrayExtensions.GetString(byte[], int) Pin
Hugo021031-Mar-13 9:45
Hugo021031-Mar-13 9:45 
GeneralRe: Ambiguous Call Extra.Mail.ByteArrayExtensions.GetString(byte[], int) Pin
Nish Nishant3-Apr-13 3:24
sitebuilderNish Nishant3-Apr-13 3:24 
QuestionHow do I use this with MVC? Pin
vnmatt12-Feb-13 1:01
vnmatt12-Feb-13 1:01 
QuestionThe link to the updated source code ... Pin
Ed Gadziemski8-Jan-13 11:26
professionalEd Gadziemski8-Jan-13 11:26 
GeneralAdded support for skippable UIDLs not included in source download. Pin
starsoft26-Jan-12 3:22
starsoft26-Jan-12 3:22 
QuestionIncreased Attachment size Pin
LokNar77712-Oct-11 5:51
LokNar77712-Oct-11 5:51 
QuestionGets me what I need Pin
TestVBC3-Oct-11 10:55
TestVBC3-Oct-11 10:55 
GeneralSome servers don't respond on the STAT command Pin
mathiasiversen19-Apr-11 4:00
mathiasiversen19-Apr-11 4:00 
GeneralOne thought about used streams Pin
Jecka22-Mar-11 5:37
Jecka22-Mar-11 5:37 
GeneralMy vote of 5 Pin
syscom02868-Mar-11 20:39
syscom02868-Mar-11 20:39 
GeneralTimeout while stream.read after retr command [modified] Pin
SokTi4-Mar-11 2:08
SokTi4-Mar-11 2:08 

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

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