Skip to main content

How to Download Messages from an IMAP Server Using C#

This article provides a solution to automate the download of email messages (including embedded files and attachments) from an IMAP server using C# and MailBee.NET.

Download Source Code - 10 KB

Introduction

This week I completed an interesting year-end assignment for another software company, InSite Systems. For the purposes of safe-keeping and due diligence, copies of all email messages sent from (and received by) team members had to be downloaded from the company's IMAP mail server and stored on an offline file system.

I created a simple C# console application to do the job, and I thought I'd share the source code with readers here.
You will need an IMAP class library for this solution. I found a great set of components from a company called AfterLogic: MailBee.NET Objects
The solution downloads only one folder for one user at a time, so if you want to automate the download of multiple folders and/or multiple users then you'll need to roll some additional code of your own.

The intent here was less admin-focused and more user-focused, so individuals have the ability to run the application for themselves and decide what they want included in the offline archive. In this case, my users were all high-end IT professionals, so they wanted this kind of fine-grained control, and they liked having the ability to modify appSettings directly in the configuration file. (In fact, I suspect a few of them modified the code as well, to suit some of their own individual requirements.)

Using the Code

The directory structure for the output looks like this:
  • C:\Output
    • insitesystems.com
      • jane.doe
        • Inbox
          • 2015
            • 01
            • 02
              • 01
              • 02
              • 03
              • 04
              • 05
              • 06
              • 07
              • 08
              • 09
              • 10
              • 11
              • 12
              • 13
              • 14
              • 15
              • 16
              • 17
              • 18
              • 19
              • 20
              • 21
              • 22
              • 23
              • 24
              • 25
              • 26
              • 27
              • 28
            • 03
            • 04
            • 05
            • 06
            • 07
            • 08
            • 09
            • 10
            • 11
            • 12
        • Sent Items
      • jim.bob
        • Inbox
        • Sent Items
      • john.doe
        • Inbox
        • Sent Items
In each user folder (e.g. Inbox) is a subfolder for the year; in each year is a subfolder for each month; in each month is a subfolder for each day. Each message is saved to an HTML file (and an EML file), for which the file name is derived from the subject line on the message.

The code assumes no two messages have the same subject line on the same date. The code ignores duplicates in this scenario, so if you don't want this behavior then you'll need to modify the code for this too. (For example, you could use GUIDs or assign sequential number suffixes to duplicate subjects to ensure uniqueness.)

There are a few things you'll need to do before you can run the code in your own environment.

Step 1

You'll need to download the MailBee.NET Objects class library (or the MailBee.NET IMAP Bundle). You can download a free 30-day full-featured trial.

Step 2

Update the application settings and the connection string settings in App.config to match your local environment. In particular:
  • Make sure the MailDump.OutputPath setting references a valid physical location in your local file system.
  • Update the settings for your email account and mail server.
  • Specify the name of the IMAP folder from which you want to download messages.
  • Provide a valid license key for the MailBee.ImapMail library.
  • Set DeleteMessagesAfterDownload to True if you want messages deleted from the server after they have been downloaded and saved.

Step 3

Execute the code generator from a console window or from a PowerShell window.

The code for the main method is simple and lightweight:

/// <summary>
/// Downloads messages from an IMAP server and saves them to the local file system.
/// </summary>
private static void Main()
{
    MailBee.Global.AutodetectPortAndSslMode = false;
    var imap = OpenConnection();
    var count = GetMessageCount(imap);

    for (var i = 1; i <= count; i++)
    {
        Console.WriteLine("Downloading message {0} of {1}...", i, count);
        var message = imap.DownloadEntireMessage(i, false);
        message.Parser.PlainToHtmlMode = PlainToHtmlAutoConvert.IfNoHtml;
        FileManager.SaveMessage(message, AppSettings.FilePath);
    }

    if (count > 1 && AppSettings.DeleteMessagesAfterDownload )
    {
        Console.WriteLine("Deleting messages {0} through {1}...", 1, count );
        var indexSet = string.Format("{0}:{1}", 1, count);
        imap.DeleteMessages(indexSet, false);
    }

    CloseConnection(imap);
}

The code for saving a message to the file system looks like this:

public static void SaveMessage(MailMessage message, string root)
{
    var folder = Path.Combine(root, string.Format("{0:yyyy}\\{0:MM}\\{0:dd}", message.Date));

    // Disregard duplicates when multiple messages have the same date and subject.
    var name = message.Subject ?? "No Subject";
    name = ToValidFileName(name, folder, message.Attachments);

    // Save an HTML copy of the message.
    var path = CreatePath( folder, name + ".html" );
    message.Parser.WorkingFolder = Path.Combine(folder, name);
    message.SaveHtmlAndRelatedFiles(path);
    SetTimestamp(path, message.Date);

    // Save an EML copy of the message.
    path = CreatePath(folder, name + ".eml");
    using (var fs = new FileStream(path, FileMode.Create))
    message.SaveMessage(fs);
    SetTimestamp(path, message.Date);

    // Save message attachments.
    if (message.Attachments.Count > 0)
    {
        foreach (Attachment attachment in message.Attachments)
        {
            if (attachment.Name != "")
            {
                path = CreatePath(Path.Combine(folder, name), attachment.Name);
                attachment.Save(path, true);
            }
        }
    }
}

Fair warning: this is "utility" code only, and not production quality. There are no unit tests and the exception handling is not bullet-proof. On the other hand, the entire solution contains less than 100 lines of code, so it should be very easy to modify and customize.

All output files are written to the location specified by the appSetting key:

MailDump.OutputPath

Comments, questions, and feedback are welcome - let me know if you find this solution helpful.

History

January 1, 2016 - First draft

Comments

Popular posts from this blog

Code of Ethics and Professional Practice

This code of ethics was developed by the IEEE-CS / ACM joint task force on Software Engineering Ethics and Professional Practices . It was jointly approved by the ACM and the IEEE-CS as the standard for teaching and practicing software engineering. This summary version of the code of ethics summarizes aspirations at a high level of abstraction. The clauses included in the full version of this code give examples and details of how these aspirations change the way we act as software engineering professionals. Without the aspirations, the details can become legalistic and tedious; without the details, the aspirations can become high sounding but empty; together, the aspirations and the details form a cohesive code. Software engineers shall commit themselves to making the analysis, specification, design, development, testing, and maintenance of software a beneficial and respected profession. In accordance with their commitment to the health, safety and welfare of the public, software ...

The High Cost of Low Software Estimates

Estimating too low on a software development project can destroy your budget and ruin your project schedule. Here's the reason why. I have been developing software for more than twenty years, and I have experienced first-hand the consequences of estimating too low (and estimating too high). Estimates at the beginning of a software project are rarely accurate, given the team's limited knowledge of the project. More often than not, users don't yet know all of their own requirements for the system to be developed, and developers don't yet know everything about the domain in which the solution will operate. Building software is a process of continuous improvement, and a well-run project attacks the areas of highest variability first in order to reduce uncertainties as quickly as possible. Ideally, your estimate for a software project should be allowed to evolve along with the software itself. And remember: the potential for an exponential overrun on cost and/or sche...