Click here to Skip to main content
15,892,537 members
Articles / Programming Languages / C#

Reading Emails with OpenPop.NET

Rate me:
Please Sign up or sign in to vote.
4.87/5 (8 votes)
22 Aug 2017CPOL7 min read 39.9K   2.6K   29   9
Reading emails from an inbox with openpop.net

Introduction

In some instances within your application, you may have a time where you need to process emails from an inbox. What I hope to provide is a useful wrapper around the OpenPop.NET library http://hpop.sourceforge.net/ that provides an easy way of accessing emails from your inbox.

This comes in handy if you allow your users to forward emails into your application, send commands via the subject line to mainpulate your application remotely or even request information from your system via email from the subject line, or maybe you've written a custom support application and want to give your users the ability to open tickets via email and manage those attachments as well.

I plan on writing an article expanding upon the above examples where you can extend the functionalit of your application through email.

Background

Topics to cover are after connecting to your email account, how to read all your mail, process attachments, reading mail from a specific user or, if you have the ability to alias email accounts, read email by the "to" address and also the "from" address along with the underlying library methods used within the wrapper class.

The ability to read by the email to address could come in handy if you have a single inbox that holds all the mail sent across all customers or maybe you have the ability to alias your email accounts where your inbox is "help@mycompany.com" but you can alias it as "customer1.help@mycompany.com" and "customer2.help@mycompany.com" in order to know what emails belong to what customer of your application.

Using the code

Connecting

Connecting to OpenPop requried connection and authenticating. Need to make this call prior to taking any of the below operations against your inbox. It is important to note that you need to call the Connect method first prior to executing any other methods in this mail repository class.

public void Connect(string hostname, string username, string password, int port, bool isUseSsl)
{
    this._client = new Pop3Client();
    this._client.Connect(hostname, port, isUseSsl);
    this._client.Authenticate(username, password);
}

Reading email & attachments

This mail repository class provides the ability to read all mail in the inbox, all mail by the to Address and all mail by to and from email addresses.

The way openpop reads mail is you have to pass in a number that corresponds to the email you wish to read.

public List<Pop3Mail> GetMail()
{
    int messageCount = this._client.GetMessageCount();

    var allMessages = new List<Pop3Mail>(messageCount);

    for (int i = messageCount; i > 0; i--)
    {
        allMessages.Add(new Pop3Mail() { MessageNumber = i, Message = this._client.GetMessage(i) });
    }

    return allMessages;
}

Two other option that I'll group together is the ability to read emails from a specific sender or the ability to read emails from sender and the address the email was sent to. If your reading from a gmail inbox or some other managed inbox like that where you may not be able to alias your email address as something else, the first overload is probably the only useful option out of these two. The second overload is useful in situations where if you have a support inbox but you want to provide customers a customized support email address where they can send their issues/questions, this has been the primary use case that I've run into when handling emails.

Reading mail from specific email address
public List<Pop3Mail> GetMail(string fromAddress)
{
    int messageCount = this._client.GetMessageCount();

    var allMessages = new List<Pop3Mail>();

    for (int i = messageCount; i > 0; i--)
    {
        var msg = this._client.GetMessage(i);

        allMessages.Add(new Pop3Mail { Message = msg, MessageNumber = i });
    }

    var relevantMail = allMessages.Where(m => m.Message.Headers.From.Address == fromAddress).ToList();

    return relevantMail;
}
Reading mail from specific email address which was sent to a specific email address
public List<Pop3Mail> GetMail(string fromAddress, string toAddress)
{
    int messageCount = this._client.GetMessageCount();

    var allMessages = new List<Pop3Mail>();

    for (int i = messageCount; i > 0; i--)
    {
        var msg = this._client.GetMessage(i);

        allMessages.Add(new Pop3Mail { Message = msg, MessageNumber = i });
    }

    var relevantMail = allMessages.Where(m => m.Message.Headers.From.Address == fromAddress && m.Message.Headers.To.Any(n => n.Address == toAddress)).ToList();

    return relevantMail;
}

You'll notice a custom class of Pop3Mail being used. This is so we can map a Message and the MessageNumber of that message together for futher handling (primarily when deleting the email).

Attachments

While reading emails, if you want to collect any attachments associate to that specific message. So when looping through your mails after calling the GetMail method, you can use the following GetAttachments method to pull any attachments and download them to your local hard drive.

OpenPop provides the method of FindAllAttachments which allows you to loop over those files and handle them how you desire. In this example, as mentioned, files are downloaded locally but you could work in a file storage interface that allows you to change where files end up, for example instead of saving locally, you could upload to AWS or Azure.

public List<string> GetAttachments(Message msg)
{
    var getAttachments = new List<string>();

    var attachments = msg.FindAllAttachments();
    var attachmentdirectory = @"c:\temp\mail\attachments";

    Directory.CreateDirectory(attachmentdirectory);

    foreach (var att in attachments)
    {
        string filename = string.Format(@"{0}{1}_{2}{3}", attachmentdirectory, Path.GetFileNameWithoutExtension(att.FileName), DateTime.Now.ToString("MMddyyyyhhmmss"), Path.GetExtension(att.FileName));
        att.Save(new FileInfo(filename));

        getAttachments.Add(filename);
    }

    return getAttachments;
}

Deleting processed email

Once you've processed your emails successfully you probably will want to clear out those items so you don't duplicate any actions taken against those emails. For deleting you have 2 options, you can just delete everything or you can delete email by message number.

I would caution against deleting everything as you may run into a continuity problem where when you begin processing you have 5 emails, while processing emails a sixth one i sent but if you call the DeleteAll method, you may end up deleting the 6th unprocess email on accident.

Also, you may work into your application error handling when processing emails so if one of the emails fails your handling, you may want to hang on to that email or forward it elsewhere if any issues arise. So the DeleteAll method should only be used if you are 100% positive you want to clear out the inbox in its entirety.

public void DeleteAll()
{
    this._client.DeleteAllMessages();
}

public void Delete(int msgNumber)
{
    this._client.DeleteMessage(msgNumber);
}

Usage

The usage of this class is pretty easy and straight forward.

Connecting

Provide your hostname, username, email address, password, port number and whether or not to connect with ssl. One note here, this was written using Office 365 but should work for all email providers assuming you have the correct information to connect.

I'll leave gmail, outlook and yahoo's pop3 connection information should you need it

gmail: https://www.lifewire.com/what-are-the-gmail-pop3-settings-1170853

yahoo: https://help.yahoo.com/kb/SLN4724.html

outlook (click the "Apps that support POP and IMAP" option to expand the settings information): https://support.office.com/en-us/article/Add-your-Outlook-com-account-to-another-mail-app-73f3b178-0009-41ae-aab1-87b80fa94970

var client = new MailRepository();
client.Connect(hostname: "smtp.office.com", username: "myemailaddress@domainname.com", password: "mypassword", port: 995, isUseSsl: true);
Reading All Mail

Take note here that if you wish to access attributes of the mail itself, you can do so off the Message property of the Pop3Mail class. Also, the Delete method is called after the mail is processed/read and handled. This would be an example place where you'd want to add any logic to say if the handling of this message was successful and if so, add the MessageNumber to a list to only delete successfully processed emails from the target inbox.

var allMail = client.GetMail();

foreach (var mail in allMail)
{
    var subject = mail.Message.Headers.Subject;
    var to = string.Join(",", mail.Message.Headers.To.Select(m => m.Address));
    var from = mail.Message.Headers.From.Address;

    Console.WriteLine("Email Subject: {0}", subject);
    Console.WriteLine("Sent To: {0}", to);
    Console.WriteLine("Sent From: {0}", from);

    client.Delete(mail.MessageNumber);
}
Reading emails by From and To addresses

As mentioned, if you want to target your reading to only read emails from a specific address you simply pass in that address. A future enhancementy may be the ability to pass in a list of from email addresses to create a white list type functionality when reading emails. This could allow you to implement functionality to only read emails from a specific domain or weed out potential spam issues in that you only handle mail from authorized senders.

The second option of mailByToAndFrom is similar to the first, just that you specify the specific email address of who the mail was sent to for reading. This may be the least used overload for the GetMail but was a use case that I ran into.

var mailByTo = client.GetMail(fromAddress: "emailsFromFriendOne@outlook.com");

foreach (var mail in mailByTo)
{
    client.Delete(mail.MessageNumber);
}

var mailByToAndFrom = client.GetMail(fromAddress: "emailsFromFriendOne@outlook.com", toAddress: "emailsOnlyToThisAddress@outlook.com");

foreach (var mail in mailByToAndFrom)
{
    client.Delete(mail.MessageNumber);
}
Handling Attachments

In the below example i've used the get mail by sender overload, you can remove the from address and run this against all the emails regardless of senders as well.

When handling attachments, you call the .GetAttachments method of the MailRepository by passing in the Message itself. If there are any attachments they are added to a List of strings which supply the local file path. Within your mail loop, you'd loop over your attachments if there are any and handle those files accordingly. This list could easily work for AWS/Azure by being the pre-signed URL to a file hosted on either of these cloud providers.

var mailWithAttachments = client.GetMail(fromAddress: "emailsFromFriendTwo@outlook.com");

foreach (var mail in mailWithAttachments)
{
    var attachments = client.GetAttachments(mail.Message);

    if (attachments.Any())
    {
        foreach (var attachment in attachments)
        {
            Console.WriteLine("File Location: {0}", attachment);
        }
    }
    else
    {
        Console.WriteLine("Email has no attachments, if attachments are required, make sure to not delete this email");
    }
    client.Delete(mail.MessageNumber);
}

Mail Repository

The full repository used for handling mail from specified inbox

public class MailRepository : IMailRepository
{
    public void Connect(string hostname, string username, string password, int port, bool isUseSsl)
    {
        this._client = new Pop3Client();
        this._client.Connect(hostname, port, isUseSsl);
        this._client.Authenticate(username, password);
    }

    public List<Message> GetMail()
    {
        int messageCount = this._client.GetMessageCount();

        var allMessages = new List<Message>(messageCount);

        for (int i = messageCount; i > 0; i--)
        {
            allMessages.Add(this._client.GetMessage(i));
        }

        return allMessages;
    }

    public List<Pop3Mail> GetMail(string toAddress)
    {
        int messageCount = this._client.GetMessageCount();

        var allMessages = new List<Pop3Mail>();

        for (int i = messageCount; i > 0; i--)
        {
            var msg = this._client.GetMessage(i);

            allMessages.Add(new Pop3Mail { MailMsg = msg, MessageNumber = i });
        }

        var relevantMail = allMessages.Where(m => m.MailMsg.Headers.To.Any(n => n.Address == toAddress)).ToList();

        return relevantMail;
    }

    public List<Pop3Mail> GetMail(string toAddress, string fromAddress)
    {
        int messageCount = this._client.GetMessageCount();

        var allMessages = new List<Pop3Mail>();

        for (int i = messageCount; i > 0; i--)
        {
            var msg = this._client.GetMessage(i);

            allMessages.Add(new Pop3Mail { MailMsg = msg, MessageNumber = i });
        }

        var relevantMail = allMessages.Where(m => m.MailMsg.Headers.From.Address == fromAddress && m.MailMsg.Headers.To.Any(n => n.Address == toAddress)).ToList();

        return relevantMail;
    }

    public List<string> GetAttachments(Message msg)
    {
        var getAttachments = new List<string>();

        var attachments = msg.FindAllAttachments();
        var attachmentdirectory = @"c:\temp\mail\attachments";

        Directory.CreateDirectory(attachmentdirectory);

        foreach (var att in attachments)
        {
            string filename = string.Format(@"{0}{1}_{2}{3}", attachmentdirectory, Path.GetFileNameWithoutExtension(att.FileName), DateTime.Now.ToString("MMddyyyyhhmmss"), Path.GetExtension(att.FileName));
            att.Save(new FileInfo(filename));

            getAttachments.Add(filename);
        }

        return getAttachments;
    }

    public void DeleteAll()
    {
        this._client.DeleteAllMessages();
    }

    public void Delete(int msgNumber)
    {
        this._client.DeleteMessage(msgNumber);
    }

    private Pop3Client _client { get; set; }
}

public class Pop3Mail
{
    public int MessageNumber { get; set; }
    public Message MailMsg { get; set; }
}

Points of Interest

OpenPop provides a lot more functionality than just the above few methods provided in the wrapper class. The functionality implemented is just a way to easily expose the most common requirements I've run into when building email reader functionality into my applications.

This class could be extended to allow for white listing functionality by reading emails from a list of approved senders, adding support for different file storage options, and probably a bunch of other things that I haven't run into yet so feel free to add to it based on whatever your needs may be. You could also move the settings from being hard coded into your app.config or work in a connection to your database to pull the settings from your table.

At some point I'll write an article combining the ability to read emails on a scheduler using Quartz with advanced functionality handling that would allow you to extend functionality of your application to power users or provide the ability for your customers to email in support tickets or add attachments to existing support tickets.

History

8/22/2017 - Initial article creation

License

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


Written By
Software Developer
United States United States
I'm a software developer and enjoy .net, angular, mvc, and many other different languages.

Comments and Discussions

 
Questioncheck folders Pin
Member 1413336325-Apr-23 7:40
Member 1413336325-Apr-23 7:40 
QuestionThe server did not respond with a + response. The response was: "-ERR Logon failure: unknown user name or bad password." Pin
kaneshka31-Jan-22 1:25
kaneshka31-Jan-22 1:25 
AnswerRe: The server did not respond with a + response. The response was: "-ERR Logon failure: unknown user name or bad password." Pin
David_Wimbley22-Feb-22 17:33
professionalDavid_Wimbley22-Feb-22 17:33 
AnswerRe: The server did not respond with a + response. The response was: "-ERR Logon failure: unknown user name or bad password." Pin
David_Wimbley25-Feb-22 20:37
professionalDavid_Wimbley25-Feb-22 20:37 
QuestionHow to Read Corresponding Email Message like Body Message Pin
jkumars31-Jul-18 1:23
jkumars31-Jul-18 1:23 
QuestionGreat wrapper but missing one thing Pin
R Rourk11-Apr-18 7:30
R Rourk11-Apr-18 7:30 
AnswerRe: Great wrapper but missing one thing Pin
David_Wimbley30-Apr-18 5:50
professionalDavid_Wimbley30-Apr-18 5:50 
QuestionOpePOP Certificate Pin
nrodriguez20148-Feb-18 5:25
nrodriguez20148-Feb-18 5:25 
AnswerRe: OpePOP Certificate Pin
David_Wimbley30-Apr-18 5:51
professionalDavid_Wimbley30-Apr-18 5:51 

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.