MailKit
Copilot instructions6.9k starsChanged 5 years agoMITView on GitHub
MailKit is a cross-platform C# IMAP, POP3 and SMTP client library built on top of MimeKit. It is a .NET Foundation project. Correctness against the relevant IETF RFCs and robust handling of real-world (often non-compliant) servers are the dominant design constraints. MimeKit is consumed as a git submodule (submodules/MimeKit) and built from source as part of MailKit.sln. Run git submodule update --init --recursive before building. MimeKit has its own submodules/MimeKit/.github/copilot-instructions.md, so read it before you change anything under submodules/MimeKit. On…
We split this copilot-instructions.md into the rules it gives an agent
12 rules. About 1 is common, 3 are distinctive, and 8 are very specific to this project.
≈1
Common · estimate
Many files like this have a rule of this kind.
≈3
Distinctive · estimate
This author's own choices. Worth a look.
8
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 23
Run the full test suite the way CI does:
- Distinctive: line 34
Run a single test or fixture (fastest inner loop.
- Very specific: line 44
Before finishing a library change, build every target framework in Release.
- Very specific: line 122
Every await in the library must use .ConfigureAwait (false).
- Common: line 126
Treat server responses as untrusted: bound anything that grows with input and fail with a protocol exception, not an unbounded allocation.
- Very specific: line 135
Namespaces: use the brace-on-same-line form: namespace MailKit.Net.Imap {.
- Very specific: line 136
License header: every source file starts with the standard MIT block, including Author: Jeffrey Stedfast <jestedfa@microsoft.com> and the …
- Very specific: line 139
Argument validation: use explicit checks, e.g. if (x == null) throw new ArgumentNullException (nameof (x));.
- Very specific: line 143
Document every exception a method can throw (follow the exhaustive <exception> lists used on existing methods).
- Very specific: line 153
Test methods are named TestXxx, and the async mirror is TestXxxAsync.
- Distinctive: line 170
When a test is meant to pin a behaviour, sanity-check it by temporarily breaking the code under test and confirming the test fails.
- Very specific: line 196
Update ReleaseNotes.md for user-visible changes, and PortingGuide.md for breaking changes.
≈ “Common” is an estimate: at least 1 in 5 of the 3,669 Copilot instructions files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
MailKit — Copilot Instructions
MailKit is a cross-platform C# IMAP, POP3 and SMTP client library built on top of MimeKit. It is a .NET Foundation project. Correctness against the relevant IETF RFCs and robust handling of real-world (often non-compliant) servers are the dominant design constraints.
MimeKit is consumed as a git submodule (submodules/MimeKit) and built from source as part of
MailKit.sln. Run git submodule update --init --recursive before building. MimeKit has its own
submodules/MimeKit/.github/copilot-instructions.md, so read it before you change anything under
submodules/MimeKit.
Build, Test, and Coverage
dotnet restore MailKit.sln
dotnet tool restore # installs altcover, coveralls.net, nunit console runner
dotnet msbuild MailKit.sln -property:Platform="Any CPU" -property:Configuration=DebugOn non-Windows also pass -property:MonoRuntime=true. This defines MONO in UnitTests and
selects the non-Windows SQLite interop binaries.
Run the full test suite the way CI does:
./scripts/test.ps1 -Configuration Debug -GenerateCodeCoverage falsescripts/test.ps1 runs dotnet nunit UnitTests/bin/<Config>/net8.0/UnitTests.dll. With
-GenerateCodeCoverage true, it instruments the assemblies in place with AltCover and re-signs them
with MailKit/mailkit.snk. This matters because MimeKit's InternalsVisibleTo ("MailKit, PublicKey=...")
requires the signature. Rebuild before running without coverage if you previously ran with it.
Run a single test or fixture (fastest inner loop; builds as a side effect):
dotnet test UnitTests\UnitTests.csproj --filter "FullyQualifiedName~ImapFolderFlagsTests.TestChangingFlagsOnEmptyListOfMessages"
dotnet test UnitTests\UnitTests.csproj --filter "FullyQualifiedName~UnitTests.Net.Smtp.SmtpClientTests"
# or, against an already-built test assembly (no rebuild):
dotnet nunit UnitTests\bin\Debug\net8.0\UnitTests.dll --where "class =~ /SmtpClientTests/" --noresultBefore finishing a library change, build every target framework in Release. This catches
API-availability problems on net4x/netstandard2.0:
dotnet msbuild MailKit\MailKit.csproj -restore -property:Configuration=Release -v:mAOT compatibility is enforced in CI:
./scripts/test-aot-compatibility.ps1 # fails if `dotnet publish` of AotCompatibility/ emits any `warning IL*`There is no separate lint step. .editorconfig and the analyzers enforce style. The NoWarn lists
in the .csproj files are deliberate, so do not "fix" a suppressed diagnostic by editing NoWarn.
Project Layout
| Path | Notes |
MailKit/ | The library (MailKit assembly/package). Depends on MimeKit.Core, not on BouncyCastle. |
submodules/MimeKit/ | MimeKit source; MailKit.sln builds MimeKit/MimeKit.csproj (MimeKit.Core) from here. |
UnitTests/ | NUnit 4, net8.0 only. |
AotCompatibility/ | Native-AOT publish probe used by scripts/test-aot-compatibility.ps1. |
Documentation/ | Sandcastle docs project. Documentation/Examples/*.cs are not compiled but are pulled into XML docs via <code source="Examples\Foo.cs" region="..."/>. |
nuget/MailKit.nuspec | Package definition (the package is built with nuget pack, not dotnet pack). |
samples/ | Standalone sample apps, not part of MailKit.sln. |
MailKit.csproj uses the SDK's default globbing, so every .cs file under MailKit/ is compiled.
A few legacy files are excluded with <Compile Remove> (e.g. Net/SelectMode.cs). Add a Remove if
you need to keep a file out of the build.
Target Frameworks and Conditional Compilation
MailKit multi-targets netstandard2.0;netstandard2.1;net462;net47;net48;net8.0;net10.0. For every change:
LangVersionis pinned to 12 (UnitTests: 10). Do not use newer language features.Nullableis enabled in the library. It is not enabled inUnitTests, so don't add?/!annotations there (CS8632).- Modern BCL APIs are not available on every target. Guard with
#if NET*or use existing helpers. IsAotCompatibleis set for net7.0+ targets. Do not use reflection-based dynamic code.- Assembly version attributes are hand-maintained in
MailKit/Properties/AssemblyInfo.cs, because assembly-info generation is disabled.
Architecture
Protocol layering
Each protocol under MailKit/Net/{Imap,Pop3,Smtp} follows the same layering:
XxxClientis the public API. It derives fromMailStore(IMAP),MailSpool(POP3) orMailTransport(SMTP), which in turn derive fromMailService. It handles connection/TLS, auth (SASL fromMailKit/Security), and state checks.XxxEngine(IMAP/POP3) owns the connection state, capabilities, command queue, and response parsing. IMAP commands areImapCommandobjects built from printf-like format strings (%Sstring/literal,%Ffolder,%Lliteral,%d,%u,%s). They are queued withEngine.QueueCommand (...)and driven withEngine.Run (ic)/await Engine.RunAsync (ic). Untagged responses are dispatched throughic.RegisterUntaggedHandler (atom, handler).XxxStreamis the line/token-oriented network stream. SMTP has no engine;SmtpStream.SendCommand[Async]talks to the server directly.ImapFolderis split across many partial-class files by feature (ImapFolderFetch.cs,ImapFolderFlags.cs,ImapFolderSearch.cs, ...).MailKit/Net/Proxyholds HTTP/HTTPS/SOCKS4/4a/5 proxy clients.MailKit/Securityholds SASL mechanisms (including NTLM) and certificate validation.MailKit/Searchholds theSearchQuerytree and its optimizer.MailKit/Telemetryholds metrics (documented inTelemetry.md).- Public interfaces (
IMailFolder,IImapClient,ISmtpClient, ...) mirror the concrete types. Convenience overloads live inIMailFolder*Extensionsclasses. When you add a public member to a type that has an interface, add it to the interface too.
Every async method is a hand-maintained mirror of its sync twin
There is no code generator, so sync and async logic are duplicated by hand. ImapClient.cs and
AsyncImapClient.cs (and likewise for Pop3/Smtp) are partial class pairs. Within other files,
Foo/FooAsync sit side by side with await ... .ConfigureAwait (false) substituted in. Some
low-level helpers take a bool doAsync parameter (e.g. ImapContinuationHandler).
- A fix applied only to the sync path is a bug.
- Watch for drift between the twins.
- Every
awaitin the library must use.ConfigureAwait (false).
Hostile and quirky servers
Treat server responses as untrusted: bound anything that grows with input and fail with a protocol exception, not an unbounded allocation. Many code paths deliberately tolerate specific broken servers (Exchange, GMail, Domino, Yahoo, ...), so keep those workarounds intact when refactoring.
Conventions
- Formatting: tabs, CRLF, and a space between a method name and its argument list
(
client.GetFolder (name, cancellationToken)) in both calls and declarations (Mono style).else/catch/finallystay on the same line as the closing brace..editorconfigis authoritative. - Namespaces use the brace-on-same-line form:
namespace MailKit.Net.Imap {. - License header: every source file starts with the standard MIT block, including
Author: Jeffrey Stedfast <jestedfa@microsoft.com>and the.NET Foundation and Contributorscopyright. Copy it verbatim into new files. - Argument validation: use explicit checks, e.g.
if (x == null) throw new ArgumentNullException (nameof (x));. Every public API is expected to validate its arguments, andUnitTests/ArgumentExceptionTests.cscovers them. - XML documentation is required on public members, because the docs ship in the NuGet package.
Document every exception a method can throw (follow the exhaustive
<exception>lists used on existing methods). Prefer<code language="c#" source="Examples\..." region="..."/>examples backed byDocumentation/Examples. - Strong naming: both MailKit and UnitTests are signed with
MailKit/mailkit.snk, andInternalsVisibleTorelies on that key.
Testing Conventions
- Fixtures are named
<Type>Testsand live in the directory that mirrors the source layout (UnitTests/Net/Imap/,UnitTests/Net/Smtp/,UnitTests/Security/, ...). - Test methods are named
TestXxx, and the async mirror isTestXxxAsync. When you add a test for an API with an async twin, add both. Sync and async tests usually share aCreateXxxCommands ()helper. Protocol tests don't use real servers. They script a conversation with
ImapReplayStream/Pop3ReplayStream/SmtpReplayStreamand a list ofXxxReplayCommands, each pairing an exact expected client command with a canned server response:new ImapReplayCommand ("A00000004 SELECT INBOX (CONDSTORE)\r\n", "common.select-inbox.txt") ... client.Connect (new ImapReplayStream (commands, isAsync), "localhost", 143, SecureSocketOptions.None);Set
TagPrefix = 'A'onImapClientso tags are predictable.- Canned responses are embedded resources under
UnitTests/Net/{Imap,Pop3,Smtp}/Resources/. They are referenced as<server>.<name>.txt, and IMAP responses are grouped into per-server folders (common,dovecot,gmail,exchange, ...). In resource files,A########is replaced with the command's tag, and LF line endings are converted to CRLF at load time. - When a test is meant to pin a behaviour, sanity-check it by temporarily breaking the code under test and confirming the test fails.
Releasing
Version numbers live in:
VersionPrefixinMailKit.csproj;MailKit/Properties/AssemblyInfo.cs;nuget/MailKit.nuspec, both<version>and theMimeKit.Coredependency version.
scripts/update-version.ps1 -Version x.y.z updates all of them and the samples.
.github/workflows/main.yml (CI) builds pre-release packages and publishes them to MyGet on pushes to
master. Official releases are made by manually running .github/workflows/release.yml with the full
40-character commit SHA. The workflow:
- verifies that the versions agree;
- builds and packs;
- optionally code-signs (
signinput); - generates build-provenance attestations;
- pushes to NuGet.org;
- tags the commit.
Any step that modifies the package must run before attestation.
Update ReleaseNotes.md for user-visible changes, and PortingGuide.md for breaking changes.
Where files are headingNew Copilot instructions files have more rules about branches and pull requests, and fewer about code style.
Of the Copilot instructions files we read, the 2,297 first seen in the last six months, against the older ones: the kind of rule that gained most share, and the kind that lost most. About files like this in general, not this one, and an estimate, since a kind is a pattern in the words.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

