{"id":13430924,"url":"https://github.com/jstedfast/MimeKit","last_synced_at":"2025-03-16T06:31:42.713Z","repository":{"id":37752204,"uuid":"6433838","full_name":"jstedfast/MimeKit","owner":"jstedfast","description":"A .NET MIME creation and parser library with support for S/MIME, PGP, DKIM, TNEF and Unix mbox spools.","archived":false,"fork":false,"pushed_at":"2024-10-20T14:42:45.000Z","size":38082,"stargazers_count":1826,"open_issues_count":9,"forks_count":371,"subscribers_count":90,"default_branch":"master","last_synced_at":"2024-10-29T15:33:57.032Z","etag":null,"topics":["c-sharp","dkim","email","mbox","mime","mime-parser","parser","pgp","smime","tnef"],"latest_commit_sha":null,"homepage":"http://www.mimekit.net","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jstedfast.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":"jstedfast"}},"created_at":"2012-10-29T00:36:24.000Z","updated_at":"2024-10-27T20:23:44.000Z","dependencies_parsed_at":"2023-10-16T06:25:59.853Z","dependency_job_id":"844bef82-7376-4503-8261-d930a1833e0e","html_url":"https://github.com/jstedfast/MimeKit","commit_stats":null,"previous_names":[],"tags_count":118,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jstedfast%2FMimeKit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jstedfast%2FMimeKit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jstedfast%2FMimeKit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jstedfast%2FMimeKit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jstedfast","download_url":"https://codeload.github.com/jstedfast/MimeKit/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243469864,"owners_count":20295841,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["c-sharp","dkim","email","mbox","mime","mime-parser","parser","pgp","smime","tnef"],"created_at":"2024-07-31T02:00:59.019Z","updated_at":"2025-03-16T06:31:42.702Z","avatar_url":"https://github.com/jstedfast.png","language":"C#","readme":"# MimeKit\n\n|  Package  |Latest Release|Latest Build|\n|:----------|:------------:|:----------:|\n|**MimeKit**|[![MimeKit NuGet](https://img.shields.io/nuget/v/MimeKit.svg?logo=nuget\u0026style=flat-square)](https://www.nuget.org/packages/MimeKit)![MimeKit NuGet Downloads](https://img.shields.io/nuget/dt/MimeKit.svg?style=flat-square)|[![MimeKit MyGet](https://img.shields.io/myget/mimekit/v/MimeKit.svg?logo=nuget\u0026style=flat-square\u0026label=myget)](https://www.myget.org/feed/mimekit/package/nuget/MimeKit)|\n|**MimeKitLite**|[![MimeKitLite NuGet](https://img.shields.io/nuget/v/MimeKitLite.svg?logo=nuget\u0026style=flat-square)](https://www.nuget.org/packages/MimeKitLite)![MimeKitLite NuGet Downloads](https://img.shields.io/nuget/dt/MimeKitLite.svg?style=flat-square)||\n|**MailKit**|[![MailKit NuGet](https://img.shields.io/nuget/v/MailKit.svg?logo=nuget\u0026style=flat-square)](https://www.nuget.org/packages/MailKit)![MailKit NuGet Downloads](https://img.shields.io/nuget/dt/MailKit.svg?style=flat-square)|[![MailKit MyGet](https://img.shields.io/myget/mimekit/v/MailKit.svg?logo=nuget\u0026style=flat-square\u0026label=myget)](https://www.myget.org/feed/mimekit/package/nuget/MailKit)|\n|**MailKitLite**|[![MailKitLite NuGet](https://img.shields.io/nuget/v/MailKitLite.svg?logo=nuget\u0026style=flat-square)](https://www.nuget.org/packages/MailKitLite)![MailKitLite NuGet Downloads](https://img.shields.io/nuget/dt/MailKitLite.svg?style=flat-square)||\n\n\n|   Platform   |Build Status|Code Coverage|Static Analysis|\n|:-------------|:----------:|:-----------:|:-------------:|\n|**Linux/Mac**|[![Build Status](https://github.com/jstedfast/MimeKit/actions/workflows/main.yml/badge.svg?event=push)](https://github.com/jstedfast/MimeKit/actions/workflows/main.yml)|[![Code Coverage](https://coveralls.io/repos/jstedfast/MimeKit/badge.svg?branch=master)](https://coveralls.io/r/jstedfast/MimeKit?branch=master)|[![Static Analysis](https://scan.coverity.com/projects/3201/badge.svg)](https://scan.coverity.com/projects/3201)|\n|**Windows**  |[![Build Status](https://github.com/jstedfast/MimeKit/actions/workflows/main.yml/badge.svg?event=push)](https://github.com/jstedfast/MimeKit/actions/workflows/main.yml)|[![Code Coverage](https://coveralls.io/repos/jstedfast/MimeKit/badge.svg?branch=master)](https://coveralls.io/r/jstedfast/MimeKit?branch=master)|[![Static Analysis](https://scan.coverity.com/projects/3201/badge.svg)](https://scan.coverity.com/projects/3201)|\n\n## What is MimeKit?\n\nMimeKit is a C# library which may be used for the creation and parsing of messages using the Multipurpose\nInternet Mail Extension (MIME), as defined by [numerous IETF specifications](https://github.com/jstedfast/MimeKit/blob/master/RFCs.md).\n\n## Donate\n\nMimeKit is a personal open source project that I have put thousands of hours into perfecting with the\ngoal of making it the very best MIME parser framework for .NET. I need your help to achieve this.\n\nDonating helps pay for things such as web hosting, domain registration and licenses for developer tools\nsuch as a performance profiler, memory profiler, a static code analysis tool, and more. It also helps\nmotivate me to continue working on the project.\n\n\u003ca href=\"https://github.com/sponsors/jstedfast\" _target=\"blank\"\u003e\u003cimg alt=\"Click here to lend your support to MimeKit by making a donation!\" src=\"https://www.paypal.com/en_US/i/btn/x-click-but21.gif\"\u003e\u003c/a\u003e\n\n## History\n\nAs a developer and user of email clients, I had come to realize that the vast majority of email client\n(and server) software had less-than-satisfactory MIME implementations. More often than not these email clients\ncreated broken MIME messages and/or would incorrectly try to parse a MIME message thus subtracting from the full\nbenefits that MIME was meant to provide. MimeKit is meant to address this issue by following the MIME specification\nas closely as possible while also providing programmers with an extremely easy to use high-level API.\n\nThis led me, at first, to implement another MIME parser library called [GMime](https://github.com/jstedfast/gmime)\nwhich is implemented in C and later added a C# binding called GMime-Sharp.\n\nNow that I typically find myself working in C# rather than lower level languages like C, I decided to\nbegin writing a new parser in C# which would not depend on GMime. This would also allow me to have more\nflexibility in that I'd be able to use Generics and create a more .NET-compliant API.\n\n## Performance\n\nWhile mainstream beliefs may suggest that C# can never be as fast as C, it turns out that with a bit of creative\nparser design and a few clever optimizations \n\u003csup\u003e[[1](http://jeffreystedfast.blogspot.com/2013/09/optimization-tips-tricks-used-by.html)]\n[[2](http://jeffreystedfast.blogspot.com/2013/10/optimization-tips-tricks-used-by.html)]\u003c/sup\u003e, MimeKit's\nperformance is actually [on par with GMime](http://jeffreystedfast.blogspot.com/2014/03/gmime-gets-speed-boost.html).\n\nSince GMime is pretty well-known as a high-performance native MIME parser and MimeKit more-or-less matches GMime's\nperformance, it stands to reason that MimeKit is likely unsurpassed in performance in the .NET MIME parser space.\n\nFor a comparison, as I [blogged here](http://jeffreystedfast.blogspot.com/2013/10/optimization-tips-tricks-used-by.html)\n(I have since optimized MimeKit by at least another 30%), MimeKit is more than 25x faster than OpenPOP.NET, 75x\nfaster than SharpMimeTools, and 65x faster than regex-based parsers. Even the commercial MIME parser offerings such\nas LimiLabs' Mail.dll and NewtonIdeas' Mime4Net cannot even come close to matching MimeKit's performance (they are\nboth orders of magnitude slower than MimeKit).\n\nFor comparison purposes, I've published a [MIME parser benchmark](https://github.com/jstedfast/MimeParserBenchmark)\nto make it easier for anyone else to compare the performance of MimeKit to their favourite MIME parser.\n\nHere are the results:\n\n```\nParsing startrek.msg (1000 iterations):\nMimeKit:        0.6989221 seconds\nOpenPop:        25.3056064 seconds\nAE.Net.Mail:    17.5971438 seconds\nMailSystem.NET: 26.3891218 seconds\nMIMER:          76.4538978 seconds\n\nParsing xamarin3.msg (1000 iterations):\nMimeKit:        3.4215505 seconds\nOpenPop:        159.3308053 seconds\nAE.Net.Mail:    132.3044291 seconds\nMailSystem.NET: 133.5832078 seconds\nMIMER:          784.433441 seconds\n```\n\nHow does your MIME parser compare?\n\n\n## License Information\n\n```\nMIT License\n\nCopyright (C) 2012-2025 .NET Foundation and Contributors\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n```\n\n## Installing via NuGet\n\nThe easiest way to install MimeKit is via [NuGet](https://www.nuget.org/packages/MimeKit/).\n\nIn Visual Studio's [Package Manager Console](https://docs.nuget.org/docs/start-here/using-the-package-manager-console),\nenter the following command:\n\n```powershell\nInstall-Package MimeKit\n```\n\n## Getting the Source Code\n\nFirst, you'll need to clone MimeKit from my GitHub repository. To do this using the command-line version of Git,\nyou'll need to issue the following command in your terminal:\n\n```bash\ngit clone --recursive https://github.com/jstedfast/MimeKit.git\n```\n\nIf you are using [TortoiseGit](https://tortoisegit.org) on Windows, you'll need to right-click in the directory\nwhere you'd like to clone MimeKit and select **Git Clone...** in the menu. Once you do that, you'll get the\nfollowing dialog:\n\n![Download the source code using TortoiseGit](https://github.com/jstedfast/MimeKit/blob/master/images/clone.png)\n\nFill in the areas outlined in red and then click **OK**. This will recursively clone MimeKit onto your local machine.\n\n## Updating the Source Code\n\nOccasionally you might want to update your local copy of the source code if I have made changes to MimeKit since you\ndownloaded the source code in the step above. To do this using the command-line version fo Git, you'll need to issue\nthe following commands in your terminal within the MimeKit directory:\n\n```bash\ngit pull\ngit submodule update\n```\n\nIf you are using [TortoiseGit](https://tortoisegit.org) on Windows, you'll need to right-click on the MimeKit\ndirectory and select **Git Sync...** in the menu. Once you do that, you'll need to click the **Pull** and\n**Submodule Update** buttons in the following dialog:\n\n![Update the source code using TortoiseGit](https://github.com/jstedfast/MimeKit/blob/master/images/update.png)\n\n## Building\n\nIn the top-level MimeKit directory, there are a number of solution files; they are:\n\n* **MimeKit.sln** - includes projects for .NET Framework 4.6.2/4.7/4.8, .NETStandard 2.0/2.1, .NET 6.0 as well as the unit tests.\n* **MimeKitLite.sln** - includes projects for the stripped-down versions of MimeKit that drop support for crypto.\n\nOnce you've opened the appropriate MimeKit solution file in [Visual Studio](https://www.visualstudio.com/downloads/),\nyou can choose the **Debug** or **Release** build configuration and then build.\n\nBoth Visual Studio 2022 and Visual Studio 2019 should be able to build MimeKit without any issues, but older versions such as\nVisual Studio 2015 and 2017 will require modifications to the projects in order to build correctly. It has been reported that adding\nNuGet package references to [Microsoft.Net.Compilers](https://www.nuget.org/packages/Microsoft.Net.Compilers/) \u003e= 3.6.0\nand [System.ValueTuple](https://www.nuget.org/packages/System.ValueTuple/) \u003e= 4.5.0 will allow MimeKit to build successfully.\n\nNote: The **Release** build will generate the xml API documentation, but the **Debug** build will not.\n\n## Using MimeKit\n\n### Parsing Messages\n\nOne of the more common operations that MimeKit is meant for is parsing email messages from arbitrary streams.\nThere are two ways of accomplishing this task.\n\nThe first way is to use one of the [Load](https://www.mimekit.net/docs/html/Overload_MimeKit_MimeMessage_Load.htm) methods\non `MimeMessage`:\n\n```csharp\n// Load a MimeMessage from a stream\nvar message = MimeMessage.Load (stream);\n```\n\nOr you can load a message from a file path:\n\n```csharp\n// Load a MimeMessage from a file path\nvar message = MimeMessage.Load (\"message.eml\");\n```\n\nThe second way is to use the [MimeParser](https://www.mimekit.net/docs/html/T_MimeKit_MimeParser.htm) class. For the most\npart, using the `MimeParser` directly is not necessary unless you wish to parse a Unix mbox file stream. However, this is\nhow you would do it:\n\n```csharp\n// Load a MimeMessage from a stream\nvar parser = new MimeParser (stream, MimeFormat.Entity);\nvar message = parser.ParseMessage ();\n```\n\nFor Unix mbox file streams, you would use the parser like this:\n\n```csharp\n// Load every message from a Unix mbox\nvar parser = new MimeParser (stream, MimeFormat.Mbox);\nwhile (!parser.IsEndOfStream) {\n    var message = parser.ParseMessage ();\n\n    // do something with the message\n}\n```\n\n### Getting the Body of a Message\n\nA common misunderstanding about email is that there is a well-defined message body and then a list\nof attachments. This is not really the case. The reality is that MIME is a tree structure of content,\nmuch like a file system.\n\nLuckily, MIME does define a set of general rules for how mail clients should interpret this tree\nstructure of MIME parts. The `Content-Disposition` header is meant to provide hints to the receiving\nclient as to which parts are meant to be displayed as part of the message body and which are meant\nto be interpreted as attachments.\n\nThe `Content-Disposition` header will generally have one of two values: `inline` or `attachment`.\n\nThe meaning of these values should be fairly obvious. If the value is `attachment`, then the content\nof said MIME part is meant to be presented as a file attachment separate from the core message.\nHowever, if the value is `inline`, then the content of that MIME part is meant to be displayed inline\nwithin the mail client's rendering of the core message body. If the `Content-Disposition` header does\nnot exist, then it should be treated as if the value were `inline`.\n\nTechnically, every part that lacks a `Content-Disposition` header or that is marked as `inline`, then,\nis part of the core message body.\n\nThere's a bit more to it than that, though.\n\nModern MIME messages will often contain a `multipart/alternative` MIME container which will generally contain\na `text/plain` and `text/html` version of the text that the sender wrote. The `text/html` version is typically\nformatted much closer to what the sender saw in his or her WYSIWYG editor than the `text/plain` version.\n\nThe reason for sending the message text in both formats is that not all mail clients are capable of displaying\nHTML.\n\nThe receiving client should only display one of the alternative views contained within the `multipart/alternative`\ncontainer. Since alternative views are listed in order of least faithful to most faithful with what the sender\nsaw in his or her WYSIWYG editor, the receiving client *should* walk over the list of alternative views starting\nat the end and working backwards until it finds a part that it is capable of displaying.\n\nExample:\n\n```text\nmultipart/alternative\n  text/plain\n  text/html\n```\n\nAs seen in the example above, the `text/html` part is listed last because it is the most faithful to\nwhat the sender saw in his or her WYSIWYG editor when writing the message.\n\nTo make matters even more complicated, sometimes modern mail clients will use a `multipart/related`\nMIME container instead of a simple `text/html` part in order to embed images and other content\nwithin the HTML.\n\nExample:\n\n```text\nmultipart/alternative\n  text/plain\n  multipart/related\n    text/html\n    image/jpeg\n    video/mp4\n    image/png\n```\n\nIn the example above, one of the alternative views is a `multipart/related` container which contains\nan HTML version of the message body that references the sibling video and images.\n\nNow that you have a rough idea of how a message is structured and how to interpret various MIME entities,\nthe next step is learning how to traverse the MIME tree using MimeKit.\n\nNote: For your convenience, MimeKit's `MimeMessage` class has two properties that can help you get the\n`text/plain` or `text/html` version of the message body. These are `TextBody` and `HtmlBody`,\nrespectively.\n\nKeep in mind, however, that at least with the `HtmlBody` property, it may be that the HTML part is\na child of a `multipart/related`, allowing it to refer to images and other types of media that\nare also contained within that `multipart/related` entity. This property is really only a convenience\nproperty and is not a really good substitute for traversing the MIME structure yourself so that you\nmay properly interpret related content.\n\n### Traversing a MimeMessage\n\nThe `MimeMessage.Body` is the top-level MIME entity of the message. Generally, it will either be a\n`TextPart` or a `Multipart`.\n\nAs an example, if you wanted to rip out all of the attachments of a message, your code might look\nsomething like this:\n\n```csharp\nvar attachments = new List\u003cMimePart\u003e ();\nvar multiparts = new List\u003cMultipart\u003e ();\nvar iter = new MimeIterator (message);\n\n// collect our list of attachments and their parent multiparts\nwhile (iter.MoveNext ()) {\n    var multipart = iter.Parent as Multipart;\n    var part = iter.Current as MimePart;\n\n    if (multipart != null \u0026\u0026 part != null \u0026\u0026 part.IsAttachment) {\n        // keep track of each attachment's parent multipart\n        multiparts.Add (multipart);\n        attachments.Add (part);\n    }\n}\n\n// now remove each attachment from its parent multipart...\nfor (int i = 0; i \u003c attachments.Count; i++)\n    multiparts[i].Remove (attachments[i]);\n```\n\n### Quick and Dirty Enumeration of Message Body Parts\n\nIf you would rather skip the proper way of traversing a MIME tree, another option that MimeKit provides\nis a simple enumerator over the message's body parts in a flat (depth-first) list.\n\nYou can access this flat list via the `BodyParts` property, like so:\n\n```csharp\nforeach (var part in message.BodyParts) {\n   // do something\n}\n```\n\nAnother helper property on the MimeMessage class is the `Attachments` property which works\nmuch the same way as the `BodyParts` property except that it will only contain MIME parts\nwhich have a `Content-Disposition` header value that is set to `attachment`.\n\n### Getting the Decoded Content of a MIME Part\n\nAt some point, you're going to want to extract the decoded content of a `MimePart` (such as an image) and\nsave it to disk or feed it to a UI control to display it.\n\nOnce you've found the `MimePart` object that you'd like to extract the content of, here's how you can\nsave the decoded content to a file:\n\n```csharp\n// This will get the name of the file as specified by the sending mail client.\n// Note: this value *may* be null, so you'll want to handle that case in your code.\nvar fileName = part.FileName;\n\nusing (var stream = File.Create (fileName)) {\n    part.Content.DecodeTo (stream);\n}\n```\n\nYou can also get access to the original raw content by \"opening\" the `Content`. This might be useful\nif you want to pass the content off to a UI control that can do its own loading from a stream.\n\n```csharp\nusing (var stream = part.Content.Open ()) {\n    // At this point, you can now read from the stream as if it were the original,\n    // raw content. Assuming you have an image UI control that could load from a\n    // stream, you could do something like this:\n    imageControl.Load (stream);\n}\n```\n\nThere are a number of useful filters that can be applied to a `FilteredStream`, so if you find this type of\ninterface appealing, I suggest taking a look at the available filters in the `MimeKit.IO.Filters` namespace\nor even write your own! The possibilities are limited only by your imagination.\n\n### Creating a Simple Message\n\nCreating MIME messages using MimeKit is really trivial.\n\n```csharp\nvar message = new MimeMessage ();\nmessage.From.Add (new MailboxAddress (\"Joey\", \"joey@friends.com\"));\nmessage.To.Add (new MailboxAddress (\"Alice\", \"alice@wonderland.com\"));\nmessage.Subject = \"How you doin?\";\n\nmessage.Body = new TextPart (\"plain\") {\n    Text = @\"Hey Alice,\n\nWhat are you up to this weekend? Monica is throwing one of her parties on\nSaturday and I was hoping you could make it.\n\nWill you be my +1?\n\n-- Joey\n\"\n};\n```\n\nA `TextPart` is a leaf-node MIME part with a text media-type. The first argument to the `TextPart` constructor\nspecifies the media-subtype, in this case, \"plain\". Another media subtype you are probably familiar with\nis the \"html\" subtype. Some other examples include \"enriched\", \"rtf\", and \"csv\".\n\nThe `Text` property is the easiest way to both get and set the string content of the MIME part.\n\n### Creating a Message with Attachments\n\nAttachments are just like any other `MimePart`, the only difference is that they typically have\na `Content-Disposition` header with a value of \"attachment\" instead of \"inline\" or no\n`Content-Disposition` header at all.\n\nTypically, when a mail client adds attachments to a message, it will create a `multipart/mixed`\npart and add the text body part and all of the file attachments to the `multipart/mixed.`\n\nHere's how you can do that with MimeKit:\n\n```csharp\nvar message = new MimeMessage ();\nmessage.From.Add (new MailboxAddress (\"Joey\", \"joey@friends.com\"));\nmessage.To.Add (new MailboxAddress (\"Alice\", \"alice@wonderland.com\"));\nmessage.Subject = \"How you doin?\";\n\n// create our message text, just like before (except don't set it as the message.Body)\nvar body = new TextPart (\"plain\") {\n    Text = @\"Hey Alice,\n\nWhat are you up to this weekend? Monica is throwing one of her parties on\nSaturday and I was hoping you could make it.\n\nWill you be my +1?\n\n-- Joey\n\"\n};\n\n// create an image attachment for the file located at path\nvar attachment = new MimePart (\"image\", \"gif\") {\n    Content = new MimeContent (File.OpenRead (path), ContentEncoding.Default),\n    ContentDisposition = new ContentDisposition (ContentDisposition.Attachment),\n    ContentTransferEncoding = ContentEncoding.Base64,\n    FileName = Path.GetFileName (path)\n};\n\n// now create the multipart/mixed container to hold the message text and the\n// image attachment\nvar multipart = new Multipart (\"mixed\");\nmultipart.Add (body);\nmultipart.Add (attachment);\n\n// now set the multipart/mixed as the message body\nmessage.Body = multipart;\n```\n\nOf course, that is just a simple example. A lot of modern mail clients such as Outlook or Thunderbird will \nsend out both a `text/html` and a `text/plain` version of the message text. To do this, you'd create a\n`TextPart` for the `text/plain` part and another `TextPart` for the `text/html` part and then add them to a\n`multipart/alternative` like so:\n\n```csharp\nvar attachment = CreateAttachment ();\nvar plain = CreateTextPlainPart ();\nvar html = CreateTextHtmlPart ();\n\n// Note: it is important that the text/html part is added second, because it is the\n// most expressive version and (probably) the most faithful to the sender's WYSIWYG \n// editor.\nvar alternative = new Multipart (\"alternative\");\nalternative.Add (plain);\nalternative.Add (html);\n\n// now create the multipart/mixed container to hold the multipart/alternative\n// and the image attachment\nvar multipart = new Multipart (\"mixed\");\nmultipart.Add (alternative);\nmultipart.Add (attachment);\n\n// now set the multipart/mixed as the message body\nmessage.Body = multipart;\n```\n\n### Creating a Message Using a BodyBuilder (not Arnold Schwarzenegger)\n\nIf you are used to System.Net.Mail's API for creating messages, you will probably find using a `BodyBuilder`\nmuch more friendly than manually creating the tree of MIME parts. Here's how you could create a message body\nusing a `BodyBuilder`:\n\n```csharp\nvar message = new MimeMessage ();\nmessage.From.Add (new MailboxAddress (\"Joey\", \"joey@friends.com\"));\nmessage.To.Add (new MailboxAddress (\"Alice\", \"alice@wonderland.com\"));\nmessage.Subject = \"How you doin?\";\n\nvar builder = new BodyBuilder ();\n\n// Set the plain-text version of the message text\nbuilder.TextBody = @\"Hey Alice,\n\nWhat are you up to this weekend? Monica is throwing one of her parties on\nSaturday and I was hoping you could make it.\n\nWill you be my +1?\n\n-- Joey\n\";\n\n// generate a Content-Id for the image we'll be referencing\nvar contentId = MimeUtils.GenerateMessageId ();\n\n// Set the html version of the message text\nbuilder.HtmlBody = string.Format (@\"\u003cp\u003eHey Alice,\u003cbr\u003e\n\u003cp\u003eWhat are you up to this weekend? Monica is throwing one of her parties on\nSaturday and I was hoping you could make it.\u003cbr\u003e\n\u003cp\u003eWill you be my +1?\u003cbr\u003e\n\u003cp\u003e-- Joey\u003cbr\u003e\n\u003ccenter\u003e\u003cimg src=\"\"cid:{0}\"\" alt=\"\"selfie.jpg\"\"\u003e\u003c/center\u003e\", contentId);\n\n// Since selfie.jpg is referenced from the html text, we'll need to add it\n// to builder.LinkedResources and then set the Content-Id header value\nbuilder.LinkedResources.Add (@\"C:\\Users\\Joey\\Documents\\Selfies\\selfie.jpg\");\nbuilder.LinkedResources[0].ContentId = contentId;\n\n// We may also want to attach a calendar event for Monica's party...\nbuilder.Attachments.Add (@\"C:\\Users\\Joey\\Documents\\party.ics\");\n\n// Now we just need to set the message body and we're done\nmessage.Body = builder.ToMessageBody ();\n```\n\n### Preparing to use MimeKit's S/MIME support\n\nBefore you can begin using MimeKit's S/MIME support, you will need to decide which\ndatabase to use for certificate storage.\n\nIf you are targetting any of the Xamarin platforms (or Linux), you won't need to do\nanything (although you certainly can if you want to) because, by default, I've\nconfigured MimeKit to use the Mono.Data.Sqlite binding to SQLite.\n\nIf you are on any of the Windows platforms, however, you'll need to decide on whether\nto use one of the conveniently available backends such as the `WindowsSecureMimeContext`\nbackend or the `TemporarySecureMimeContext` backend or else you'll need to pick a\nSystem.Data provider such as\n[System.Data.SQLite](https://www.nuget.org/packages/System.Data.SQLite) to use with\nthe `DefaultSecureMimeContext` base class.\n\nIf you opt for using the `DefaultSecureMimeContext` backend, you'll need to implement\nyour own `DefaultSecureMimeContext` subclass. Luckily, it's very simple to do.\nAssuming you've chosen System.Data.SQLite, here's how you'd implement your own\n`DefaultSecureMimeContext` class:\n\n```csharp\nusing System.Data.SQLite;\nusing MimeKit.Cryptography;\n\nusing MyAppNamespace {\n    class MySecureMimeContext : DefaultSecureMimeContext\n    {\n        public MySecureMimeContext () : base (OpenDatabase (\"C:\\\\wherever\\\\certdb.sqlite\"))\n        {\n        }\n\n        static IX509CertificateDatabase OpenDatabase (string fileName)\n        {\n            var builder = new SQLiteConnectionStringBuilder ();\n            builder.DateTimeFormat = SQLiteDateFormats.Ticks;\n            builder.DataSource = fileName;\n\n            if (!File.Exists (fileName))\n                SQLiteConnection.CreateFile (fileName);\n\n            var sqlite = new SQLiteConnection (builder.ConnectionString);\n            sqlite.Open ();\n\n            return new SqliteCertificateDatabase (sqlite, \"password\");\n        }\n    }\n}\n```\n\nNow that you've implemented your own `SecureMimeContext`, you'll want to register it with MimeKit:\n\n```csharp\nCryptographyContext.Register (typeof (MySecureMimeContext));\n```\n\nNow you are ready to encrypt, decrypt, sign and verify S/MIME messages!\n\nNote: If you choose to use the `WindowsSecureMimeContext` or `TemporarySecureMimeContext` backend,\nyou should register that class instead.\n\n### Preparing to use MimeKit's PGP/MIME support\n\nLike with S/MIME support, you also need to register your own `OpenPgpContext`. Unlike S/MIME, however,\nyou don't need to choose a database if you subclass `GnuPGContext` because it uses GnuPG's PGP keyrings\nto load and store public and private keys. If you choose to subclass `GnuPGContext`, the only thing you\nyou need to do is implement a password callback method:\n\n```csharp\nusing MimeKit.Cryptography;\n\nnamespace MyAppNamespace {\n    class MyGnuPGContext : GnuPGContext\n    {\n        public MyGnuPgContext () : base ()\n        {\n        }\n\n        protected override string GetPasswordForKey (PgpSecretKey key)\n        {\n            // prompt the user (or a secure password cache) for the password for the specified secret key.\n            return \"password\";\n        }\n    }\n}\n```\n\nOnce again, to register your `OpenPgpContext`, you can use the following code snippet:\n\n```csharp\nCryptographyContext.Register (typeof (MyGnuPGContext));\n```\n\nNow you are ready to encrypt, decrypt, sign and verify PGP/MIME messages!\n\n### Encrypting Messages with S/MIME\n\nS/MIME uses an `application/pkcs7-mime` MIME part to encapsulate encrypted content (as well as other things).\n\n```csharp\nvar joey = new MailboxAddress (\"Joey\", \"joey@friends.com\");\nvar alice = new MailboxAddress (\"Alice\", \"alice@wonderland.com\");\n\nvar message = new MimeMessage ();\nmessage.From.Add (joey);\nmessage.To.Add (alice);\nmessage.Subject = \"How you doin?\";\n\n// create our message body (perhaps a multipart/mixed with the message text and some\n// image attachments, for example)\nvar body = CreateMessageBody ();\n\n// now to encrypt our message body using our custom S/MIME cryptography context\nusing (var ctx = new MySecureMimeContext ()) {\n    // Note: this assumes that \"Alice\" has an S/MIME certificate with an X.509\n    // Subject Email identifier that matches her email address. If she doesn't,\n    // try using a SecureMailboxAddress which allows you to specify the\n    // fingerprint of her certificate to use for lookups.\n    message.Body = ApplicationPkcs7Mime.Encrypt (ctx, message.To.Mailboxes, body);\n}\n```\n\n### Decrypting S/MIME Messages\n\nAs mentioned earlier, S/MIME uses an `application/pkcs7-mime` part with an \"smime-type\" parameter with a value of\n\"enveloped-data\" to encapsulate the encrypted content.\n\nThe first thing you must do is find the `ApplicationPkcs7Mime` part (see the section on traversing MIME parts).\n\n```csharp\nif (entity is ApplicationPkcs7Mime) {\n    var pkcs7 = (ApplicationPkcs7Mime) entity;\n\n    if (pkcs7.SecureMimeType == SecureMimeType.EnvelopedData)\n        return pkcs7.Decrypt ();\n}\n```\n\n### Encrypting Messages with PGP/MIME\n\nUnlike S/MIME, PGP/MIME uses `multipart/encrypted` to encapsulate its encrypted data.\n\n```csharp\nvar joey = new MailboxAddress (\"Joey\", \"joey@friends.com\");\nvar alice = new MailboxAddress (\"Alice\", \"alice@wonderland.com\");\n\nvar message = new MimeMessage ();\nmessage.From.Add (joey);\nmessage.To.Add (alice);\nmessage.Subject = \"How you doin?\";\n\n// create our message body (perhaps a multipart/mixed with the message text and some\n// image attachments, for example)\nvar body = CreateMessageBody ();\n\n// now to encrypt our message body using our custom PGP/MIME cryptography context\nusing (var ctx = new MyGnuPGContext ()) {\n    // Note: this assumes that \"Alice\" has a public PGP key that matches her email\n    // address. If she doesn't, try using a SecureMailboxAddress which allows you\n    // to specify the fingerprint of her public PGP key to use for lookups.\n    message.Body = MultipartEncrypted.Encrypt (ctx, message.To.Mailboxes, body);\n}\n```\n\n### Decrypting PGP/MIME Messages\n\nAs mentioned earlier, PGP/MIME uses a `multipart/encrypted` part to encapsulate the encrypted content.\n\nA `multipart/encrypted` contains exactly 2 parts: the first `MimeEntity` is the version information while the\nsecond `MimeEntity` is the actual encrypted content and will typically be an `application/octet-stream`.\n\nThe first thing you must do is find the `MultipartEncrypted` part (see the section on traversing MIME parts).\n\n```csharp\nif (entity is MultipartEncrypted) {\n    var encrypted = (MultipartEncrypted) entity;\n\n    return encrypted.Decrypt ();\n}\n```\n\n### Digitally Signing Messages with S/MIME or PGP/MIME\n\nBoth S/MIME and PGP/MIME use a `multipart/signed` to contain the signed content and the detached signature data.\n\nHere's how you might digitally sign a message using S/MIME:\n\n```csharp\nvar joey = new MailboxAddress (\"Joey\", \"joey@friends.com\");\nvar alice = new MailboxAddress (\"Alice\", \"alice@wonderland.com\");\n\nvar message = new MimeMessage ();\nmessage.From.Add (joey);\nmessage.To.Add (alice);\nmessage.Subject = \"How you doin?\";\n\n// create our message body (perhaps a multipart/mixed with the message text and some\n// image attachments, for example)\nvar body = CreateMessageBody ();\n\n// now to digitally sign our message body using our custom S/MIME cryptography context\nusing (var ctx = new MySecureMimeContext ()) {\n    // Note: this assumes that \"Joey\" has an S/MIME signing certificate and private key\n    // with an X.509 Subject Email identifier that matches Joey's email address.\n    message.Body = MultipartSigned.Create (ctx, joey, DigestAlgorithm.Sha1, body);\n}\n```\n\nFor S/MIME, if you have a way for the user to configure which S/MIME certificate to use\nas their signing certificate, you could also do something more like this:\n\n```csharp\n// now to digitally sign our message body using our custom S/MIME cryptography context\nusing (var ctx = new MySecureMimeContext ()) {\n    var certificate = GetJoeysX509Certificate ();\n    var signer = new CmsSigner (certificate);\n    signer.DigestAlgorithm = DigestAlgorithm.Sha1;\n\n    message.Body = MultipartSigned.Create (ctx, signer, body);\n}\n```\n\nIf you'd prefer to use PGP instead of S/MIME, things work almost exactly the same except that you\nwould use an OpenPGP cryptography context. For example, you might use a subclass of the\n`GnuPGContext` that comes with MimeKit if you want to re-use the user's GnuPG keyrings (you can't\nuse `GnuPGContext` directly because it has no way of prompting the user for their passphrase).\n\nFor the sake of this example, let's pretend that you've written a minimal subclass of\n`MimeKit.Cryptography.GnuPGContext` that only overrides the `GetPassword()` method and\nthat this subclass is called `MyGnuPGContext`.\n\n```csharp\n// now to digitally sign our message body using our custom OpenPGP cryptography context\nusing (var ctx = new MyGnuPGContext ()) {\n    // Note: this assumes that \"Joey\" has a PGP key that matches his email address.\n    message.Body = MultipartSigned.Create (ctx, joey, DigestAlgorithm.Sha1, body);\n}\n```\n\nJust like S/MIME, however, you can also do your own PGP key lookups instead of\nrelying on email addresses to match up with the user's private key.\n\n```csharp\n// now to digitally sign our message body using our custom OpenPGP cryptography context\nusing (var ctx = new MyGnuPGContext ()) {\n    var key = GetJoeysPrivatePgpKey ();\n    message.Body = MultipartSigned.Create (ctx, key, DigestAlgorithm.Sha1, body);\n}\n```\n\n### Verifying S/MIME and PGP/MIME Digital Signatures\n\nAs mentioned earlier, both S/MIME and PGP/MIME typically use a `multipart/signed` part to contain the\nsigned content and the detached signature data.\n\nA `multipart/signed` contains exactly 2 parts: the first `MimeEntity` is the signed content while the second\n`MimeEntity` is the detached signature and, by default, will either be an `ApplicationPgpSignature` part or\nan `ApplicationPkcs7Signature` part (depending on whether the sending client signed using OpenPGP or S/MIME).\n\nBecause the `multipart/signed` part may have been signed by multiple signers, it is important to\nverify each of the digital signatures (one for each signer) that are returned by the\n`MultipartSigned.Verify()` method:\n\n```csharp\nif (entity is MultipartSigned) {\n    var signed = (MultipartSigned) entity;\n\n    foreach (var signature in signed.Verify ()) {\n        try {\n            bool valid = signature.Verify ();\n\n            // If valid is true, then it signifies that the signed content has not been\n            // modified since this particular signer signed the content.\n            //\n            // However, if it is false, then it indicates that the signed content has\n            // been modified.\n        } catch (DigitalSignatureVerifyException) {\n            // There was an error verifying the signature.\n        }\n    }\n}\n```\n\nIt should be noted, however, that while most S/MIME clients will use the preferred `multipart/signed`\napproach, it is possible that you may encounter an `application/pkcs7-mime` part with an \"smime-type\"\nparameter set to \"signed-data\". Luckily, MimeKit can handle this format as well:\n\n```csharp\nif (entity is ApplicationPkcs7Mime) {\n    var pkcs7 = (ApplicationPkcs7Mime) entity;\n\n    if (pkcs7.SecureMimeType == SecureMimeType.SignedData) {\n        // extract the original content and get a list of signatures\n        MimeEntity extracted;\n\n        // Note: if you are rendering the message, you'll want to render the\n        // extracted mime part rather than the application/pkcs7-mime part.\n        foreach (var signature in pkcs7.Verify (out extracted)) {\n            try {\n                bool valid = signature.Verify ();\n\n                // If valid is true, then it signifies that the signed content has not\n                // been modified since this particular signer signed the content.\n                //\n                // However, if it is false, then it indicates that the signed content\n                // has been modified.\n            } catch (DigitalSignatureVerifyException) {\n                // There was an error verifying the signature.\n            }\n        }\n    }\n}\n```\n\n### Signing Messages with DKIM\n\nIn addition to OpenPGP and S/MIME, MimeKit also supports DKIM signatures. To sign a message using DKIM,\nyou'll first need a private key. In the following example, assume that the private key is saved in a\nfile called **privatekey.pem**:\n\n```csharp\nvar headers = new HeaderId[] { HeaderId.From, HeaderId.Subject, HeaderId.Date };\nvar signer = new DkimSigner (\"privatekey.pem\", \"example.com\", \"brisbane\", DkimSignatureAlgorithm.RsaSha256) {\n    HeaderCanonicalizationAlgorithm = DkimCanonicalizationAlgorithm.Simple,\n    BodyCanonicalizationAlgorithm = DkimCanonicalizationAlgorithm.Simple,\n    AgentOrUserIdentifier = \"@eng.example.com\",\n    QueryMethod = \"dns/txt\",\n};\n\n// Prepare the message body to be sent over a 7bit transport (such as older versions of SMTP).\n// Note: If the SMTP server you will be sending the message over supports the 8BITMIME extension,\n// then you can use `EncodingConstraint.EightBit` instead.\nmessage.Prepare (EncodingConstraint.SevenBit);\n\nsigner.Sign (message, headers);\n```\n\nAs you can see, it's fairly straight forward.\n\n### Verifying DKIM Signatures\n\nVerifying DKIM signatures is slightly more involved than creating them because you'll need to write a custom\nimplementation of the `IDkimPublicKeyLocator` interface. Typically, this custom class will need to download\nthe DKIM public keys via your chosen DNS library as they are requested by MimeKit during verification of\nDKIM signature headers.\n\nOnce you've implemented a custom `IDkimPublicKeyLocator`, verifying signatures is fairly trivial. Most of the work\nneeded will be in the `IDkimPublicKeyLocator` implementation. As an example of how to implement this interface,\nhere is one possible implementation using the [Heijden.DNS](https://www.nuget.org/packages/Heijden.Dns/) library:\n\n```csharp\nusing System;\nusing System.IO;\nusing System.Text;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing System.Collections.Generic;\n\nusing Heijden.DNS;\n\nusing Org.BouncyCastle.Crypto;\n\nusing MimeKit;\nusing MimeKit.Cryptography;\n\nnamespace DkimVerifierExample\n{\n    // Note: By using the DkimPublicKeyLocatorBase, we avoid having to parse the DNS TXT records\n    // in order to get the public key ourselves.\n    class DkimPublicKeyLocator : DkimPublicKeyLocatorBase\n    {\n        readonly Dictionary\u003cstring, AsymmetricKeyParameter\u003e cache;\n        readonly Resolver resolver;\n\n        public DkimPublicKeyLocator ()\n        {\n            cache = new Dictionary\u003cstring, AsymmetricKeyParameter\u003e ();\n\n            resolver = new Resolver (\"8.8.8.8\") {\n                TransportType = TransportType.Udp,\n                UseCache = true,\n                Retries = 3\n            };\n        }\n\n        AsymmetricKeyParameter DnsLookup (string domain, string selector, CancellationToken cancellationToken)\n        {\n            var query = selector + \"._domainkey.\" + domain;\n            AsymmetricKeyParameter pubkey;\n\n            // checked if we've already fetched this key\n            if (cache.TryGetValue (query, out pubkey))\n                return pubkey;\n\n            // make a DNS query\n            var response = resolver.Query (query, QType.TXT);\n            var builder = new StringBuilder ();\n\n            // combine the TXT records into 1 string buffer\n            foreach (var record in response.RecordsTXT) {\n                foreach (var text in record.TXT)\n                    builder.Append (text);\n            }\n\n            var txt = builder.ToString ();\n\n            // DkimPublicKeyLocatorBase provides us with this helpful method.\n            pubkey = GetPublicKey (txt);\n\n            cache.Add (query, pubkey);\n\n            return pubkey;\n        }\n\n        public AsymmetricKeyParameter LocatePublicKey (string methods, string domain, string selector, CancellationToken cancellationToken = default (CancellationToken))\n        {\n            var methodList = methods.Split (new char[] { ':' }, StringSplitOptions.RemoveEmptyEntries);\n            for (int i = 0; i \u003c methodList.Length; i++) {\n                if (methodList[i] == \"dns/txt\")\n                    return DnsLookup (domain, selector, cancellationToken);\n            }\n\n            throw new NotSupportedException (string.Format (\"{0} does not include any suported lookup methods.\", methods));\n        }\n\n        public Task\u003cAsymmetricKeyParameter\u003e LocatePublicKeyAsync (string methods, string domain, string selector, CancellationToken cancellationToken = default (CancellationToken))\n        {\n            return Task.Run (() =\u003e {\n                return LocatePublicKey (methods, domain, selector, cancellationToken);\n            }, cancellationToken);\n        }\n    }\n\n    class Program\n    {\n        public static void Main (string[] args)\n        {\n            if (args.Length == 0) {\n                Help ();\n                return;\n            }\n\n            for (int i = 0; i \u003c args.Length; i++) {\n                if (args[i] == \"--help\") {\n                    Help ();\n                    return;\n                }\n            }\n\n            var locator = new DkimPublicKeyLocator ();\n            var verifier = new DkimVerifier (locator);\n\n            for (int i = 0; i \u003c args.Length; i++) {\n                if (!File.Exists (args[i])) {\n                    Console.Error.WriteLine (\"{0}: No such file.\", args[i]);\n                    continue;\n                }\n\n                Console.Write (\"{0} -\u003e \", args[i]);\n\n                var message = MimeMessage.Load (args[i]);\n                var index = message.Headers.IndexOf (HeaderId.DkimSignature);\n\n                if (index == -1) {\n                    Console.WriteLine (\"NO SIGNATURE\");\n                    continue;\n                }\n\n                var dkim = message.Headers[index];\n\n                if (verifier.Verify (message, dkim)) {\n                    // the DKIM-Signature header is valid!\n                    Console.ForegroundColor = ConsoleColor.Green;\n                    Console.WriteLine (\"VALID\");\n                    Console.ResetColor ();\n                } else {\n                    // the DKIM-Signature is invalid!\n                    Console.ForegroundColor = ConsoleColor.Red;\n                    Console.WriteLine (\"INVALID\");\n                    Console.ResetColor ();\n                }\n            }\n        }\n\n        static void Help ()\n        {\n            Console.WriteLine (\"Usage is: DkimVerifier [options] [messages]\");\n            Console.WriteLine ();\n            Console.WriteLine (\"Options:\");\n            Console.WriteLine (\"  --help               This help menu.\");\n        }\n    }\n}\n```\n\n### Signing Messages with ARC\n\nSigning with ARC is similar to DKIM but quite a bit more involved. In order to sign with\nARC, you must first validate that the existing message is authentictic and produce\nan ARC-Authentication-Results header containing the methods that you used to\nauthenticate the message as well as their results.\n\nThe abstract [ArcSigner](https://www.mimekit.net/docs/html/T_MimeKit_Cryptography_ArcSigner.htm)\nclass provided by MimeKit will need to be subclassed before it can be used. An example subclass\nthat provides 2 different implementations for generating the ARC-Authentication-Results header\ncan be seen below:\n\n```csharp\nusing System;\nusing System.Linq;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing System.Collections.Generic;\n\nusing MimeKit;\nusing MimeKit.Cryptography;\n\nnamespace ArcSignerExample\n{\n    class MyArcSigner : ArcSigner\n    {\n        public MyArcSigner (string fileName, string domain, string selector, DkimSigningAlgorithm algorithm = DkimSignatureAlgorithm.RsaSha256)\n               : base (fileName, domain, selector, algorithm)\n        {\n        }\n\n        /// \u003csummary\u003e\n        /// Generate the ARC-Authentication-Results header.\n        /// \u003c/summary\u003e\n        /// \u003cremarks\u003e\n        /// The ARC-Authentication-Results header contains information detailing the results of\n        /// authenticating/verifying the message via ARC, DKIM, SPF, etc.\n        ///\n        /// In the following implementation, we assume that all of these authentication results\n        /// have already been determined by other mail software that has added some Authentication-Results\n        /// headers containing this information.\n        ///\n        /// Note: This method is used when ArcSigner.Sign() is called instead of ArcSigner.SignAsync().\n        /// \u003c/remarks\u003e\n        protected override AuthenticationResults GenerateArcAuthenticationResults (FormatOptions options, MimeMessage message, CancellationToken cancellationToken)\n        {\n            const string AuthenticationServiceIdentifier = \"lists.example.com\";\n\n            var results = new AuthenticationResults (AuthenticationServiceIdentifier);\n\n            for (int i = 0; i \u003c message.Headers.Count; i++) {\n                var header = message.Headers[i];\n\n                if (header.Id != HeaderId.AuthenticationResults)\n                    continue;\n\n                if (!AuthenticationResults.TryParse (header.RawValue, out AuthenticationResults authres))\n                    continue;\n\n                if (authres.AuthenticationServiceIdentifier != AuthenticationServiceIdentifier)\n                    continue;\n\n                // Merge any authentication results that aren't already known.\n                foreach (var result in authres.Results) {\n                    if (!results.Results.Any (r =\u003e r.Method == result.Method))\n                        results.Results.Add (result);\n                }\n            }\n\n            return results;\n        }\n\n        /// \u003csummary\u003e\n        /// Generate the ARC-Authentication-Results asynchronously.\n        /// \u003c/summary\u003e\n        /// \u003cremarks\u003e\n        /// The ARC-Authentication-Results header contains information detailing the results of\n        /// authenticating/verifying the message via ARC, DKIM, SPF, etc.\n        ///\n        /// In the following implementation, we assume that we have to verify all of the various\n        /// authentication methods ourselves.\n        ///\n        /// Note: This method is used when ArcSigner.SignAsync() is called instead of ArcSigner.Sign().\n        /// \u003c/remarks\u003e\n        protected override async Task\u003cAuthenticationResults\u003e GenerateArcAuthenticationResultsAsync (FormatOptions options, MimeMessage message, CancellationToken cancellationToken)\n        {\n            const string AuthenticationServiceIdentifier = \"lists.example.com\";\n\n            var results = new AuthenticationResults (AuthenticationServiceIdentifier);\n            var locator = new DkimPublicKeyLocator (); // from the DKIM example above\n            var dkimVerifier = new DkimVerifier (locator);\n            var arcVerifier = new ArcVerifier (locator);\n            AuthenticationMethodResult method;\n\n            // Add the ARC authentication results\n            try {\n                var arc = await arcVerifier.VerifyAsync (message, cancellationToken);\n                var result = arc.Chain.ToString ().ToLowerInvariant ();\n\n                method = new AuthenticationMethodResult (\"arc\", result);\n                results.Results.Add (method);\n            } catch {\n                // Likely a DNS error\n                method = new AuthenticationMethodResult (\"arc\", \"fail\");\n                method.Reason = \"DNS error\";\n                results.Results.Add (method);\n            }\n\n            // Add authentication results for each DKIM signature\n            foreach (var dkimHeader in message.Headers.Where (h =\u003e h.Id == HeaderId.DkimSignature)) {\n                string result;\n\n                try {\n                    if (await dkimVerifier.VerifyAsync (message, cancellationToken)) {\n                        result = \"pass\";\n                    } else {\n                        result = \"fail\";\n                    }\n                } catch {\n                    result = \"fail\";\n                }\n\n                method = new AuthenticationMethodResult (\"dkim\", result);\n\n                // Parse the DKIM-Signature header so that we can add some\n                // properties to our method result.\n                var params = dkimHeader.Value.Replace (\" \", \"\").Split (new char[] { ';' });\n                var i = params.FirstOrDefault (p =\u003e p.StartsWith (\"i=\", StringComparison.Ordinal));\n                var b = params.FirstOrDefault (p =\u003e p.StartsWith (\"b=\", StringComparison.Ordinal));\n\n                if (i != null)\n                    method.Parameters.Add (\"header.i\", i.Substring (2));\n\n                if (b != null)\n                    method.Parameters.Add (\"header.b\", b.Substring (2, 8));\n\n                results.Results.Add (method);\n            }\n\n            return results;\n        }\n    }\n}\n```\n\nOnce you have a custom `ArcSigner` class, the actual logic for signing is almost identical to DKIM.\n\nNote: As with the DKIM signing example above, assume that the private key is saved in a\nfile called **privatekey.pem**:\n\n```csharp\nvar headers = new HeaderId[] { HeaderId.From, HeaderId.Subject, HeaderId.Date };\nvar signer = new MyArcSigner (\"privatekey.pem\", \"example.com\", \"brisbane\", DkimSignatureAlgorithm.RsaSha256) {\n    HeaderCanonicalizationAlgorithm = DkimCanonicalizationAlgorithm.Relaxed,\n    BodyCanonicalizationAlgorithm = DkimCanonicalizationAlgorithm.Relaxed,\n    AgentOrUserIdentifier = \"@eng.example.com\"\n};\n\n// Prepare the message body to be sent over a 7bit transport (such as older versions of SMTP).\n// Note: If the SMTP server you will be sending the message over supports the 8BITMIME extension,\n// then you can use `EncodingConstraint.EightBit` instead.\nmessage.Prepare (EncodingConstraint.SevenBit);\n\nsigner.Sign (message, headers); // or SignAsync\n```\n\n### Verifying ARC Signatures\n\nJust like with verifying DKIM signatures, you will need to implement the `IDkimPublicKeyLocator`\ninterface. To see an example of how to implement this interface, see the DKIM signature verification\nexample above.\n\nThe `ArcVerifier` works exactly the same as the `DkimVerifier` except that it is not necessary\nto provide a `Header` argument to the `Verify` or `VerifyAsync` method.\n\n```csharp\nvar verifier = new ArcVerifier (new DkimPublicKeyLocator ());\nvar results = await verifier.VerifyAsync (message);\n\n// The Chain results are the only real important results.\nConsole.WriteLine (\"ARC results: {0}\", results.Chain);\n```\n\n## Contributing\n\nThe first thing you'll need to do is fork MimeKit to your own GitHub repository. For instructions on how to\ndo that, see the section titled **Getting the Source Code**.\n\nIf you use [Visual Studio for Mac](https://visualstudio.microsoft.com/vs/mac/) or [MonoDevelop](https://monodevelop.com),\nall of the solution files are configured with the coding style used by MimeKit. If you use Visual Studio on Windows or\nsome other editor, please try to maintain the existing coding style as best as you can.\n\nOnce you've got some changes that you'd like to submit upstream to the official MimeKit repository,\nsend me a **Pull Request** and I will try to review your changes in a timely manner.\n\nIf you'd like to contribute but don't have any particular features in mind to work on, check out the issue\ntracker and look for something that might pique your interest!\n\n## Reporting Bugs\n\nHave a bug or a feature request? Please open a new\n[bug report](https://github.com/jstedfast/MimeKit/issues/new?template=bug_report.md)\nor\n[feature request](https://github.com/jstedfast/MimeKit/issues/new?template=feature_request.md).\n\nBefore opening a new issue, please search through any [existing issues](https://github.com/jstedfast/MimeKit/issues)\nto avoid submitting duplicates. It may also be worth checking the\n[FAQ](https://github.com/jstedfast/MimeKit/blob/master/FAQ.md) for common questions that other developers\nhave had.\n\nIf you are getting an exception from somewhere within MimeKit, don't just provide the `Exception.Message`\nstring. Please include the `Exception.StackTrace` as well. The `Message`, by itself, is often useless.\n\n## Documentation\n\nAPI documentation can be found at [https://www.mimekit.net/docs](https://www.mimekit.net/docs).\n\nA copy of the XML-formatted API reference documentation is also included in the NuGet package.\n\n## .NET Foundation\n\nMimeKit is a [.NET Foundation](https://www.dotnetfoundation.org/projects) project.\n\nThis project has adopted the code of conduct defined by the [Contributor Covenant](https://contributor-covenant.org/) to clarify expected behavior in our community. For more information, see the [.NET Foundation Code of Conduct](https://www.dotnetfoundation.org/code-of-conduct).\n","funding_links":["https://github.com/sponsors/jstedfast","https://www.paypal.com/en_US/i/btn/x-click-but21.gif"],"categories":["Frameworks, Libraries and Tools","C#","others","框架, 库和工具","Code","C\\#","Mail","邮件","Parsing"],"sub_categories":["Mail","邮件","Library","GUI - other"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjstedfast%2FMimeKit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjstedfast%2FMimeKit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjstedfast%2FMimeKit/lists"}