Skip to content

Commit 92e971c

Browse files
committed
feat: Add MailKit documentation covering email creation, parsing, sending, and IMAP management
1 parent 73c8e7d commit 92e971c

8 files changed

Lines changed: 1056 additions & 0 deletions

File tree

.github/skills/mailkit/SKILL.md

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
---
2+
name: mailkit
3+
description: Use MailKit and MimeKit to create, parse, send, and receive email in .NET — covering MimeMessage construction (BodyBuilder, HTML/text bodies, attachments, inline images), parsing .eml files, extracting attachments, headers and addresses (MailboxAddress, reply construction), SmtpClient (SecureSocketOptions, OAuth2, DI patterns), ImapClient (FetchAsync, SearchQuery, StoreFlagsRequest, MoveTo, IDLE push notifications), Pop3Client, and SaslMechanismOAuth2 for Gmail and Exchange. Trigger whenever the user writes, reviews, or asks about email creation, MIME messages, .eml parsing, extracting attachments, sending email, reading inbox, IMAP folder management, SmtpClient, ImapClient, BodyBuilder, MimeMessage, MimePart, MailboxAddress, SaslMechanismOAuth2, or building any email feature in .NET — even if they don't mention MailKit or MimeKit by name. Always prefer this skill over guessing; the MIME tree model, BodyBuilder vs manual multipart construction, FolderAccess modes, FetchAsync vs GetMessageAsync, IDLE threading, and OAuth2 SASL wiring all have non-obvious failure modes.
4+
---
5+
6+
# MailKit + MimeKit
7+
8+
MailKit and MimeKit are the standard .NET libraries for email. MimeKit handles MIME message construction and parsing. MailKit builds on top of it to add SMTP, IMAP, and POP3 transport.
9+
10+
**NuGet package**
11+
- `MailKit` — includes MimeKit as a transitive dependency; one package for everything
12+
13+
## Reference Index
14+
15+
| File | Topics |
16+
|------|--------|
17+
| [creating-messages.md](references/creating-messages.md) | `MimeMessage`, `BodyBuilder`, `TextPart`, `MimePart`, multipart, linked resources, inline images |
18+
| [parsing-messages.md](references/parsing-messages.md) | `MimeMessage.Load`, `TextBody`/`HtmlBody`, `MimeIterator`, extracting and saving attachments |
19+
| [headers-addresses.md](references/headers-addresses.md) | `MailboxAddress`, `GroupAddress`, `InternetAddressList`, custom headers, reply construction |
20+
| [smtp.md](references/smtp.md) | `SmtpClient`, connect/auth/send/disconnect, `SecureSocketOptions`, DI pattern, error handling |
21+
| [imap.md](references/imap.md) | `ImapClient`, folder access, `FetchAsync`, `GetMessageAsync`, `SearchQuery`, flags, move/delete |
22+
| [oauth2.md](references/oauth2.md) | `SaslMechanismOAuth2`, Gmail OAuth, Exchange/Office 365, token refresh |
23+
| [imap-idle.md](references/imap-idle.md) | IMAP IDLE push notifications, `CountChanged`, polling fallback |
24+
25+
## Quick Reference
26+
27+
```csharp
28+
// Build a message
29+
var message = new MimeMessage();
30+
message.From.Add(new MailboxAddress("Sender", "sender@example.com"));
31+
message.To.Add(new MailboxAddress("Recipient", "recipient@example.com"));
32+
message.Subject = "Hello";
33+
34+
var builder = new BodyBuilder { HtmlBody = "<b>Hello!</b>", TextBody = "Hello!" };
35+
builder.Attachments.Add("report.pdf", pdfBytes, ContentType.Parse("application/pdf"));
36+
message.Body = builder.ToMessageBody();
37+
38+
// Parse a message
39+
var loaded = MimeMessage.Load("message.eml");
40+
string? text = loaded.TextBody;
41+
string? html = loaded.HtmlBody;
42+
foreach (var attachment in loaded.Attachments.OfType<MimePart>())
43+
{
44+
using var stream = File.Create(attachment.FileName ?? "file");
45+
attachment.Content.DecodeTo(stream);
46+
}
47+
48+
// Send (SMTP)
49+
using var smtp = new SmtpClient();
50+
await smtp.ConnectAsync("smtp.example.com", 587, SecureSocketOptions.StartTls, ct);
51+
await smtp.AuthenticateAsync("user", "pass", ct);
52+
await smtp.SendAsync(message, ct);
53+
await smtp.DisconnectAsync(true, ct);
54+
55+
// Read inbox (IMAP)
56+
using var imap = new ImapClient();
57+
await imap.ConnectAsync("imap.example.com", 993, SecureSocketOptions.SslOnConnect, ct);
58+
await imap.AuthenticateAsync("user", "pass", ct);
59+
await imap.Inbox.OpenAsync(FolderAccess.ReadOnly, ct);
60+
var summaries = await imap.Inbox.FetchAsync(0, -1,
61+
MessageSummaryItems.Envelope | MessageSummaryItems.Flags, ct);
62+
await imap.DisconnectAsync(true, ct);
63+
```
64+
65+
## Key design decisions
66+
67+
**Use `BodyBuilder` to construct messages.** It manages the multipart tree (mixed/alternative/related) automatically. Build manually only when you need precise MIME structure control.
68+
69+
**Prefer `FetchAsync` over `GetMessageAsync` for listing.** `FetchAsync` with `MessageSummaryItems.Envelope` downloads only headers — far faster for mailbox views.
70+
71+
**Always open folders with minimum required access.** `FolderAccess.ReadOnly` for reading, `FolderAccess.ReadWrite` for flag changes or deletions.
72+
73+
**Use UIDs, not sequence numbers.** Sequence numbers shift when messages are expunged; `UniqueId` stays stable. All bulk operations have UID-based overloads.
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
# Creating MIME Messages
2+
3+
## NuGet Setup
4+
5+
```xml
6+
<PackageReference Include="MimeKit" />
7+
```
8+
9+
---
10+
11+
## BodyBuilder (recommended)
12+
13+
`BodyBuilder` is the easiest way to compose messages. It builds the correct multipart tree automatically.
14+
15+
```csharp
16+
var message = new MimeMessage();
17+
message.From.Add(new MailboxAddress("Joey", "joey@example.com"));
18+
message.To.Add(new MailboxAddress("Alice", "alice@example.com"));
19+
message.Subject = "Meeting invitation";
20+
21+
var builder = new BodyBuilder();
22+
builder.TextBody = "Hi Alice,\n\nSee attached.";
23+
builder.HtmlBody = "<p>Hi Alice,</p><p>See attached.</p>";
24+
25+
// Attachment — accepts file path, Stream, or byte[] overloads
26+
builder.Attachments.Add("invite.ics");
27+
builder.Attachments.Add("report.pdf", pdfBytes, ContentType.Parse("application/pdf"));
28+
29+
message.Body = builder.ToMessageBody();
30+
```
31+
32+
### Inline/embedded images
33+
34+
```csharp
35+
var builder = new BodyBuilder();
36+
37+
// Generate a content ID for the image to reference in HTML
38+
var contentId = MimeUtils.GenerateMessageId();
39+
builder.HtmlBody = $@"<p>Hello!</p><img src=""cid:{contentId}"" alt=""logo"" />";
40+
41+
// Add as a linked resource (not a download attachment)
42+
var logo = builder.LinkedResources.Add("logo.png");
43+
logo.ContentId = contentId;
44+
45+
message.Body = builder.ToMessageBody();
46+
```
47+
48+
---
49+
50+
## Manual multipart construction
51+
52+
Use when you need explicit control over the MIME structure (e.g., custom content types, multipart/signed).
53+
54+
### Plain text with file attachment
55+
56+
```csharp
57+
var body = new TextPart("plain") { Text = "See attached." };
58+
59+
var attachment = new MimePart("application", "pdf")
60+
{
61+
Content = new MimeContent(File.OpenRead("report.pdf")),
62+
ContentDisposition = new ContentDisposition(ContentDisposition.Attachment),
63+
ContentTransferEncoding = ContentEncoding.Base64,
64+
FileName = "report.pdf"
65+
};
66+
67+
var multipart = new Multipart("mixed");
68+
multipart.Add(body);
69+
multipart.Add(attachment);
70+
71+
message.Body = multipart;
72+
```
73+
74+
### multipart/alternative (plain + HTML)
75+
76+
```csharp
77+
var plain = new TextPart("plain") { Text = "Fallback plain text" };
78+
var html = new TextPart("html") { Text = "<p>Rich HTML body</p>" };
79+
80+
var alternative = new MultipartAlternative();
81+
alternative.Add(plain);
82+
alternative.Add(html); // clients pick the last format they support
83+
84+
message.Body = alternative;
85+
```
86+
87+
---
88+
89+
## Serialising / writing messages
90+
91+
```csharp
92+
// Write to file
93+
message.WriteTo("message.eml");
94+
95+
// Write to stream
96+
using var stream = File.OpenWrite("message.eml");
97+
message.WriteTo(stream);
98+
99+
// Write to stream (async)
100+
await message.WriteToAsync(stream);
101+
```
102+
103+
---
104+
105+
## Key types
106+
107+
| Type | Purpose |
108+
|------|---------|
109+
| `MimeMessage` | Root envelope: headers + body |
110+
| `BodyBuilder` | Fluent helper for composing body/attachments |
111+
| `TextPart` | `text/plain` or `text/html` leaf node |
112+
| `MimePart` | Binary leaf node (images, files) |
113+
| `Multipart` | Container for multiple parts |
114+
| `MultipartAlternative` | `multipart/alternative` container |
115+
| `MimeContent` | Wraps a `Stream` inside a `MimePart` |
116+
| `ContentDisposition` | `inline` or `attachment` + filename |
117+
| `ContentEncoding` | `Base64`, `QuotedPrintable`, `SevenBit`, etc. |
118+
| `MimeUtils` | Utility: `GenerateMessageId()`, etc. |
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
# Headers and Addresses
2+
3+
## Address types
4+
5+
| Type | Represents |
6+
|------|-----------|
7+
| `MailboxAddress` | A single `Name <addr>` email address |
8+
| `GroupAddress` | A named group containing mailboxes |
9+
| `InternetAddressList` | `IList<InternetAddress>` — the type of `To`, `Cc`, `Bcc`, `From`, `ReplyTo` |
10+
11+
### Creating addresses
12+
13+
```csharp
14+
var mailbox = new MailboxAddress("Alice", "alice@example.com");
15+
var noName = new MailboxAddress(string.Empty, "anon@example.com");
16+
var parsed = MailboxAddress.Parse("Bob <bob@example.com>");
17+
var parsedGroup = InternetAddressList.Parse("Alice <alice@x.com>, Bob <bob@x.com>");
18+
```
19+
20+
### Adding recipients
21+
22+
```csharp
23+
message.To.Add(new MailboxAddress("Alice", "alice@example.com"));
24+
message.To.Add(new MailboxAddress("Bob", "bob@example.com"));
25+
message.Cc.Add(new MailboxAddress("Carol", "carol@example.com"));
26+
message.Bcc.Add(new MailboxAddress("Dave", "dave@example.com"));
27+
```
28+
29+
### Iterating mailboxes (skip group addresses)
30+
31+
```csharp
32+
foreach (var mailbox in message.To.Mailboxes)
33+
Console.WriteLine($"{mailbox.Name} — {mailbox.Address}");
34+
```
35+
36+
### Flattening mixed groups + individual addresses
37+
38+
```csharp
39+
IEnumerable<MailboxAddress> AllMailboxes(InternetAddressList list)
40+
{
41+
foreach (var addr in list)
42+
{
43+
if (addr is MailboxAddress mb)
44+
yield return mb;
45+
else if (addr is GroupAddress group)
46+
foreach (var groupMb in group.Members.Mailboxes)
47+
yield return groupMb;
48+
}
49+
}
50+
```
51+
52+
---
53+
54+
## Working with headers
55+
56+
### Standard headers via typed properties
57+
58+
```csharp
59+
message.Subject = "Re: Questions";
60+
message.Date = DateTimeOffset.UtcNow;
61+
message.MessageId = MimeUtils.GenerateMessageId();
62+
message.InReplyTo = "<original-id@host>";
63+
message.Priority = MessagePriority.Urgent;
64+
message.Importance = MessageImportance.High;
65+
```
66+
67+
### Custom and raw headers
68+
69+
```csharp
70+
// Add custom headers
71+
message.Headers.Add("X-Custom-Header", "value");
72+
message.Headers.Add("X-Mailer", "MyApp/1.0");
73+
74+
// Replace a header value
75+
message.Headers[HeaderId.Subject] = "Corrected Subject";
76+
77+
// Read a specific header
78+
string? xCustom = message.Headers["X-Custom-Header"];
79+
80+
// Enumerate all headers
81+
foreach (var header in message.Headers)
82+
Console.WriteLine($"{header.Field}: {header.Value}");
83+
84+
// Get all values for a multi-value header (e.g. Received)
85+
var received = message.Headers.GetAllValues(HeaderId.Received);
86+
87+
// Remove a header
88+
message.Headers.Remove(HeaderId.XPriority);
89+
```
90+
91+
---
92+
93+
## Constructing a reply message
94+
95+
```csharp
96+
public static MimeMessage BuildReply(MimeMessage original, MailboxAddress from, bool replyToAll)
97+
{
98+
var reply = new MimeMessage();
99+
reply.From.Add(from);
100+
101+
// Reply-To takes precedence over From
102+
if (original.ReplyTo.Count > 0)
103+
reply.To.AddRange(original.ReplyTo);
104+
else if (original.From.Count > 0)
105+
reply.To.AddRange(original.From);
106+
else if (original.Sender is not null)
107+
reply.To.Add(original.Sender);
108+
109+
if (replyToAll)
110+
{
111+
reply.To.AddRange(original.To);
112+
reply.Cc.AddRange(original.Cc);
113+
}
114+
115+
// Prefix Subject with "Re: " if not already present
116+
reply.Subject = original.Subject?.StartsWith("Re:", StringComparison.OrdinalIgnoreCase) == true
117+
? original.Subject
118+
: "Re: " + original.Subject;
119+
120+
// Thread headers
121+
if (!string.IsNullOrEmpty(original.MessageId))
122+
{
123+
reply.InReplyTo = original.MessageId;
124+
foreach (var id in original.References)
125+
reply.References.Add(id);
126+
reply.References.Add(original.MessageId);
127+
}
128+
129+
// Quote original plain-text body
130+
var sender = original.Sender ?? original.From.Mailboxes.FirstOrDefault();
131+
var quotedText = new StringBuilder();
132+
quotedText.AppendLine($"On {original.Date:f}, {sender?.Name ?? sender?.Address} wrote:");
133+
using var reader = new StringReader(original.TextBody ?? string.Empty);
134+
for (string? line; (line = reader.ReadLine()) is not null;)
135+
quotedText.AppendLine("> " + line);
136+
137+
reply.Body = new TextPart("plain") { Text = quotedText.ToString() };
138+
return reply;
139+
}
140+
```
141+
142+
---
143+
144+
## `ContentType` and `MediaType`
145+
146+
```csharp
147+
// Access the content type of any MimeEntity
148+
var ct = entity.ContentType;
149+
Console.WriteLine(ct.MimeType); // e.g. "text/plain"
150+
Console.WriteLine(ct.MediaType); // e.g. "text"
151+
Console.WriteLine(ct.MediaSubtype); // e.g. "plain"
152+
Console.WriteLine(ct.Charset); // e.g. "utf-8"
153+
154+
// Compare
155+
if (ct.IsMimeType("text", "html")) { ... }
156+
157+
// Parse from string
158+
var parsed = ContentType.Parse("application/json; charset=utf-8");
159+
```

0 commit comments

Comments
 (0)