# Frequently Asked Questions
## Question Index
### General
* [Are MimeKit and MailKit completely free? Can I use them in my proprietary product(s)?](#completely-free)
* [Why do I get `NotSupportedException: No data is available for encoding ######. For information on defining a custom encoding, see the documentation for the Encoding.RegisterProvider method.`?](#register-provider)
* [Why do I get a `TypeLoadException` when I try to create a new MimeMessage?](#type-load-exception)
* [Why do I get `"MailKit.Security.SslHandshakeException: An error occurred while attempting to establish an SSL or TLS connection."` when I try to Connect?](#ssl-handshake-exception)
* [How can I get a protocol log for IMAP, POP3, or SMTP to see what is going wrong?](#protocol-log)
* [Why doesn't MailKit find some of my GMail POP3 or IMAP messages?](#gmail-hidden-messages)
* [How can I access GMail using MailKit?](#gmail-access)
* [How can I log in to a GMail account using OAuth 2.0?](#gmail-oauth2)
### Messages
* [How can I create a message with attachments?](#create-attachments)
* [How can I get the main body of a message?](#message-body)
* [How can I tell if a message has attachments?](#has-attachments)
* [Why doesn't the `MimeMessage` class implement `ISerializable` so that I can serialize a message to disk and read it back later?](#serialize-message)
* [How can I parse messages?](#load-messages)
* [How can I save messages?](#save-messages)
* [How can I save attachments?](#save-attachments)
* [How can I get the email addresses in the From, To, and Cc headers?](#address-headers)
* [Why do attachments with unicode filenames appear as "ATT0####.dat" in Outlook?](#untitled-attachments)
* [How can I decrypt PGP messages that are embedded in the main message text?](#decrypt-inline-pgp)
* [How can I reply to a message?](#reply-message)
* [How can I forward a message?](#forward-message)
* [Why does text show up garbled in my ASP.NET Core / .NET Core / .NET 5 app?](#garbled-text)
### ImapClient
* [How can I get the number of unread messages in a folder?](#imap-unread-count)
* [How can I search for messages delivered between two dates?](#imap-search-date-range)
* [What does "The ImapClient is currently busy processing a command." mean?](#imap-client-busy)
* [Why do I get InvalidOperationException: "The folder is not currently open."?](#imap-folder-not-open-exception)
* [Why doesn't ImapFolder.MoveTo() move the message out of the source folder?](#imap-move-does-not-move)
* [How can I mark messages as read using IMAP?](#imap-mark-as-read)
* [How can I re-synchronize the cache for an IMAP folder?](#imap-folder-resync)
* [How can I login using a shared mailbox in Office365?](#office365-shared-mailboxes)
### SmtpClient
* [Why doesn't the message show up in the "Sent Mail" folder after sending it?](#smtp-sent-folder)
* [How can I send email to the SpecifiedPickupDirectory?](#smtp-specified-pickup-directory)
* [How can I request a notification when the message is read by the user?](#smtp-request-read-receipt)
* [How can I process a read receipt notification?](#smtp-process-read-receipt)
## General
### Q: Are MimeKit and MailKit completely free? Can I use them in my proprietary product(s)?
Yes. MimeKit and MailKit are both completely free and open source. They are both covered under the
[MIT](https://opensource.org/licenses/MIT) license.
### Q: Why do I get `NotSupportedException: No data is available for encoding ######. For information on defining a custom encoding, see the documentation for the Encoding.RegisterProvider method.`?
In .NET Core, Microsoft decided to split out the non-Unicode text encodings into a separate NuGet package called
[System.Text.Encoding.CodePages](https://www.nuget.org/packages/System.Text.Encoding.CodePages).
MimeKit already pulls in a reference to this NuGet package, so you shouldn't need to add a reference to it in
your project. That said, you will still need to register the encoding provider. It is recommended that you add
the following line of code to your program initialization (e.g. the beginning of your program's Main() method):
```csharp
System.Text.Encoding.RegisterProvider (System.Text.CodePagesEncodingProvider.Instance);
```
### Q: Why do I get a `TypeLoadException` when I try to create a new MimeMessage?
This only seems to happen in cases where the application is built for .NET Framework (v4.x) and seems to be most
common for ASP.NET web applications that were built using Visual Studio 2019 (it is unclear whether this happens
with Visual Studio 2022 as well).
The issue is that some (older?) versions of MSBuild do not correctly generate `\*.dll.config`, `app.config`
and/or `web.config` files with proper assembly version binding redirects.
If this problem is happening to you, make sure to use MimeKit and MailKit >= v4.0 which include `MimeKit.dll.config`
and `MailKit.dll.config`.
The next step is to manually edit your application's `app.config` (or `web.config`) to add a binding redirect
for `System.Runtime.CompilerServices.Unsafe`:
```xml
urls that refer to images embedded within the message with
// "file://" urls that the browser control will actually be able to load.
void HtmlTagCallback (HtmlTagContext ctx, HtmlWriter htmlWriter)
{
if (ctx.TagId == HtmlTagId.Meta && !ctx.IsEndTag) {
bool isContentType = false;
ctx.WriteTag (htmlWriter, false);
// replace charsets with "utf-8" since our output will be in utf-8 (and not whatever the original charset was)
foreach (var attribute in ctx.Attributes) {
if (attribute.Id == HtmlAttributeId.Charset) {
htmlWriter.WriteAttributeName (attribute.Name);
htmlWriter.WriteAttributeValue ("utf-8");
} else if (isContentType && attribute.Id == HtmlAttributeId.Content) {
htmlWriter.WriteAttributeName (attribute.Name);
htmlWriter.WriteAttributeValue ("text/html; charset=utf-8");
} else {
if (attribute.Id == HtmlAttributeId.HttpEquiv && attribute.Value != null
&& attribute.Value.Equals ("Content-Type", StringComparison.OrdinalIgnoreCase))
isContentType = true;
htmlWriter.WriteAttribute (attribute);
}
}
} else if (ctx.TagId == HtmlTagId.Image && !ctx.IsEndTag && stack.Count > 0) {
ctx.WriteTag (htmlWriter, false);
// replace the src attribute with a file:// URL
foreach (var attribute in ctx.Attributes) {
if (attribute.Id == HtmlAttributeId.Src) {
if (!TryGetImage (attribute.Value, out var image)) {
htmlWriter.WriteAttribute (attribute);
continue;
}
// Note: you can either use a "file://" URI or you can use a
// "data:" URI, the choice is yours.
var uri = GetFileUri (image, attribute.Value);
//var uri = GetDataUri (image);
htmlWriter.WriteAttributeName (attribute.Name);
htmlWriter.WriteAttributeValue (uri);
} else {
htmlWriter.WriteAttribute (attribute);
}
}
} else if (ctx.TagId == HtmlTagId.Body && !ctx.IsEndTag) {
ctx.WriteTag (htmlWriter, false);
// add and/or replace oncontextmenu="return false;"
foreach (var attribute in ctx.Attributes) {
if (attribute.Name.Equals ("oncontextmenu", StringComparison.OrdinalIgnoreCase))
continue;
htmlWriter.WriteAttribute (attribute);
}
htmlWriter.WriteAttribute ("oncontextmenu", "return false;");
} else {
// pass the tag through to the output
ctx.WriteTag (htmlWriter, true);
}
}
protected override void VisitTextPart (TextPart entity)
{
TextConverter converter;
if (body != null) {
// since we've already found the body, treat this as an attachment
attachments.Add (entity);
return;
}
if (entity.IsHtml) {
converter = new HtmlToHtml {
HtmlTagCallback = HtmlTagCallback
};
} else if (entity.IsFlowed) {
var flowed = new FlowedToHtml ();
string delsp;
if (entity.ContentType.Parameters.TryGetValue ("delsp", out delsp))
flowed.DeleteSpace = delsp.Equals ("yes", StringComparison.OrdinalIgnoreCase);
converter = flowed;
} else {
converter = new TextToHtml ();
}
body = converter.Convert (entity.Text);
}
protected override void VisitTnefPart (TnefPart entity)
{
// extract any attachments in the MS-TNEF part
attachments.AddRange (entity.ExtractAttachments ());
}
protected override void VisitMessagePart (MessagePart entity)
{
// treat message/rfc822 parts as attachments
attachments.Add (entity);
}
protected override void VisitMimePart (MimePart entity)
{
// realistically, if we've gotten this far, then we can treat this as an attachment
// even if the IsAttachment property is false.
attachments.Add (entity);
}
}
```
And the way you'd use this visitor might look something like this:
```csharp
void Render (MimeMessage message)
{
var tmpDir = Path.Combine (Path.GetTempPath (), message.MessageId);
var visitor = new HtmlPreviewVisitor (tmpDir);
Directory.CreateDirectory (tmpDir);
message.Accept (visitor);
DisplayHtml (visitor.HtmlBody);
DisplayAttachments (visitor.Attachments);
}
```
Once you've rendered the message using the above technique, you'll have a list of attachments that
were not used, even if they did not match the simplistic criteria used by the `MimeMessage.Attachments`
property.
### Q: Why doesn't the `MimeMessage` class implement `ISerializable` so that I can serialize a message to disk and read it back later?
The MimeKit API was designed to use the existing MIME format for serialization. In light of this, the ability
to use the .NET serialization API and format did not make much sense to support.
You can easily serialize a [MimeMessage](https://www.mimekit.net/docs/html/T_MimeKit_MimeMessage.htm) to a stream using the
[WriteTo](https://www.mimekit.net/docs/html/Overload_MimeKit_MimeMessage_WriteTo.htm) methods.
For more information on this topic, see the following other two topics:
* [How can I parse messages?](#load-messages)
* [How can I save messages?](#save-messages)
### Q: How can I parse messages?
One of the more common operations that MimeKit is meant for is parsing email messages from arbitrary streams.
There are two ways of accomplishing this task.
The first way is to use one of the [Load](https://www.mimekit.net/docs/html/Overload_MimeKit_MimeMessage_Load.htm) methods
on `MimeMessage`:
```csharp
// Load a MimeMessage from a stream
var message = MimeMessage.Load (stream);
```
Or you can load a message from a file path:
```csharp
// Load a MimeMessage from a file path
var message = MimeMessage.Load ("message.eml");
```
The second way is to use the [MimeParser](https://www.mimekit.net/docs/html/T_MimeKit_MimeParser.htm) class. For the most
part, using the `MimeParser` directly is not necessary unless you wish to parse a Unix mbox file stream. However, this is
how you would do it:
```csharp
// Load a MimeMessage from a stream
var parser = new MimeParser (stream, MimeFormat.Entity);
var message = parser.ParseMessage ();
```
For Unix mbox file streams, you would use the parser like this:
```csharp
// Load every message from a Unix mbox
var parser = new MimeParser (stream, MimeFormat.Mbox);
while (!parser.IsEndOfStream) {
var message = parser.ParseMessage ();
// do something with the message
}
```
### Q: How can I save messages?
One you've got a [MimeMessage](https://www.mimekit.net/docs/html/T_MimeKit_MimeMessage.htm), you can save
it to a file using the [WriteTo](https://mimekit.net/docs/html/Overload_MimeKit_MimeMessage_WriteTo.htm) method:
```csharp
message.WriteTo ("message.eml");
```
The `WriteTo` method also has overloads that allow you to write the message to a `Stream` instead.
By default, the `WriteTo` method will save the message using DOS line-endings on Windows and Unix
line-endings on Unix-based systems such as macOS and Linux. You can override this behavior by
passing a [FormatOptions](https://mimekit.net/docs/html/T_MimeKit_FormatOptions.htm) argument to
the method:
```csharp
// clone the default formatting options
var format = FormatOptions.Default.Clone ();
// override the line-endings to be DOS no matter what platform we are on
format.NewLineFormat = NewLineFormat.Dos;
message.WriteTo (format, "message.eml");
```
Note: While it may seem like you can safely use the `ToString` method to serialize a message,
***DON'T DO IT!*** This is ***not*** safe! MIME messages cannot be accurately represented as
strings due to the fact that each MIME part of the message *may* be encoded in a different
character set, thus making it impossible to convert the message into a unicode string using a
single charset to do the conversion (which is *exactly* what `ToString` does).
### Q: How can I save attachments?
If you've already got a [MimePart](https://www.mimekit.net/docs/html/T_MimeKit_MimePart.htm) that represents
the attachment that you'd like to save, here's how you might save it:
```csharp
using (var stream = File.Create (fileName))
attachment.Content.DecodeTo (stream);
```
Pretty simple, right?
But what if your attachment is actually a [MessagePart](https://www.mimekit.net/docs/html/T_MimeKit_MessagePart.htm)?
To save the content of a `message/rfc822` part, you'd use the following code snippet:
```csharp
using (var stream = File.Create (fileName))
attachment.Message.WriteTo (stream);
```
If you are iterating over all of the attachments in a message, you might do something like this:
```csharp
foreach (var attachment in message.Attachments) {
var fileName = attachment.ContentDisposition?.FileName ?? attachment.ContentType.Name;
using (var stream = File.Create (fileName)) {
if (attachment is MessagePart) {
var rfc822 = (MessagePart) attachment;
rfc822.Message.WriteTo (stream);
} else {
var part = (MimePart) attachment;
part.Content.DecodeTo (stream);
}
}
}
```
### Q: How can I get the email addresses in the From, To, and Cc headers?
The [From](https://www.mimekit.net/docs/html/P_MimeKit_MimeMessage_From.htm),
[To](https://www.mimekit.net/docs/html/P_MimeKit_MimeMessage_To.htm), and
[Cc](https://www.mimekit.net/docs/html/P_MimeKit_MimeMessage_Cc.htm) properties of a
[MimeMessage](https://www.mimekit.net/docs/html/T_MimeKit_MimeMessage.htm) are all of type
[InternetAddressList](https://www.mimekit.net/docs/html/T_MimeKit_InternetAddressList.htm). An
`InternetAddressList` is a list of
[InternetAddress](https://www.mimekit.net/docs/html/T_MimeKit_InternetAddress.htm) items. This is
where most people start to get lost because an `InternetAddress` is an abstract class that only
really has a [Name](https://www.mimekit.net/docs/html/P_MimeKit_InternetAddress_Name.htm) property.
As you've probably already discovered, the `Name` property contains the name of the person
(if available), but what you want is his or her email address, not their name.
To get the email address, you'll need to figure out what subclass of address each `InternetAddress`
really is. There are 2 subclasses of `InternetAddress`:
[GroupAddress](https://www.mimekit.net/docs/html/T_MimeKit_GroupAddress.htm) and
[MailboxAddress](https://www.mimekit.net/docs/html/T_MimeKit_MailboxAddress.htm).
A `GroupAddress` is a named group of more `InternetAddress` items that are contained within the
[Members](https://www.mimekit.net/docs/html/P_MimeKit_GroupAddress_Members.htm) property. To get
an idea of what a group address represents, consider the following examples:
```
To: My Friends: Joey
htmlWriter.WriteEndTag (HtmlTagId.BlockQuote);
// pass the
htmlWriter.WriteStartTag (HtmlTagId.BlockQuote); htmlWriter.WriteAttribute (HtmlAttributeId.Style, "border-left: 1px #ccc solid; margin: 0 0 0 .8ex; padding-left: 1ex;"); ctx.InvokeCallbackForEndTag = true; } } else { // pass the tag through to the output ctx.WriteTag (htmlWriter, true); } } string QuoteText (string text) { using (var quoted = new StringWriter ()) { quoted.WriteLine (GetOnDateSenderWrote (original)); using (var reader = new StringReader (text)) { string line; while ((line = reader.ReadLine ()) != null) { quoted.Write ("> "); quoted.WriteLine (line); } } return quoted.ToString (); } } protected override void VisitTextPart (TextPart entity) { string text; if (entity.IsHtml) { var converter = new HtmlToHtml { HtmlTagCallback = HtmlTagCallback }; text = converter.Convert (entity.Text); } else if (entity.IsFlowed) { var converter = new FlowedToText (); text = converter.Convert (entity.Text); text = QuoteText (text); } else { // quote the original message text text = QuoteText (entity.Text); } var part = new TextPart (entity.ContentType.MediaSubtype.ToLowerInvariant ()) { Text = text }; Push (part); } protected override void VisitMessagePart (MessagePart entity) { // don't descend into message/rfc822 parts } protected override void VisitMimePart (MimePart entity) { if (isRelated > 0 || !entity.IsAttachment) { var parent = stack.Peek (); parent.Add (entity); } } } ``` ```csharp public static MimeMessage Reply (MimeMessage message, MailboxAddress from, bool replyToAll) { var visitor = new ReplyVisitor (from, replyToAll); visitor.Visit (message); return visitor.Reply; } ``` ### Q: How can I forward a message? There are 2 common ways of forwarding a message: attaching the original message as an attachment and inlining the message body much like replying typically does. Which method you choose is up to you. To forward a message by attaching it as an attachment, you would do something like this: ```csharp public static MimeMessage Forward (MimeMessage original, MailboxAddress from, IEnumerableto) { var message = new MimeMessage (); message.From.Add (from); message.To.AddRange (to); // set the forwarded subject if (!original.Subject?.StartsWith ("FW:", StringComparison.OrdinalIgnoreCase)) message.Subject = "FW: " + (original.Subject ?? string.Empty); else message.Subject = original.Subject; // create the main textual body of the message var text = new TextPart ("plain") { Text = "Here's the forwarded message:" }; // create the message/rfc822 attachment for the original message var rfc822 = new MessagePart { Message = original }; // create a multipart/mixed container for the text body and the forwarded message var multipart = new Multipart ("mixed"); multipart.Add (text); multipart.Add (rfc822); // set the multipart as the body of the message message.Body = multipart; return message; } ``` To forward a message by inlining the original message's text content, you can do something like this: ```csharp public static MimeMessage Forward (MimeMessage original, MailboxAddress from, IEnumerable to) { var message = new MimeMessage (); message.From.Add (from); message.To.AddRange (to); // set the forwarded subject if (!original.Subject?.StartsWith ("FW:", StringComparison.OrdinalIgnoreCase)) message.Subject = "FW: " + (original.Subject ?? string.Empty); else message.Subject = original.Subject; // quote the original message text using (var text = new StringWriter ()) { text.WriteLine (); text.WriteLine ("-------- Original Message --------"); text.WriteLine ("Subject: {0}", original.Subject ?? string.Empty); text.WriteLine ("Date: {0}", DateUtils.FormatDate (original.Date)); text.WriteLine ("From: {0}", original.From); text.WriteLine ("To: {0}", original.To); text.WriteLine (); text.Write (original.TextBody); message.Body = new TextPart ("plain") { Text = text.ToString () }; } return message; } ``` Keep in mind that not all messages will have a `TextBody` available, so you'll have to find a way to handle those cases. ### Q: Why does text show up garbled in my ASP.NET Core / .NET Core / .NET 5 app? .NET Core (and ASP.NET Core by extension) and .NET 5 only provide the Unicode encodings, ASCII and ISO-8859-1 by default. Other text encodings are not available to your application unless your application [registers](https://docs.microsoft.com/en-us/dotnet/api/system.text.encoding.registerprovider?view=net-5.0) the encoding provider that provides all of the additional encodings. First, add a package reference for the [System.Text.Encoding.CodePages](https://www.nuget.org/packages/System.Text.Encoding.CodePages) nuget package to your project and then register the additional text encodings using the following code snippet: ```csharp System.Text.Encoding.RegisterProvider (System.Text.CodePagesEncodingProvider.Instance); ``` Note: The above code snippet should be safe to call in .NET Framework versions >= 4.6 as well. ## ImapClient ### Q: How can I get the number of unread messages in a folder? If the folder is open (via [Open](https://www.mimekit.net/docs/html/Overload_MailKit_Net_Imap_ImapFolder_Open.htm)), then the [ImapFolder.Unread](https://www.mimekit.net/docs/html/P_MailKit_MailFolder_Unread.htm) property will be kept up to date (at least as-of the latest command issued to the server). If the folder *isn't* open, then you will need to query the unread state of the folder using the [Status](https://www.mimekit.net/docs/html/M_MailKit_Net_Imap_ImapFolder_Status.htm) method with the appropriate [StatusItems](https://www.mimekit.net/docs/html/T_MailKit_StatusItems.htm) flag(s). For example, to get the total *and* unread counts, you can do this: ```csharp folder.Status (StatusItems.Count | StatusItems.Unread); int total = folder.Count; int unread = folder.Unread; ``` ### Q: How can I search for messages delivered between two dates? The obvious solution is: ```csharp var query = SearchQuery.DeliveredAfter (dateRange.BeginDate) .And (SearchQuery.DeliveredBefore (dateRange.EndDate)); var results = folder.Search (query); ``` However, it has been reported to me that this doesn't work reliably depending on the IMAP server implementation. If you find that this query doesn't get the expected results for your IMAP server, here's another solution that should always work: ```csharp var query = SearchQuery.Not (SearchQuery.DeliveredBefore (dateRange.BeginDate) .Or (SearchQuery.DeliveredAfter (dateRange.EndDate))); var results = folder.Search (query); ``` ### Q: What does "The ImapClient is currently busy processing a command." mean? If you get an InvalidOperationException with the message, "The ImapClient is currently busy processing a command.", it means that you are trying to use the [ImapClient](https://www.mimekit.net/docs/html/T_MailKit_Net_Imap_ImapClient.htm) and/or one of its [ImapFolder](https://www.mimekit.net/docs/html/T_MailKit_Net_Imap_ImapFolder.htm)s from multiple threads. To avoid this situation, you'll need to lock the `SyncRoot` property of the `ImapClient` and `ImapFolder` objects when performing operations on them. For example: ```csharp lock (client.SyncRoot) { client.NoOp (); } ``` Note: Locking the `SyncRoot` is only necessary when using the synchronous API's. All `Async()` method variants already do this locking for you. ### Q: Why do I get InvalidOperationException: "The folder is not currently open."? If you get this exception, it's probably because you thought you had to open the destination folder that you passed as an argument to one of the [CopyTo](https://www.mimekit.net/docs/html/Overload_MailKit_MailFolder_CopyTo.htm) or [MoveTo](https://www.mimekit.net/docs/html/Overload_MailKit_MailFolder_MoveTo.htm) methods. When you opened that destination folder, you also inadvertently closed the source folder which is why you are getting this exception. The IMAP server can only have a single folder open at a time. Whenever you open a folder, you automatically close the previously opened folder. When copying or moving messages from one folder to another, you only need to have the source folder open. ### Q: Why doesn't ImapFolder.MoveTo() move the message out of the source folder? If you look at the source code for the `ImapFolder.MoveTo()` method, what you'll notice is that there are several code paths depending on the features that the IMAP server supports. If the IMAP server supports the `MOVE` extension, then MailKit's `MoveTo()` method will use the `MOVE` command. I suspect that your server does not support the `MOVE` command or you probably wouldn't be seeing what you are seeing. When the IMAP server does not support the `MOVE` command, MailKit has to use the `COPY` command to copy the message(s) to the destination folder. Once the `COPY` command has completed, it will then mark the messages that you asked it to move for deletion by setting the `\Deleted` flag on those messages. If the server supports the `UIDPLUS` extension, then MailKit will attempt to `EXPUNGE` the subset of messages that it just marked for deletion, however, if the `UIDPLUS` extension is not supported by the IMAP server, then it cannot safely expunge just that subset of messages and so it stops there. My guess is that your server supports neither `MOVE` nor `UIDPLUS` and that is why clients like Outlook continue to see the messages in your folder. I believe, however, that Outlook has a setting to show deleted messages with a strikeout (which you probably have disabled). So to answer your question more succinctly: After calling `folder.MoveTo (...);`, if you are confident that the messages marked for deletion should be expunged, call `folder.Expunge ();` ### Q: How can I mark messages as read for IMAP? The way to mark messages as read using the IMAP protocol is to set the `\Seen` flag on the message(s). To do this using MailKit, you will first need to know either the index(es) or the UID(s) of the messages that you would like to set the `\Seen` flag on. Once you have that information, you will want to call one of the [AddFlags](https://www.mimekit.net/docs/html/Overload_MailKit_MailFolder_AddFlags.htm) methods on the `ImapFolder`. For example: ```csharp folder.AddFlags (uids, MessageFlags.Seen, true); ``` To mark messages as unread, you would *remove* the `\Seen` flag, like so: ```csharp folder.RemoveFlags (uids, MessageFlags.Seen, true); ``` ### Q: How can I re-synchronize the cache for an IMAP folder? Assuming your IMAP server does not support the `QRESYNC` extension (which simplifies this procedure a ton), here is some simple code to illustrate how to go about re-synchronizing your cache with the remote IMAP server. ```csharp /// /// Just a simple class to represent the cached information about a message. /// class CachedMessageInfo { public UniqueId UniqueId; public MessageFlags Flags; public HashSetKeywords; public Envelope Envelope; public BodyPart Body; } /// /// Resynchronize the cache with the remote IMAP folder. /// /// The IMAP folder. /// The local cache of message metadata. /// The cached UIDVALIDITY value of the IMAP folder from a previous session. static void ResyncFolder (ImapFolder folder, Listcache, ref uint cachedUidValidity) { IList summaries; // Step 1: Open the folder. // Note: we only need read-only access to update our cache, but depending on // what you plan to do with the folder after resynchronizing, you may want // top open the folder in read-write mode instead. folder.Open (FolderAccess.ReadOnly); if (cache.Count > 0) { if (folder.UidValidity == cachedUidValidity) { // Step 2: Remove messages from our cache that no longer exist on the server. // get the full list of UIDs on the server... var all = folder.Search (SearchQuery.All); // remove any messages from our cache that no longer exist... for (int i = 0; i < cache.Count; i++) { if (!all.Contains (cache[i].UniqueId)) { cache.RemoveAt (i); i--; } } // Step 3: Sync any flag changes for our cached messages. // get a list of known uids... astute observers will note that an easy // optimization to make here would be to merge this loop with the above // loop. var known = new UniqueIdSet (SortOrder.Ascending); for (int i = 0; i < cache.Count; i++) known.Add (cache[i].UniqueId); // fetch the flags for our known messages... summaries = folder.Fetch (known, MessageSummaryItems.Flags); for (int i = 0; i < summaries.Count; i++) { // Note: the indexes should match up with our cache, but it wouldn't // hurt to add error checking to make sure. I'm not bothering to here // for simplicity reasons. cache[i].Flags = summaries[i].Flags.Value; cache[i].Keywords = summaries[i].Keywords; } } else { // The UIDVALIDITY of the folder has changed. This means that our entire // cache is obsolete. We need to clear our cache and start from scratch. cachedUidValidity = folder.UidValidity; cache.Clear (); } } else { // We have nothing cached, so just start from scratch. cachedUidValidity = folder.UidValidity; } // Step 4: Fetch the messages we don't already know about and add them to our cache. summaries = folder.Fetch (cache.Count, -1, MessageSummaryItems.UniqueId | MessageSummaryItems.Flags | MessageSummaryItems.Envelope | MessageSummaryItems.BodyStructure); for (int i = 0; i < summaries.Count; i++) { cache.Add (new CachedMessageInfo { UniqueId = summaries[i].UniqueId, Flags = summaries[i].Flags.Value, Keywords = summaries[i].Keywords, Envelope = summaries[i].Envelope, Body = summaries[i].Body }); } // Tada! Now we are resynchronized with the server! } ``` ### Q: How can I login using a shared mailbox in Office365? ```csharp var result = await GetPublicClientOAuth2CredentialsAsync ("IMAP", "sharedMailboxName@custom-domain.com"); // Note: We always use result.Account.Username instead of `Username` because the user may have selected an alternative account. var oauth2 = new SaslMechanismOAuth2 (result.Account.Username, result.AccessToken); using (var client = new ImapClient ()) { await client.ConnectAsync ("outlook.office365.com", 993, SecureSocketOptions.SslOnConnect); await client.AuthenticateAsync (oauth2); // ... await client.DisconnectAsync (true); } ``` Notes: 1. The `GetPublicClientOAuth2CredentialsAsync()` method used in this example code snippet can be found in the [ExchangeOAuth2.md](ExchangeOAuth2.md#desktop-and-mobile-applications) documentation. 2. Some users have reported that they need to use `"username@custom-domain.com\\sharedMailboxName"` as their username instead of `"sharedMailboxName@custom-domain.com"`. ## SmtpClient ### Q: Why doesn't the message show up in the "Sent Mail" folder after sending it? It seems to be a common misunderstanding that messages sent via SMTP will magically show up in the account's "Sent Mail" folder. In order for the message to show up in the "Sent Mail" folder, you will need to append the message to the "Sent Mail" folder yourself because the SMTP protocol does not support doing this automatically. If the "Sent Mail" folder is a local mbox folder, you'll need to append it like this: ```csharp using (var mbox = File.Open ("C:\\path\\to\\Sent Mail.mbox", FileMode.Append, FileAccess.Write)) { var marker = string.Format ("From MAILER-DAEMON {0}{1}", DateTime.Now.ToString (CultureInfo.InvariantCulture, "ddd MMM d HH:mm:ss yyyy"), Environment.NewLine); var bytes = Encoding.ASCII.GetBytes (marker); // Write the mbox marker bytes. mbox.Write (bytes, 0, bytes.Length); // Write the message, making sure to escape any line that looks like an mbox From-marker. using (var filtered = new FilteredStream (stream)) { filtered.Add (new MboxFromMarker ()); message.WriteTo (filtered); filtered.Flush (); } mbox.Flush (); } ``` If the "Sent Mail" folder exists on an IMAP server, you would need to do something more like this: ```csharp using (var client = new ImapClient ()) { client.Connect ("imap.server.com", 993, SecureSocketOptions.SslOnConnect); client.Authenticate ("username", "password"); IMailFolder sentMail; if (client.Capabilities.HasFlag (ImapCapabilities.SpecialUse)) { sentMail = client.GetFolder (SpecialFolder.Sent); } else { var personal = client.GetFolder (client.PersonalNamespaces[0]); // Note: This assumes that the "Sent Mail" folder lives at the root of the folder hierarchy // and is named "Sent Mail" as opposed to "Sent" or "Sent Items" or any other variation. sentMail = personal.GetSubfolder ("Sent Mail"); } sentMail.Append (message, MessageFlags.Seen); client.Disconnect (true); } ``` ### Q: How can I send email to a SpecifiedPickupDirectory? Based on Microsoft's [referencesource](https://github.com/Microsoft/referencesource/blob/master/System/net/System/Net/mail/SmtpClient.cs#L401), when `SmtpDeliveryMethod.SpecifiedPickupDirectory` is used, the `SmtpClient` saves the message to the specified pickup directory location using a randomly generated filename based on `Guid.NewGuid ().ToString () + ".eml"`, so to achieve the same results with MailKit, you could do something like this: ```csharp public static void SaveToPickupDirectory (MimeMessage message, string pickupDirectory) { do { // Generate a random file name to save the message to. var path = Path.Combine (pickupDirectory, Guid.NewGuid ().ToString () + ".eml"); Stream stream; try { // Attempt to create the new file. stream = File.Open (path, FileMode.CreateNew); } catch (IOException) { // If the file already exists, try again with a new Guid. if (File.Exists (path)) continue; // Otherwise, fail immediately since it probably means that there is // no graceful way to recover from this error. throw; } try { using (stream) { // IIS pickup directories expect the message to be "byte-stuffed" // which means that lines beginning with "." need to be escaped // by adding an extra "." to the beginning of the line. // // Use an SmtpDataFilter "byte-stuff" the message as it is written // to the file stream. This is the same process that an SmtpClient // would use when sending the message in a `DATA` command. using (var filtered = new FilteredStream (stream)) { filtered.Add (new SmtpDataFilter ()); // Make sure to write the message in DOS ( ) format. var options = FormatOptions.Default.Clone (); options.NewLineFormat = NewLineFormat.Dos; message.WriteTo (options, filtered); filtered.Flush (); return; } } } catch { // An exception here probably means that the disk is full. // // Delete the file that was created above so that incomplete files are not // left behind for IIS to send accidentally. File.Delete (path); throw; } } while (true); } ``` ### Q: How can I request a notification when the message is read by the user? The first thing I need to make clear is that requesting a notification does not guarantee that you'll actually get one. In order for you to receive a notification that the message was read by its recipient, the recipient's mail client needs to know how to send such a notification *and* that the user has enabled it to do so. That said, here's how you can request a notification when the recipient reads the message that has been sent: ```csharp // Add the following header to tell the recipient's client that you want to receive a // notification when the message has been read by the user. message.Headers[HeaderId.DispositionNotificationTo] = new MailboxAddress ("My Name", "me@example.com").ToString (true); ``` For more information on this topic, read [rfc3798](https://tools.ietf.org/html/rfc3798). ### Q: How can I process a read receipt notification? A read receipt notification comes in the form of a MIME message with a top-level MIME part with a MIME-type of `multipart/report` that has a `report-type` parameter with a value of `disposition-notification`. You could check for this in code like this: ```csharp var report = message.Body as MultipartReport; if (report != null && report.ReportType.Equals ("disposition-notification", StringComparison.OrdinalIgnoreCase)) { // This is a read receipt notification. } ``` The first part of the `multipart/report` will be a human-readable explanation of the notification. The second part will have a MIME-type of `message/disposition-notification` and be represented by a [MessageDispositionNotification](https://www.mimekit.net/docs/html/T_MimeKit_MessageDispositionNotification.htm). This notification part will contain a list of header-like fields containing information about the message that this notification is for such as the `Original-Message-Id`, `Original-Recipient`, etc. ```csharp var notification = report[1] as MessageDispositionNotification; if (notification != null) { // Get the Message-Id of the message this notification is for... var messageId = notification.Fields["Original-Message-Id"]; } ``` For more information on this topic, read [rfc3798](https://tools.ietf.org/html/rfc3798).