Receiving Emails In C#/.NET

How do you receive emails in C#/.NET? Surely it can't be that hard, after all, electronic mail existed decades before the Internet. As you may know, emails are sent using the Simple Mail Transfer Protocol (SMTP), and received using Internet Message Access Protocol (IMAP) - which replaced POP3.

As you may be aware from my previous article, SMTP has a system library available. IMAP does not, but here again MailKit comes to the rescue as the package recommended by even the likes of Microsoft.

MailKit and MimeKit

Here's how to download emails using MailKit and the MimeKit dependency:

using (var imapClient = new ImapClient())
{
	imapClient.Connect("imap.example.com", 993, true);
	imapClient.Authenticate("username", "password");
	imapClient.Inbox.Open(FolderAccess.ReadOnly);
	var uids = imapClient.Inbox.Search(SearchQuery.All);
	foreach (var uid in uids)
	{
		var mimeMessage = imapClient.Inbox.GetMessage(uid);
		// mimeMessage.WriteTo($"{uid}.eml"); // for testing
		yield return mimeMessage;
	}
	imapClient.Disconnect(true);
}

That's not too bad. Let's try a more realistic scenario:

var mimeMessages = new List<MimeMessage>();
using (var imapClient = new ImapClient(new ProtocolLogger("Logs/ImapClient.txt")))
{
	await imapClient.ConnectAsync("imap.gmail.com", 993, SecureSocketOptions.SslOnConnect, cancellationToken);
	await imapClient.AuthenticateAsync("[email protected]", "App1icati0nP455w0rd", cancellationToken);
	var mailFolder = await imapClient.GetFolderAsync("INBOX/Subfolder", cancellationToken);
	await mailFolder.OpenAsync(FolderAccess.ReadOnly, cancellationToken);
	var messageSummaries = await mailFolder.FetchAsync(0, 249, MessageSummaryItems.UniqueId, cancellationToken);
	foreach (var msg in messageSummaries)
	{
		var mimeMessage = await mailFolder.GetMessageAsync(msg.UniqueId, cancellationToken);
        mimeMessages.Add(mimeMessage);
	}
    await mailFolder.CloseAsync(false);
	await imapClient.DisconnectAsync(true);
}

You can also move the client definition to a separate appsettings.json file like so (if adding it manually then make sure to set Properties -> Copy to Output Directory -> Copy if newer):

{
  "EmailReceiver": {
    "MailFolderName": "INBOX",
    "ImapHost": "imap.gmail.com",
    "ImapPort": 993,
    "ImapCredential": {
      "UserName": "[email protected]",
      "Password": "App1icati0nP455w0rd"
    },
    "ProtocolLog": "Logs\\ImapClient.txt"
  },
  "FolderMonitor": {
    "IgnoreExistingMailOnConnect": false,
    "MessageSummaryItems": "None",
    "IdleMinutes": 9,
    "MaxRetries": 3
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "MailKitSimplified.Receiver.Services.MailKitProtocolLogger": "Debug"
    }
  }
}

You could read those settings with Microsoft.Extensions.Configuration.Json, parse the configuration to a predefined object type, and use them as follows (later we'll re-use MailKitSimplified.Receiver.Models.EmailReceiverOptions):

using Microsoft.Extensions.Configuration;

var configuration = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build();
var emailReceiverOptions = configuration.GetRequiredSection(EmailReceiverOptions.SectionName).Get<EmailReceiverOptions>();

var messageSummaries = new List<IMessageSummary>();
using (var imapClient = new ImapClient(new ProtocolLogger(emailReceiverOptions.ProtocolLog)))
{
	await imapClient.ConnectAsync(emailReceiverOptions.ImapHost, 993, SecureSocketOptions.SslOnConnect, cancellationToken);
	await imapClient.AuthenticateAsync(Encoding.Default, emailReceiverOptions.ImapCredential, cancellationToken);
	var mailFolder = await imapClient.GetFolderAsync(emailReceiverOptions.MailFolderName, cancellationToken);
	await mailFolder.OpenAsync(FolderAccess.ReadOnly, cancellationToken);
	messageSummaries = await mailFolder.FetchAsync(0, -1, MessageSummaryItems.UniqueId, cancellationToken);
	await mailFolder.CloseAsync(false);
	await imapClient.DisconnectAsync(true);
}

Not too bad, but usually you want to download new emails as they arrive. How would you do that? I won't write it out as it's 251 lines of code, but can see an example here.

It's starting to get a little more convoluted now. It can be a bit of a learning curve to set up and use MailKit in more advanced scenarios. Is there a simpler way?

There is.

MailKitSimplified.Receiver

By adding the MailKitSimplified.Receiver package from NuGet, you can do everything MailKit does as that's used under the hood, but all of the pain points go away:

using var imapReceiver = ImapReceiver.Create("imap.example.com:993")
    .SetCredential("username", "password");
var uids = await imapReceiver.MailFolderClient
    .SearchAsync(SearchQuery.All, cancellationToken);

That was the first example above: 13 lines down to 2-4 lines of code! (Hint: you can use GetAwaiter().GetResult() instead of await if you're not ready to make the transition to async yet, but async is better.)

What about the second example?

using var imapReceiver = ImapReceiver.Create("imap.gmail.com:993")
    .SetCredential("[email protected]", "App1icati0nP455w0rd");
var mimeMessages = await imapReceiver.ReadMail
    .Take(250).GetMimeMessagesAsync();

16 lines down to 2-4 lines of code!

What about defining the receiver options in an application configuration file? We could do that manually with MailKitSimplified.Receiver.Models.EmailReceiverOptions like we did before:

using var imapReceiver = ImapReceiver.Create(emailReceiverOptions);
var messageSummaries = await imapReceiver.ReadMail
    .GetMessageSummariesAsync(MessageSummaryItems.UniqueId);

The third example then becomes effectively two lines as well!

If you know how dependency injection works then it's even easier using MailKitSimplified.Receiver:

services.AddMailKitSimplifiedEmailReceiver(context.Configuration);

What about the mail folder idle client? Here's how to do it manually:

using var imapReceiver = ImapReceiver.Create(emailReceiverOptions);
await imapReceiver.MonitorFolder
    .SetIgnoreExistingMailOnConnect().SetMessageSummaryItems()
    .OnMessageArrival((messageSummary) => OnArrivalAsync(messageSummary))
    .IdleAsync();

251 lines down to 2-5 lines of code.

Normally you'd use dependency injection to get IMailFolderMonitor directly, and if you do it becomes one line of code:

await mailFolderMonitor.OnMessageArrival(ReadAsync).IdleAsync();

It doesn't get much better than that!

Next Steps

To see more examples and request new features, head over to https://github.com/danzuep/MailKitSimplified.

Read on to learn about replies and forwarding in my next article, Forwarding and Replying To Emails In C#/.NET!


Similar Articles