diff --git a/.gitignore b/.gitignore
index 513f739..86647ca 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,7 @@
node_modules
__pycache__
*.pyc
+
+# .NET
+bin/
+obj/
diff --git a/dotnet/JmapSamples.csproj b/dotnet/JmapSamples.csproj
new file mode 100644
index 0000000..e78bcba
--- /dev/null
+++ b/dotnet/JmapSamples.csproj
@@ -0,0 +1,15 @@
+
+
+
+ Exe
+ net6.0;net7.0
+ enable
+ enable
+ preview
+
+
+
+
+
+
+
diff --git a/dotnet/Program.cs b/dotnet/Program.cs
new file mode 100644
index 0000000..811059a
--- /dev/null
+++ b/dotnet/Program.cs
@@ -0,0 +1,18 @@
+using JmapNet;
+using JmapNet.Client;
+using JmapSamples;
+
+// initialize the client
+var hostName = Environment.GetEnvironmentVariable("JMAP_HOSTNAME") ?? "api.fastmail.com";
+var userName = Environment.GetEnvironmentVariable("JMAP_USERNAME") ?? throw new InvalidOperationException("no user name");
+var token = Environment.GetEnvironmentVariable("JMAP_TOKEN") ?? throw new InvalidOperationException("no token");
+
+using var jmap = await JmapClient.Init(new Uri($"https://{hostName}"), token)
+ ?? throw new InvalidOperationException("couldn't initialize client");
+
+var accountId = jmap.Session.PrimaryAccounts[JmapConstants.JmapMailCapability];
+
+// programs
+await jmap.HelloWorld(accountId, userName);
+await jmap.TopTen(accountId);
+await jmap.TopTenWithHelpers();
diff --git a/dotnet/Samples.cs b/dotnet/Samples.cs
new file mode 100644
index 0000000..b0ba5e8
--- /dev/null
+++ b/dotnet/Samples.cs
@@ -0,0 +1,147 @@
+using JmapNet;
+using JmapNet.Client;
+using JmapNet.Models.Core;
+using JmapNet.Models.Mail;
+
+namespace JmapSamples;
+
+internal static class Samples
+{
+ // query for the drafts mailbox, create a new email, and send it
+ internal static async Task HelloWorld(this JmapClient jmap, string accountId, string userName)
+ {
+ // get drafts mailbox
+ var mailboxQueryFilter = new JmapMailboxQueryFilter
+ {
+ Name = "Drafts"
+ };
+
+ var mailboxQueryRequest = new JmapMailboxQueryRequest(accountId)
+ {
+ Filter = mailboxQueryFilter
+ }.Invoke("a");
+
+ var mailboxQueryResponse = await jmap.SendRequest(mailboxQueryRequest);
+
+ var draftMailboxId = mailboxQueryResponse
+ ?.GetIds(mailboxQueryRequest)
+ ?.SingleOrDefault() ?? throw new InvalidOperationException("couldn't get draft mailbox");
+
+ // compose email
+ const string emailBody =
+ """
+ Hi!
+
+ This email may not look like much, but I sent it with JMAP, a protocol
+ designed to make it easier to manage email, contacts, calendars, and more of
+ your digital life in general.
+
+ Pretty cool, right?
+
+ --
+ This email sent from my next-generation email system at Fastmail.
+ """;
+
+ var email = new JmapEmail
+ {
+ From = new List { new("", userName) },
+ To = new List { new("", userName) },
+ Subject = "Hello, world!",
+ Keywords = new Dictionary { { "$draft", true } },
+ MailboxIds = new Dictionary { { draftMailboxId, true } },
+ BodyStructure = new JmapEmailBodyPart { Type = "text/plain", PartId = "body" },
+ BodyValues = new Dictionary { { "body", new JmapEmailBodyValue { Value = emailBody } } }
+ };
+
+ // get identity
+ var identityGetRequest = new JmapIdentityGetRequest(accountId).Invoke("i");
+ var identityGetResponse = await jmap.SendRequest(identityGetRequest);
+ var identities = identityGetResponse.GetItems(identityGetRequest);
+ var identity = identities.First(ident => ident.Email == userName);
+
+ // chained Email/set and EmailSubmission/set calls in a single request
+ var emailSet = new JmapEmailSetRequest(accountId)
+ {
+ Create = new Dictionary { { "draft", email } }
+ }.Invoke("a");
+
+ var emailSubmissionSet = new JmapEmailSubmissionSetRequest(accountId)
+ {
+ Create = new Dictionary
+ {
+ { "sendIt", new JmapEmailSubmission { EmailId = "#draft", IdentityId = identity.Id } }
+ },
+ OnSuccessDestroyEmail = new List
+ {
+ "#sendIt"
+ }
+ }.Invoke("b");
+
+ var emailCreateResponse = await jmap.SendRequest(emailSet, emailSubmissionSet);
+
+ Console.WriteLine($"{Util.JsonStr(emailCreateResponse)}");
+ }
+
+ // query for the inbox, query and retrieve the 10 most recent emails
+ internal static async Task TopTen(this JmapClient jmap, string accountId)
+ {
+ // get inbox
+ var inboxQuery = new JmapMailboxQueryRequest(accountId)
+ {
+ Filter = new JmapMailboxQueryFilter { Role = "inbox", HasAnyRole = true }
+ }.Invoke("a");
+
+ var inboxQueryResponse = await jmap.SendRequest(inboxQuery);
+ var inboxId = inboxQueryResponse
+ ?.GetIds(inboxQuery)
+ ?.SingleOrDefault() ?? throw new InvalidOperationException("couldn't get inbox ID");
+
+ // query emails
+ var emailQueryFilter = new JmapEmailQueryFilter { InMailbox = inboxId };
+ var emailQuerySort = new List { JmapComparator.Desc(nameof(JmapEmail.ReceivedAt)) };
+ var emailQueryLimit = 10;
+ var emailGetProperties = new List { "receivedAt", "subject" };
+
+ var emailQuery = new JmapEmailQueryRequest(accountId)
+ {
+ Filter = emailQueryFilter,
+ Sort = emailQuerySort,
+ Limit = emailQueryLimit
+ }.Invoke("a");
+
+ var emailGet = new JmapEmailGetRequest(accountId)
+ {
+ References = new Dictionary
+ {
+ { "#ids", emailQuery.GetRef("/ids/*") }
+ },
+ Properties = emailGetProperties
+ }.Invoke("b");
+
+ var emailResponse = await jmap.SendRequest(emailQuery, emailGet);
+
+ var emails = emailResponse.GetItems(emailGet);
+
+ foreach (var email in emails)
+ Console.WriteLine($"{email.ReceivedAt} - {email.Subject}");
+ }
+
+ // same as above, but with high level helper methods
+ internal static async Task TopTenWithHelpers(this JmapClient jmap)
+ {
+ // get inbox
+ var inboxResults = await jmap.GetMailboxes(new JmapMailboxQueryFilter { Role = "inbox", HasAnyRole = true });
+ var inbox = inboxResults.Single();
+
+ // query emails
+ var emailQueryFilter = new JmapEmailQueryFilter { InMailbox = inbox.Id };
+ var emailQuerySort = new List { JmapComparator.Desc(nameof(JmapEmail.ReceivedAt)) };
+ var emailQueryLimit = 10;
+ var emailGetProperties = new List { "receivedAt", "subject" };
+
+ var emails = await jmap.GetEmails(emailQueryFilter, emailQuerySort, emailGetProperties, emailQueryLimit);
+
+ foreach (var email in emails)
+ Console.WriteLine($"{email.ReceivedAt} - {email.Subject}");
+ }
+}
\ No newline at end of file