MailKit Documentation

repository·master·Indexed 24 days ago

https://github.com/jstedfast/mailkit

A cross-platform, RFC-compliant mail client library for .NET built on top of MimeKit. MailKit provides full implementations for SMTP, POP3, and IMAP protocols, including support for OAuth2 authentication with Microsoft Entra ID for Exchange. It allows developers to send email messages via SmtpClient, retrieve messages using Pop3Client and ImapClient, manage IMAP folders, and perform granular message fetching.

Tokens
26.7K
Snippets
54
Records
105
Agent score
92%

What's inside MailKit

  1. Simulate SmtpDeliveryMethod.SpecifiedPickupDirectory

    master

    To replicate the behavior of .NET's SmtpDeliveryMethod.SpecifiedPickupDirectory in MailKit, you must manually save the MimeMessage to a directory using a random GUID-based filename.

    Crucially, IIS pickup directories expect messages to be "byte-stuffed" (lines beginning with . must be escaped by adding an extra .). Use FilteredStream with SmtpDataFilter and set NewLineFormat.Dos to ensure compatibility.

    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 to "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 (<CR><LF>) 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);
    }
  2. Authenticate an ASP.NET Web App with GMail OAuth2

    master

    In ASP.NET Core, configure Google Authentication in Program.cs and use the GoogleScopedAuthorizeAttribute to request the necessary scopes. Once the credential is retrieved, use SaslMechanismOAuthBearer to authenticate the MailKit client.

    // 1. Configure Authentication in Program.cs
    builder.Services.AddAuthentication (options => {
        options.DefaultChallengeScheme = GoogleOpenIdConnectDefaults.AuthenticationScheme;
        options.DefaultForbidScheme = GoogleOpenIdConnectDefaults.AuthenticationScheme;
        options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    })
    .AddCookie (options => {
        options.ExpireTimeSpan = TimeSpan.FromMinutes (5);
    })
    .AddGoogleOpenIdConnect (options => {
        var secrets = GoogleClientSecrets.FromFile ("client_secret.json").Secrets;
        options.ClientId = secrets.ClientId;
        options.ClientSecret = secrets.ClientSecret;
    });
    
    // Ensure middleware is used
    app.UseHttpsRedirection ();
    app.UseStaticFiles ();
    app.UseRouting ();
    app.UseAuthentication ();
    app.UseAuthorization ();
    
    // 2. Use the credential in a controller/service
    [GoogleScopedAuthorize(DriveService.ScopeConstants.DriveReadonly)]
    public async Task AuthenticateAsync ([FromServices] IGoogleAuthProvider auth)
    {
        GoogleCredential? googleCred = await auth.GetCredentialAsync ();
        string token = await googleCred.UnderlyingCredential.GetAccessTokenForRequestAsync ();
        
        var oauth2 = new SaslMechanismOAuthBearer ("UserEmail", token);
        
        using var emailClient = new ImapClient ();
        await emailClient.ConnectAsync ("imap.gmail.com", 993, SecureSocketOptions.SslOnConnect);
        await emailClient.AuthenticateAsync (oauth2);
        await emailClient.DisconnectAsync (true);
    }
  3. Navigate IMAP folders

    master

    You can navigate the folder hierarchy using client.GetFolder(). To find standard folders like Drafts or Sent, check if the server supports ImapCapabilities.SpecialUse or ImapCapabilities.XList. If supported, use client.GetFolder(SpecialFolder.Drafts). If not, you may need to iterate through subfolders and match names against common patterns.

    // List top-level folders under the first personal namespace
    var personal = client.GetFolder (client.PersonalNamespaces[0]);
    foreach (var folder in personal.GetSubfolders (false))
    	Console.WriteLine ("[folder] {0}", folder.Name);
    
    // Accessing special folders via extensions
    if ((client.Capabilities & (ImapCapabilities.SpecialUse | ImapCapabilities.XList)) != 0) {
    	var drafts = client.GetFolder (SpecialFolder.Drafts);
    }
  4. Search for messages between two dates in ImapFolder

    master

    To search for messages delivered within a specific date range, you can use SearchQuery.DeliveredAfter and SearchQuery.DeliveredBefore.

    Note: Some IMAP server implementations may not handle the standard range query reliably. If you encounter issues, use a negated query with Or logic to ensure compatibility across different servers.

    // Standard approach
    var query = SearchQuery.DeliveredAfter (dateRange.BeginDate)
        .And (SearchQuery.DeliveredBefore (dateRange.EndDate));
    var results = folder.Search (query);
    
    // Reliable fallback for problematic IMAP servers
    var query = SearchQuery.Not (SearchQuery.DeliveredBefore (dateRange.BeginDate)
        .Or (SearchQuery.DeliveredAfter (dateRange.EndDate)));
    var results = folder.Search (query);
  5. Register a Service Principal for a Web Service

    master

    To use a web service with Exchange OAuth2, a tenant admin must register your service principal using Azure PowerShell.

    1. Install and connect to the ExchangeOnlineManagement module:
    Install-Module -Name ExchangeOnlineManagement -allowprerelease
    Import-module ExchangeOnlineManagement 
    Connect-ExchangeOnline -Organization <tenantId>
    1. Register the Service Principal using the New-ServicePrincipal cmdlet.

    Important: When retrieving the <OBJECT_ID>, ensure you use the Object ID from the Service Principal under Enterprise Applications in the Azure portal, not the App Registration ID.

    New-ServicePrincipal -AppId <APPLICATION_ID> -ObjectId <OBJECT_ID> [-Organization <ORGANIZATION_ID>]
  6. Append sent messages to an IMAP 'Sent Mail' folder

    master

    The SMTP protocol does not automatically save sent messages to a 'Sent Mail' folder. To ensure messages appear there, you must manually append the message to the appropriate folder using an ImapClient.

    If the server supports ImapCapabilities.SpecialUse, you can use client.GetFolder(SpecialFolder.Sent). Otherwise, you may need to navigate to the personal namespace and find the subfolder by name (e.g., "Sent Mail").

    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);
    }
  7. Access GMail using an App Password

    master

    Since Google no longer supports username/password authentication, you must use either OAuth 2.0 or an App Password. To use an App Password, enable 2-Step Verification in your Google Account and generate a password specifically for your application.

    using (var client = new ImapClient ()) {
        client.Connect ("imap.gmail.com", 993, SecureSocketOptions.SslOnConnect);
        client.Authenticate ("user@gmail.com", "app-specific-password");
    
        // do stuff...
    
        client.Disconnect (true);
    }
  8. Install MailKit via NuGet

    master

    You can install MailKit using the NuGet package manager. The project also provides related packages like MimeKit and MimeKitLite.

    To install the main MailKit package, use the NuGet CLI or your preferred package manager.

  9. Access GMail using OAuth 2.0

    master

    To use OAuth 2.0 with GMail, obtain credentials from Google and use the Google.Apis.Auth library to acquire an access token. Then, use the SaslMechanismOAuthBearer class to authenticate the MailKit client.

    const string GMailAccount = "username@gmail.com";
    
    var clientSecrets = new ClientSecrets {
        ClientId = "XXX.apps.googleusercontent.com",
        ClientSecret = "XXX"
    };
    
    var codeFlow = new GoogleAuthorizationCodeFlow (new GoogleAuthorizationCodeFlow.Initializer {
        // Cache tokens on Linux/Mac in ~/.local/share/google-filedatastore/CredentialCacheFolder
        DataStore = new FileDataStore ("CredentialCacheFolder", false),
        Scopes = new [] { "https://mail.google.com/" },
        ClientSecrets = clientSecrets,
        LoginHint = GMailAccount
    });
    
    // For a web app, use AuthorizationCodeWebApp instead.
    var codeReceiver = new LocalServerCodeReceiver ();
    var authCode = new AuthorizationCodeInstalledApp (codeFlow, codeReceiver);
    
    var credential = await authCode.AuthorizeAsync (GMailAccount, CancellationToken.None);
    
    if (credential.Token.IsStale)
        await credential.RefreshTokenAsync (CancellationToken.None);
    
    var oauth2 = new SaslMechanismOAuthBearer (credential.UserId, credential.Token.AccessToken);
    
    using (var client = new ImapClient ()) {
        await client.ConnectAsync ("imap.gmail.com", 993, SecureSocketOptions.SslOnConnect);
        await client.AuthenticateAsync (oauth2);
        await client.DisconnectAsync (true);
    }
  10. Set message flags in IMAP

    master
    To update flags (like marking a message as Seen or Deleted), use the Store method on a folder. You must provide the message's uid (or index) and a StoreFlagsRequest specifying the StoreAction (e.g., StoreAction.Add) and the MessageFlags (e.g., MessageFlags.Seen).
  11. Search and sort messages in an IMAP folder

    master

    Use SearchQuery to build complex queries (e.g., DeliveredAfter, SubjectContains, Seen) and combine them with .And(). Use inbox.Search(query) to get matching UIDs. To sort results, use inbox.Sort(query, orderBy) where orderBy is an array of OrderBy values (e.g., OrderBy.ReverseArrival, OrderBy.Subject).

    // Search for messages
    var query = SearchQuery.DeliveredAfter (DateTime.Parse ("2013-01-12"))
        .And (SearchQuery.SubjectContains ("MailKit")).And (SearchQuery.Seen);
    
    foreach (var uid in inbox.Search (query)) {
    	var message = inbox.GetMessage (uid);
    	Console.WriteLine ("[match] {0}: {1}", uid, message.Subject);
    }
    
    // Sort search results
    var orderBy = new [] { OrderBy.ReverseArrival, OrderBy.Subject };
    foreach (var uid in inbox.Sort (query, orderBy)) {
    	var message = inbox.GetMessage (uid);
    	Console.WriteLine ("[match] {0}: {1}", uid, message.Subject);
    }