diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000..7f3f684
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,16 @@
+# Required to run the Mailinator integration tests. Leave unset to skip the suite.
+MAILINATOR_TEST_API_TOKEN=
+
+# Optional values used by endpoint-specific integration tests.
+MAILINATOR_TEST_DOMAIN_PRIVATE=
+MAILINATOR_TEST_INBOX=
+MAILINATOR_TEST_PHONE_NUMBER=
+MAILINATOR_TEST_MESSAGE_WITH_ATTACHMENT_ID=
+MAILINATOR_TEST_ATTACHMENT_ID=
+MAILINATOR_TEST_DELETE_DOMAIN=
+MAILINATOR_TEST_WEBHOOKTOKEN_PRIVATEDOMAIN=
+MAILINATOR_TEST_WEBHOOKTOKEN_CUSTOMSERVICE=
+MAILINATOR_TEST_AUTH_SECRET=
+MAILINATOR_TEST_AUTH_ID=
+MAILINATOR_TEST_WEBHOOK_INBOX=
+MAILINATOR_TEST_WEBHOOK_CUSTOMSERVICE=
diff --git a/.gitignore b/.gitignore
index ad3b5f0..d8c4de2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -275,4 +275,5 @@ Session.vim
# Private test configuration and binaries.
config.ps1
-**/IISApplications
\ No newline at end of file
+.env
+**/IISApplications
diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 0000000..74db6be
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,24 @@
+# Repository instructions
+
+## Project layout
+
+- `mailinator-csharp-client/` contains the SDK. It targets `net471` and `netstandard2.0`; do not change the supported frameworks without an explicit compatibility decision.
+- API methods live in `Clients/ApiClients//`, and their request, response, and entity types live in the matching `Models//` tree.
+- `mailinator-csharp-client-tests/` is a legacy .NET Framework 4.7.2 MSTest project whose tests call the live Mailinator API.
+- `eng/OpenApiCoverageCheck/` is a .NET 8 tool for comparing the SDK request surface with the Mailinator OpenAPI specification. See `eng/README.md` for usage.
+
+## Making SDK changes
+
+- Treat the [Mailinator OpenAPI specification](https://github.com/manybrain/mailinatordocs/blob/main/openapi/mailinator-api.yaml) as the source of truth for documented endpoints.
+- Preserve the `/api/v2` base path defined in `MailinatorClient.cs`. Endpoint clients receive a relative prefix and append their operation paths.
+- Follow the existing RestSharp request pattern: create requests with `GetRequest`, use `AddUrlSegment` for path values, `AddSafeQueryParameter` for optional query values, and `AddJsonBody` for JSON bodies.
+- Keep public operations asynchronous and named with the `Async` suffix. Put new models in the matching area and layer (`Requests`, `Responses`, or `Entities`) using the existing namespaces.
+- Do not remove or silently change obsolete public members as part of unrelated work; that is a breaking API change.
+- When the public API changes, update relevant examples and release documentation (`EXAMPLES.md`, `README.md`, and `CHANGELOG.md`) in the same change.
+
+## Verification
+
+- Build the solution with `dotnet build mailinator-csharp-client.sln` when the installed SDK supports all target frameworks.
+- For endpoint or parameter changes, run the coverage tool described in `eng/README.md`. Prefer a checked-out spec with `--spec`; omitting it fetches the current spec from GitHub.
+- Treat the MSTest suite as integration testing, not as an offline unit suite. It requires a deliberately configured Mailinator account and can create or delete domains, rules, and messages. Do not run it against live credentials—or run deletion tests—without explicit authorization.
+- If the local environment cannot build the legacy targets or lacks the .NET SDK, report that limitation instead of claiming verification.
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..abcd3e3
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,21 @@
+# Changelog
+
+All notable changes to this project will be documented in this file.
+
+The format is based on *Keep a Changelog* and this project aims to follow *Semantic Versioning*.
+
+
+## [1.0.7] - (Unreleased)
+
+### Added
+
+- `ROADMAP.md`
+- `CHANGELOG.md`
+- `AGENTS.md`
+- `EXAMPLES.md`
+
+### Deprecated
+
+- All `RulesClient` endpoints (`CreateRuleAsync`, `DeleteRuleAsync`, `EnableRuleAsync`, `DisableRuleAsync`, `GetAllRulesAsync`, `GetRuleAsync`).
+- `DomainsClient` create/delete endpoints (`CreateDomainAsync`, `DeleteDomainAsync`).
+- Messages “Latest” wildcard endpoints (`FetchLatestMessagesAsync`, `FetchLatestInboxMessagesAsync`).
diff --git a/EXAMPLES.md b/EXAMPLES.md
new file mode 100644
index 0000000..16341e9
--- /dev/null
+++ b/EXAMPLES.md
@@ -0,0 +1,220 @@
+# Examples
+
+This file is a living collection of copy/pasteable examples for the Mailinator C# client.
+
+## Setup
+
+Install via NuGet:
+
+```
+PM> Install-Package MailinatorApiClient
+```
+
+Create a client using an API token:
+
+```csharp
+using mailinator_csharp_client;
+
+var client = new MailinatorClient("yourApiTokenHere");
+```
+
+## Quickstart
+
+Fetch message summaries for an inbox:
+
+```csharp
+using mailinator_csharp_client;
+using mailinator_csharp_client.Models.Messages.Requests;
+using mailinator_csharp_client.Models.Messages.Entities;
+
+var client = new MailinatorClient("yourApiTokenHere");
+
+var request = new FetchInboxRequest
+{
+ Domain = "your_private_domain.com",
+ Inbox = "your_inbox",
+ Skip = 0,
+ Limit = 20,
+ Sort = Sort.desc
+};
+
+var response = await client.MessagesClient.FetchInboxAsync(request);
+```
+
+## Authenticators
+
+Instant TOTP code + list authenticators:
+
+```csharp
+using mailinator_csharp_client;
+using mailinator_csharp_client.Models.Authenticators.Requests;
+
+var client = new MailinatorClient("yourApiTokenHere");
+
+var totp = await client.AuthenticatorsClient.InstantTOTP2FACodeAsync(
+ new InstantTOTP2FACodeRequest { TotpSecretKey = "yourAuthSecret" });
+
+var authenticators = await client.AuthenticatorsClient.GetAuthenticatorsAsync();
+
+var byId = await client.AuthenticatorsClient.GetAuthenticatorsByIdAsync(
+ new GetAuthenticatorsByIdRequest { Id = "yourAuthId" });
+```
+
+## Domains
+
+List domains + fetch a domain:
+
+```csharp
+using mailinator_csharp_client;
+using mailinator_csharp_client.Models.Domains.Requests;
+
+var client = new MailinatorClient("yourApiTokenHere");
+
+var all = await client.DomainsClient.GetAllDomainsAsync();
+
+var domain = await client.DomainsClient.GetDomainAsync(
+ new GetDomainRequest { DomainId = "yourDomainIdHere" });
+```
+
+## Messages
+
+Post (inject) a message:
+
+```csharp
+using mailinator_csharp_client;
+using mailinator_csharp_client.Models.Messages.Entities;
+using mailinator_csharp_client.Models.Messages.Requests;
+
+var client = new MailinatorClient("yourApiTokenHere");
+
+var message = new MessageToPost
+{
+ Subject = "Testing message",
+ From = "test_email@test.com",
+ Text = "Hello World!"
+};
+
+var response = await client.MessagesClient.PostMessageAsync(
+ new PostMessageRequest { Domain = "yourDomainNameHere", Inbox = "yourInboxHere", Message = message });
+```
+
+Fetch inbox summaries + fetch message by id:
+
+```csharp
+using mailinator_csharp_client;
+using mailinator_csharp_client.Models.Messages.Requests;
+using mailinator_csharp_client.Models.Messages.Entities;
+
+var client = new MailinatorClient("yourApiTokenHere");
+
+var inbox = await client.MessagesClient.FetchInboxAsync(
+ new FetchInboxRequest { Domain = "yourDomainNameHere", Inbox = "yourInboxHere", Skip = 0, Limit = 20, Sort = Sort.desc });
+
+var message = await client.MessagesClient.FetchMessageAsync(
+ new FetchMessageRequest { Domain = "yourDomainNameHere", MessageId = "yourMessageIdHere" });
+```
+
+Fetch attachments + download a single attachment:
+
+```csharp
+using mailinator_csharp_client;
+using mailinator_csharp_client.Models.Messages.Requests;
+
+var client = new MailinatorClient("yourApiTokenHere");
+
+var attachments = await client.MessagesClient.FetchMessageAttachmentsAsync(
+ new FetchMessageAttachmentsRequest { Domain = "yourDomainNameHere", MessageId = "yourMessageIdHere" });
+
+var attachment = await client.MessagesClient.FetchMessageAttachmentAsync(
+ new FetchMessageAttachmentRequest { Domain = "yourDomainNameHere", MessageId = "yourMessageIdHere", AttachmentId = "yourAttachmentIdHere" });
+```
+
+Links, SMTP log, and raw content:
+
+```csharp
+using mailinator_csharp_client;
+using mailinator_csharp_client.Models.Messages.Requests;
+
+var client = new MailinatorClient("yourApiTokenHere");
+
+var links = await client.MessagesClient.FetchMessageLinksAsync(
+ new FetchMessageLinksRequest { Domain = "yourDomainNameHere", MessageId = "yourMessageIdHere" });
+
+var linksFull = await client.MessagesClient.FetchMessageLinksFullAsync(
+ new FetchMessageLinksFullRequest { Domain = "yourDomainNameHere", MessageId = "yourMessageIdHere" });
+
+var smtp = await client.MessagesClient.FetchMessageSmtpLogAsync(
+ new FetchMessageSmtpLogRequest { Domain = "yourDomainNameHere", MessageId = "yourMessageIdHere" });
+
+var raw = await client.MessagesClient.FetchMessageRawAsync(
+ new FetchMessageRawRequest { Domain = "yourDomainNameHere", MessageId = "yourMessageIdHere" });
+```
+
+Deletes:
+
+```csharp
+using mailinator_csharp_client;
+using mailinator_csharp_client.Models.Messages.Requests;
+
+var client = new MailinatorClient("yourApiTokenHere");
+
+var deleted = await client.MessagesClient.DeleteMessageAsync(
+ new DeleteMessageRequest { Domain = "yourDomainNameHere", Inbox = "yourInboxHere", MessageId = "yourMessageIdHere" });
+
+var deletedInbox = await client.MessagesClient.DeleteAllInboxMessagesAsync(
+ new DeleteAllInboxMessagesRequest { Domain = "yourDomainNameHere", Inbox = "yourInboxHere" });
+
+var deletedDomain = await client.MessagesClient.DeleteAllDomainMessagesAsync(
+ new DeleteAllDomainMessagesRequest { Domain = "yourDomainNameHere" });
+```
+
+## Stats
+
+Team summary:
+
+```csharp
+using mailinator_csharp_client;
+
+var client = new MailinatorClient("yourApiTokenHere");
+
+var team = await client.StatsClient.GetTeamAsync();
+var stats = await client.StatsClient.GetTeamStatsAsync();
+var info = await client.StatsClient.GetTeamInfoAsync();
+```
+
+## Webhooks
+
+Inject via webhook endpoints (uses `whtoken`):
+
+```csharp
+using mailinator_csharp_client;
+using mailinator_csharp_client.Models.Webhooks.Entities;
+using mailinator_csharp_client.Models.Webhooks.Requests;
+
+var client = new MailinatorClient("yourApiTokenHere");
+
+var webhook = new Webhook
+{
+ From = "MyMailinatorCSharpTest",
+ Subject = "testing message",
+ Text = "hello world",
+ To = "jack"
+};
+
+var privateWebhook = await client.WebhooksClient.PrivateWebhookAsync(
+ new PrivateWebhookRequest { WebhookToken = "yourWebhookTokenPrivateDomain", Webhook = webhook });
+
+var privateInboxWebhook = await client.WebhooksClient.PrivateInboxWebhookAsync(
+ new PrivateInboxWebhookRequest { WebhookToken = "yourWebhookTokenPrivateDomain", Inbox = "yourWebhookInbox", Webhook = webhook });
+
+var customServiceWebhook = await client.WebhooksClient.PrivateCustomServiceWebhookAsync(
+ new PrivateCustomServiceWebhookRequest { WebhookToken = "yourWebhookTokenCustomService", CustomService = "yourWebhookCustomService", Webhook = webhook });
+
+var customServiceInboxWebhook = await client.WebhooksClient.PrivateCustomServiceInboxWebhookAsync(
+ new PrivateCustomServiceInboxWebhookRequest { WebhookToken = "yourWebhookTokenCustomService", CustomService = "yourWebhookCustomService", Inbox = "yourWebhookInbox", Webhook = webhook });
+```
+
+## Troubleshooting
+
+- Ensure you’re using an API token from your Mailinator team settings.
+- For webhook injection, use webhook tokens (`whtoken`) instead of your API token.
diff --git a/README.md b/README.md
index bdfc4a0..a3754cd 100644
--- a/README.md
+++ b/README.md
@@ -14,318 +14,17 @@ To start using the API you need to first create an account at [mailinator.com](h
Once you have an account you will need an API Token which you can generate in [mailinator.com/v3/#/#team_settings_pane](https://www.mailinator.com/v3/#/#team_settings_pane).
-Then you can configure the library with:
+Usage examples live in [EXAMPLES.md](https://github.com/manybrain/mailinator-csharp-client/blob/master/EXAMPLES.md).
-```csharp
- using mailinator_csharp_client;
-
- MailinatorClient mailinatorClient = new MailinatorClient("yourApiTokenHere");
-```
-
-## Examples
-
-##### Authenticators methods:
-
-- InstantTOTP2FACode / Get Authenticators / Get Authenticators By Id:
-
- ```csharp
- using mailinator_csharp_client;
- using mailinator_csharp_client.Models.Domains.Requests;
- using mailinator_csharp_client.Models.Domains.Responses;
-
- MailinatorClient mailinatorClient = new MailinatorClient("yourApiTokenHere");
-
- //InstantTOTP2FACode
- InstantTOTP2FACodeRequest instantTOTP2FACodeRequest = new InstantTOTP2FACodeRequest() { TotpSecretKey = "yourAuthSecret" };
- var instantTOTP2FACodeResponse = await mailinatorClient.AuthenticatorsClient.InstantTOTP2FACodeAsync(instantTOTP2FACodeRequest);
-
- //Get Authenticators
- GetAuthenticatorsResponse getAuthenticatorsResponse = await mailinatorClient.AuthenticatorsClient.GetAuthenticatorsAsync();
-
- //Get Authenticators By Id
- GetAuthenticatorsByIdRequest getAuthenticatorsByIdRequest = new GetAuthenticatorsByIdRequest() { Id = "yourAuthId" };
- GetAuthenticatorsByIdResponse getAuthenticatorsByIdResponse = await mailinatorClient.AuthenticatorsClient.GetAuthenticatorsByIdAsync(getAuthenticatorsByIdRequest);
-
- // ...
- ```
-
-##### Domains methods:
-
-- Get AllDomains / Domain:
-
- ```csharp
- using mailinator_csharp_client;
- using mailinator_csharp_client.Models.Domains.Requests;
- using mailinator_csharp_client.Models.Domains.Responses;
-
- MailinatorClient mailinatorClient = new MailinatorClient("yourApiTokenHere");
-
- //Get All Domains
- GetAllDomainsResponse getAllDomainsResponse = await mailinatorClient.DomainsClient.GetAllDomainsAsync();
-
- //Get Domain
- GetDomainRequest getDomainRequest = new GetDomainRequest() { DomainId = "yourDomainIdHere" };
- GetDomainResponse getDomainResponse = await mailinatorClient.DomainsClient.GetDomainAsync(getDomainRequest);
- // ...
- ```
-
-- Create / Delete Domain:
-
- ```csharp
- using mailinator_csharp_client;
- using mailinator_csharp_client.Models.Domains.Requests;
- using mailinator_csharp_client.Models.Domains.Responses;
-
- MailinatorClient mailinatorClient = new MailinatorClient("yourApiTokenHere");
-
- //Create Domain
- CreateDomainRequest createDomainRequest = new CreateDomainRequest()
- {
- Name = DateTime.UtcNow.Ticks.ToString(),
- Description = "Description",
- Enabled = true,
- Rules = new System.Collections.Generic.List()
- };
- CreateDomainResponse createDomainResponse = await mailinatorClient.DomainsClient.CreateDomainAsync(createDomainRequest );
-
- //Delete Domain
- var deleteDomainRequest = new DeleteDomainRequest() { DomainId = "yourDomainIdHere" };
- DeleteDomainResponse deleteDomainResponse = await mailinatorClient.DomainsClient.DeleteDomainAsync(deleteDomainRequest);
- // ...
- ```
-
-##### Rules methods:
-
-- Create / Delete Rule:
-
- ```csharp
- using mailinator_csharp_client;
- using mailinator_csharp_client.Models.Rules.Entities;
- using mailinator_csharp_client.Models.Rules.Requests;
- using mailinator_csharp_client.Models.Rules.Responses;
- using System.Collections.Generic;
-
- MailinatorClient mailinatorClient = new MailinatorClient("yourApiTokenHere");
-
- //Create Rule
- RuleToCreate ruleToCreate = new RuleToCreate()
- {
- Name = "RuleName",
- Priority = 15,
- Description = "Description",
- Conditions = new List()
- {
- new Condition()
- {
- Operation = OperationType.PREFIX,
- ConditionData = new ConditionData()
- {
- Field = "to",
- Value = "raul"
- }
- }
- },
- Enabled = true,
- Match = MatchType.ANY,
- Actions = new List() { new ActionRule() { Action = ActionType.WEBHOOK, ActionData = new ActionData() { Url = "https://www.google.com" } } }
- };
- CreateRuleRequest createRuleRequest = new CreateRuleRequest() { DomainId = "yourDomainIdHere", Rule = ruleToCreate };
- CreateRuleResponse createRuleResponse = await mailinatorClient.RulesClient.CreateRuleAsync(createRuleRequest);
-
- //Delete Rule
- DeleteRuleRequest deleteRuleRequest = new DeleteRuleRequest() { DomainId = "yourDomainIdHere", RuleId = "yourRuleIdHere" };
- DeleteRuleResponse deleteRuleResponse = await mailinatorClient.RulesClient.DeleteRuleAsync(deleteRuleRequest);
- // ...
- ```
-
-- Enable / Disable Rule:
-
- ```csharp
- using mailinator_csharp_client;
- using mailinator_csharp_client.Models.Rules.Requests;
- using mailinator_csharp_client.Models.Rules.Responses;
-
- MailinatorClient mailinatorClient = new MailinatorClient("yourApiTokenHere");
-
- //Enable Rule
- EnableRuleRequest enableRuleRequest = new EnableRuleRequest() { DomainId = "yourDomainIdHere", RuleId = "yourRuleIdHere" };
- EnableRuleResponse enableRuleResponse = await mailinatorClient.RulesClient.EnableRuleAsync(enableRuleRequest);
-
- //Disable Rule
- DisableRuleRequest disableRuleRequest = new DisableRuleRequest() { DomainId = "yourDomainIdHere", RuleId = "yourRuleIdHere" };
- DisableRuleResponse disableRuleResponse = await mailinatorClient.RulesClient.DisableRuleAsync(disableRuleRequest);
- ```
-
-- Get All Rules / Rule:
-
-```csharp
- using mailinator_csharp_client;
- using mailinator_csharp_client.Models.Rules.Requests;
- using mailinator_csharp_client.Models.Rules.Responses;
+##### Build with tests
- MailinatorClient mailinatorClient = new MailinatorClient("yourApiTokenHere");
+Run the fast, offline unit tests with:
- //Get All Rules
- var getAllRulesRequest = new GetAllRulesRequest() { DomainId = "yourDomainIdHere" };
- var getAllRulesResponse = await mailinatorClient.RulesClient.GetAllRulesAsync(getAllRulesRequest);
-
- //Get Rule
- var getRuleRequest = new GetRuleRequest() { DomainId = "yourDomainIdHere", RuleId = "yourRuleIdHere" };
- var getRuleResponse = await mailinatorClient.RulesClient.GetRuleAsync(getRuleRequest);
+```
+dotnet test mailinator-csharp-client-unit-tests/mailinator-csharp-client-unit-tests.csproj
```
-##### Messages methods:
-
-- Post Message:
-
- ```csharp
- using mailinator_csharp_client;
- using mailinator_csharp_client.Models.Messages.Entities;
- using mailinator_csharp_client.Models.Messages.Requests;
- using mailinator_csharp_client.Models.Messages.Responses;
-
- MailinatorClient mailinatorClient = new MailinatorClient("yourApiTokenHere");
-
- MessageToPost messageToPost = new MessageToPost()
- {
- Subject = "Testing message",
- From = "test_email@test.com",
- Text = "Hello World!"
- };
- PostMessageRequest postMessageRequest = new PostMessageRequest() { Domain = "yourDomainNameHere", Inbox = "yourInboxHere", Message = messageToPost };
- PostMessageResponse postMessageResponse = await mailinatorClient.MessagesClient.PostNewMessageAsync(postMessageRequest);
- // ...
- ```
-
-- Fetch Inbox / Message / SMS Messages / Attachments / Attachment / Smtp Log / Raw / Latest:
-
- ```csharp
- using mailinator_csharp_client;
- using mailinator_csharp_client.Models.Messages.Entities;
- using mailinator_csharp_client.Models.Messages.Requests;
- using mailinator_csharp_client.Models.Messages.Responses;
-
- MailinatorClient mailinatorClient = new MailinatorClient("yourApiTokenHere");
-
- //Fetch Inbox
- FetchInboxRequest fetchInboxRequest = new FetchInboxRequest() { Domain = "yourDomainNameHere", Inbox = "yourInboxHere", Skip = 0, Limit = 20, Sort = Sort.asc };
- FetchInboxResponse fetchInboxResponse = await mailinatorClient.MessagesClient.FetchInboxAsync(fetchInboxRequest);
-
- //Fetch Message
- FetchMessageRequest fetchMessageRequest = new FetchMessageRequest() { Domain = "yourDomainNameHere", Inbox = "yourInboxHere", MessageId = "yourMessageIdHere" };
- FetchMessageResponse fetchMessageResponse = await mailinatorClient.MessagesClient.FetchMessageAsync(fetchMessageRequest);
-
- //Fetch SMS Messages
- FetchSMSMessagesRequest fetchSMSMessagesRequest = new FetchSMSMessagesRequest() { Domain = "yourDomainNameHere", TeamSMSNumber = "yourTeamSMSNumberHere" };
- FetchSMSMessagesResponse fetchSMSMessagesResponse = await mailinatorClient.MessagesClient.FetchSMSMessagesAsync(fetchSMSMessagesRequest);
-
- //Fetch Attachments
- FetchAttachmentsRequest fetchAttachmentsRequest = new FetchAttachmentsRequest() { Domain = "yourDomainNameHere", Inbox = "yourInboxHere", MessageId = "yourMessageIdWithAttachmentHere" };
- FetchAttachmentsResponse fetchAttachmentsResponse = await mailinatorClient.MessagesClient.FetchAttachmentsAsync(fetchAttachmentsRequest);
-
- //Fetch Attachment
- FetchAttachmentRequest fetchAttachmentRequest = new FetchAttachmentRequest() { Domain = "yourDomainNameHere", Inbox = "yourInboxHere", MessageId = "yourMessageIdWithAttachmentHere", AttachmentId = "yourAttachmentIdHere" };
- FetchAttachmentResponse fetchAttachmentResponse = await mailinatorClient.MessagesClient.FetchAttachmentAsync(fetchAttachmentRequest);
-
- //Fetch Message Links
- FetchMessageLinksRequest fetchMessageLinksRequest = new FetchMessageLinksRequest() { Domain = "yourDomainNameHere", Inbox = "yourInboxHere", MessageId = "yourMessageIdWithAttachmentHere" };
- FetchMessageLinksResponse fetchMessageLinksResponse = await mailinatorClient.MessagesClient.FetchMessageLinksAsync(fetchMessageLinksRequest);
-
- //Fetch Message Links Full
- FetchMessageLinksFullRequest fetchMessageLinksFullRequest = new FetchMessageLinksFullRequest() { Domain = "yourDomainNameHere", MessageId = "yourMessageIdWithAttachmentHere" };
- FetchMessageLinksFullResponse fetchMessageLinksFullResponse = await mailinatorClient.MessagesClient.FetchMessageLinksFullAsync(fetchMessageLinksFullRequest);
-
- //Fetch Message Smtp Log
- FetchMessageSmtpLogRequest fetchMessageSmtpLogRequest = new FetchMessageSmtpLogRequest() { Domain = "yourDomainNameHere", MessageId = "yourMessageIdHere" };
- FetchMessageSmtpLogResponse fetchMessageSmtpLogResponse= await mailinatorClient.MessagesClient.FetchMessageSmtpLogAsync(fetchMessageSmtpLogRequest);
-
- //Fetch Message Raw
- FetchMessageRawRequest fetchMessageRawRequest = new FetchMessageRawRequest() { Domain = "yourDomainNameHere", MessageId = "yourMessageIdHere" };
- FetchMessageRawResponse fetchMessageRawResponse= await mailinatorClient.MessagesClient.FetchMessageRawAsync(fetchMessageRawRequest);
-
- //Fetch Latest Messages
- FetchLatestMessagesRequest fetchLatestMessagesRequest = new FetchLatestMessagesRequest() { Domain = "yourDomainNameHere" };
- FetchLatestMessagesResponse fetchLatestMessagesResponse = await mailinatorClient.MessagesClient.FetchLatestMessagesAsync(fetchLatestMessagesRequest);
- ```
-
-- Delete Message / AllInboxMessages / AllDomainMessages
-
- ```csharp
- using mailinator_csharp_client;
- using mailinator_csharp_client.Models.Messages.Requests;
- using mailinator_csharp_client.Models.Messages.Responses;
-
- MailinatorClient mailinatorClient = new MailinatorClient("yourApiTokenHere");
-
- //Delete Message
- DeleteMessageRequest deleteMessageRequest = new DeleteMessageRequest() { Domain = "yourDomainNameHere", Inbox = "yourInboxHere", MessageId = "yourMessageIdHere" };
- DeleteMessageResponse deleteMessageResponse = await mailinatorClient.MessagesClient.DeleteMessageAsync(deleteMessageRequest);
-
- //Delete All Inbox Messages
- DeleteAllInboxMessagesRequest deleteAllInboxMessagesRequest = new DeleteAllInboxMessagesRequest() { Domain = "yourDomainNameHere", Inbox = "yourInboxHere" };
- DeleteAllInboxMessagesResponse deleteAllInboxMessagesResponse = await mailinatorClient.MessagesClient.DeleteAllInboxMessagesAsync(deleteAllInboxMessagesRequest);
-
- //Delete All Domain Messages
- DeleteAllDomainMessagesRequest deleteAllDomainMessagesRequest = new DeleteAllDomainMessagesRequest() { Domain = "yourDomainNameHere" };
- DeleteAllDomainMessagesResponse deleteAllDomainMessagesResponse = await mailinatorClient.MessagesClient.DeleteAllDomainMessagesAsync(deleteAllDomainMessagesRequest);
- ```
-
-##### Stats methods:
-
-- Get Team / Team Stats / Team Info:
-
- ```csharp
- using mailinator_csharp_client;
- using mailinator_csharp_client.Models.Domains.Requests;
- using mailinator_csharp_client.Models.Domains.Responses;
-
- MailinatorClient mailinatorClient = new MailinatorClient("yourApiTokenHere");
-
- //Get Team
- GetTeamResponse getTeamResponse = await mailinatorClient.StatsClient.GetTeamAsync();
-
- //Get TeamStats
- GetTeamStatsResponse getTeamStatsResponse = await mailinatorClient.StatsClient.GetTeamStatsAsync();
-
- //Get TeamInfo
- GetTeamInfoResponse getTeamInfoResponse = await mailinatorClient.StatsClient.GetTeamInfoAsync();
-
- // ...
- ```
-
-##### Webhooks methods:
-
-- Private Webhook / Private Inbox Webhook / Private Custom Service Webhook / Private Custom Service Inbox Webhook:
-
- ```csharp
- using mailinator_csharp_client;
- using mailinator_csharp_client.Models.Domains.Requests;
- using mailinator_csharp_client.Models.Domains.Responses;
-
- MailinatorClient mailinatorClient = new MailinatorClient("yourApiTokenHere");
- Webhook webhookToAdd = new Webhook { From = "MyMailinatorCSharpTest", Subject = "testing message", Text = "hello world", To = "jack" };
-
- //Private Webhook
- PrivateWebhookRequest privateWebhookRequest = new PrivateWebhookRequest() { WebhookToken = "yourWebhookTokenPrivateDomain", Webhook = webhookToAdd };
- PrivateWebhookResponse privateWebhookResponse = await mailinatorClient.WebhooksClient.PrivateWebhookAsync(privateWebhookRequest);
-
- //Private Inbox Webhook
- PrivateInboxWebhookRequest privateInboxWebhookRequest = new PrivateInboxWebhookRequest() { WebhookToken = "yourWebhookTokenPrivateDomain", Inbox = "yourWebhookInbox", Webhook = webhookToAdd };
- PrivateWebhookResponse privateInboxWebhookResponse = await mailinatorClient.WebhooksClient.PrivateInboxWebhookAsync(privateInboxWebhookRequest);
-
- //Private Custom Service Webhook
- PrivateCustomServiceWebhookRequest privateCustomServiceWebhookRequest = new PrivateCustomServiceWebhookRequest() { WebhookToken = "yourWebhookTokenCustomService", CustomService = "yourWebhookCustomService", Webhook = webhookToAdd };
- PrivateCustomServiceWebhookResponse privateCustomServiceWebhookResponse = await mailinatorClient.WebhooksClient.PrivateCustomServiceWebhookAsync(privateCustomServiceWebhookRequest);
-
- //Private Custom Service Inbox Webhook
- PrivateCustomServiceInboxWebhookRequest privateCustomServiceInboxWebhookRequest = new PrivateCustomServiceInboxWebhookRequest() { WebhookToken = "yourWebhookTokenCustomService", CustomService = "yourWebhookCustomService", Inbox = "yourWebhookInbox", Webhook = webhookToAdd };
- PrivateCustomServiceWebhookResponse privateCustomServiceInboxWebhookResponse = await mailinatorClient.WebhooksClient.PrivateCustomServiceInboxWebhookAsync(privateCustomServiceInboxWebhookRequest);
- // ...
- ```
-
-##### Build with tests
-
-Some of the tests require env variables with valid values. Visit tests source code and review `TastBase.cs` class. The more env variables you set, the more tests are run.
+The tests are live integration tests. Configure them in a repository-root `.env` file (which is ignored by Git); process environment variables take precedence. If `MAILINATOR_TEST_API_TOKEN` is missing, the entire integration suite is skipped. Copy `.env.example` as a starting point.
* `MAILINATOR_TEST_API_TOKEN` - API tokens for authentication; basic requirement across many tests;see also https://manybrain.github.io/m8rdocs/#api-authentication
* `MAILINATOR_TEST_DOMAIN_PRIVATE` - private domain; visit https://www.mailinator.com/
@@ -339,4 +38,4 @@ Some of the tests require env variables with valid values. Visit tests source co
* `MAILINATOR_TEST_AUTH_SECRET` - authenticator secret
* `MAILINATOR_TEST_AUTH_ID` - authenticator id
* `MAILINATOR_TEST_WEBHOOK_INBOX` - inbox for webhook
-* `MAILINATOR_TEST_WEBHOOK_CUSTOMSERVICE` - custom service for webhook
\ No newline at end of file
+* `MAILINATOR_TEST_WEBHOOK_CUSTOMSERVICE` - custom service for webhook
diff --git a/ROADMAP.md b/ROADMAP.md
new file mode 100644
index 0000000..c1e1f6b
--- /dev/null
+++ b/ROADMAP.md
@@ -0,0 +1,128 @@
+# Roadmap
+
+This document is a living roadmap for the Mailinator C# client. It’s intentionally high-level and should evolve as we audit the SDK against the Mailinator OpenAPI spec and customer needs.
+
+## Goals
+
+- Stay aligned with the Mailinator OpenAPI specification.
+- Maintain backwards compatibility where practical (or document breaking changes clearly).
+- Provide clear, copy/pasteable examples for common workflows.
+- Make releases predictable and easy to consume.
+
+## Current Status:
+
+- Target frameworks: `net471`; `netstandard2.0`
+- API coverage vs spec: see “Gap Analysis”
+- Known gaps / bugs: missing spec endpoints, SDK-only endpoints, path parameter-name mismatches, and one missing query parameter listed below.
+
+## Dependency Maintenance
+
+Audit refreshed: 2026-08-10.
+
+Security status:
+
+- No current direct dependency or package listed in `mailinator-csharp-client-tests/packages.config` falls within a known advisory range in NuGet's vulnerability feed.
+- `RestSharp` `112.0.0` resolves `System.Text.Json` at `8.0.4` or newer for `net471` and `netstandard2.0`; the advisories currently listed for the 8.x line affect versions through `8.0.3`.
+- The repository has no lock files, and the .NET SDK is not available in the current audit environment, so a restored full transitive graph could not be verified with `dotnet list package --vulnerable --include-transitive`.
+
+Production and tooling dependencies:
+
+- `Newtonsoft.Json`: current `13.0.3`; latest stable `13.0.4`.
+- `RestSharp`: current `112.0.0`; latest stable `114.0.0`. Version `114.0.0` still supports `net471` and `netstandard2.0`, but raises its `System.Text.Json` dependency from `8.0.4` to `10.0.0` and requires API compatibility testing.
+- `Microsoft.OpenApi.Readers`: current `1.6.29`; latest stable `1.6.29` (2.x remains preview-only).
+
+Legacy test-project dependencies:
+
+- `Microsoft.ApplicationInsights`: `2.22.0` → `3.1.2`.
+- `Microsoft.Testing.Platform` and related extensions: `1.3.2` → `2.3.3`.
+- `Microsoft.TestPlatform.ObjectModel`: `17.10.0` → `18.8.1`.
+- `MSTest.TestAdapter` and `MSTest.TestFramework`: `3.5.2` → `4.3.3`.
+- Explicitly pinned support packages are also behind: `System.Buffers` (`4.5.1` → `4.6.1`), `System.Collections.Immutable` (`1.5.0` → `10.0.10`), `System.Diagnostics.DiagnosticSource` (`5.0.0` → `10.0.10`), `System.Memory` (`4.5.4` → `4.6.3`), `System.Numerics.Vectors` (`4.5.0` → `4.6.1`), `System.Reflection.Metadata` (`1.6.0` → `10.0.10`), and `System.Runtime.CompilerServices.Unsafe` (`5.0.0` → `6.1.2`).
+
+Work items:
+
+- Update `Newtonsoft.Json` to `13.0.4` and run build/tests.
+- Evaluate `RestSharp` `114.0.0` in a dedicated change; verify source compatibility, serialization behavior, all target frameworks, and the full SDK test suite.
+- Convert the legacy `net472` test project from `packages.config` to SDK-style `PackageReference`, then upgrade the Microsoft testing packages as one coordinated stack and remove direct pins for transitive `System.*` dependencies where possible.
+- Keep `Microsoft.OpenApi.Readers` on `1.6.29` until a stable 2.x release or a specific tooling requirement justifies a preview.
+- Add lock files and a CI dependency check (`dotnet list package --vulnerable --include-transitive`) after restore tooling is available.
+
+## Gap Analysis (2026-03-23)
+
+This snapshot compares the SDK’s implemented operations to the Mailinator OpenAPI spec (`mailinator-api.yaml`).
+
+- Spec operations: 35
+- SDK operations: 43
+- Exact matches: 21
+- Missing from SDK: 10
+- SDK-only (no spec match): 17
+- SDK aliases / convenience wrappers: 1
+- Path parameter-name mismatches: 4
+- Operations with missing query params: 1
+
+Re-run locally:
+
+- Fetch the spec YAML and compare it to `mailinator-csharp-client/Clients/ApiClients/**` operations (method + effective path + query params).
+- Or run `dotnet run --project eng/OpenApiCoverageCheck -- --spec path/to/mailinator-api.yaml`.
+
+### Work Items (spec → SDK)
+
+Add these operations that exist in the spec but are missing from the SDK:
+
+- **Messages**
+ - `listDomainMessages` — `GET /api/v2/domains/{domain}/inboxes`
+ - `getMessageHeaders` — `GET /api/v2/domains/{domain}/messages/{messageId}/headers`
+ - `getMessageSummary` — `GET /api/v2/domains/{domain}/messages/{messageId}/summary`
+ - `getMessageText` — `GET /api/v2/domains/{domain}/messages/{messageId}/text`
+ - `getMessageTextHtml` — `GET /api/v2/domains/{domain}/messages/{messageId}/texthtml`
+ - `getMessageTextPlain` — `GET /api/v2/domains/{domain}/messages/{messageId}/textplain`
+ - `streamDomainMessages` — `GET /api/v2/domains/{domain}/stream`
+ - `streamInboxMessages` — `GET /api/v2/domains/{domain}/stream/{inbox}`
+- **Webhook**
+ - `postWebhookMessage` — `POST /api/v2/domains/{domain}/webhook`
+ - `postWebhookInboxMessage` — `POST /api/v2/domains/{domain}/webhook/{inbox}`
+
+### Work Items (SDK → spec)
+
+These SDK operations do not have a matching operation in the current OpenAPI spec. Decide for each group whether to (a) update the spec, (b) deprecate/remove the SDK surface, or (c) keep but document explicitly as “not in spec”.
+
+- **Rules** (6 operations under `/api/v2/domains/{domain_id}/rules...`)
+- **Domains** create/delete (`POST`/`DELETE /api/v2/domains/{domain_id}`)
+- **Authenticators** list/get variants (`/api/v2/authenticator...` and `/api/v2/authenticators`)
+- **Messages** “latest” wildcard endpoints (`GET .../messages/*`)
+- **Webhooks** private/custom-service endpoints (`POST /api/v2/domains/private/...`)
+
+### Work Items (spec alignment)
+
+Path template parameter names differ from the spec (non-breaking, but worth aligning for clarity and consistency):
+
+- Attachments: `{attachmentName}` (spec) vs `{attachmentId}` (SDK)
+- Authenticators: `{authenticator_id}` (spec) vs `{auth_id}` (SDK)
+- Domains: `{domain_name}` (spec) vs `{domain_id}` (SDK)
+
+Query parameters differ from the spec:
+
+- `GET /api/v2/domains/{domain}/inboxes/{inbox}/messages/{messageId}` is missing the optional `delete` query parameter in the SDK.
+
+## Near-Term (next 1–3 updates)
+
+- Keep gap analysis up to date (re-run after changes).
+- Decide on versioning and release cadence.
+- Implement missing spec endpoints (see “Work Items (spec → SDK)”).
+- Resolve spec alignment issues (path template parameter names).
+- Make an explicit decision on SDK-only endpoints (spec update vs deprecate vs document).
+- Improve docs: examples, configuration, troubleshooting.
+
+## Mid-Term
+
+- Improve test coverage and add integration test guidance.
+- Add more ergonomic APIs / helpers while keeping the low-level request mapping.
+
+## Long-Term
+
+- Automate spec drift detection and regeneration / validation workflows.
+- Improve observability and diagnostics (logging hooks, request/response tracing).
+
+## Out of Scope (for now)
+
+- Anything that depends on undocumented endpoints without confirmation.
diff --git a/eng/OpenApiCoverageCheck/OpenApiCoverageCheck.csproj b/eng/OpenApiCoverageCheck/OpenApiCoverageCheck.csproj
new file mode 100644
index 0000000..8a4f8a9
--- /dev/null
+++ b/eng/OpenApiCoverageCheck/OpenApiCoverageCheck.csproj
@@ -0,0 +1,14 @@
+
+
+
+ Exe
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/eng/OpenApiCoverageCheck/Program.cs b/eng/OpenApiCoverageCheck/Program.cs
new file mode 100644
index 0000000..32ee4f8
--- /dev/null
+++ b/eng/OpenApiCoverageCheck/Program.cs
@@ -0,0 +1,408 @@
+using System.Text.RegularExpressions;
+using Microsoft.OpenApi.Models;
+using Microsoft.OpenApi.Readers;
+
+const string DefaultSpecUrl = "https://raw.githubusercontent.com/manybrain/mailinatordocs/main/openapi/mailinator-api.yaml";
+
+var options = Options.Parse(args);
+if (options.ShowHelp)
+{
+ Options.PrintHelp();
+ return 0;
+}
+
+var spec = await LoadSpecAsync(options);
+var specOperations = GetOpenApiOperations(spec.Document).ToList();
+var sdkOperations = GetCSharpOperations(options.ClientRoot).ToList();
+
+var specByKey = specOperations.GroupBy(operation => operation.Key).ToDictionary(group => group.Key, group => group.ToList());
+var sdkByKey = sdkOperations.GroupBy(operation => operation.Key).ToDictionary(group => group.Key, group => group.ToList());
+var specByStructuralKey = specOperations.GroupBy(operation => operation.StructuralKey).ToDictionary(group => group.Key, group => group.ToList());
+var sdkByStructuralKey = sdkOperations.GroupBy(operation => operation.StructuralKey).ToDictionary(group => group.Key, group => group.ToList());
+
+var pathParameterMismatches = specOperations
+ .Where(specOperation => !sdkByKey.ContainsKey(specOperation.Key))
+ .Select(specOperation =>
+ {
+ sdkByStructuralKey.TryGetValue(specOperation.StructuralKey, out var candidates);
+ var sdkOperation = candidates?.FirstOrDefault(candidate => candidate.Method == specOperation.Method);
+ return sdkOperation is null || sdkOperation.PathParams.SetEquals(specOperation.PathParams)
+ ? null
+ : new OperationPair(specOperation, sdkOperation);
+ })
+ .Where(pair => pair is not null)
+ .Cast()
+ .ToList();
+
+var exactMatchKeys = specByKey.Keys.Intersect(sdkByKey.Keys).ToHashSet(StringComparer.Ordinal);
+var mismatchedSpecKeys = pathParameterMismatches.Select(pair => pair.SpecOperation.Key).ToHashSet(StringComparer.Ordinal);
+var mismatchedSdkKeys = pathParameterMismatches.Select(pair => pair.SdkOperation.Key).ToHashSet(StringComparer.Ordinal);
+
+var missingFromSdk = specOperations
+ .Where(operation => !sdkByKey.ContainsKey(operation.Key) && !mismatchedSpecKeys.Contains(operation.Key))
+ .ToList();
+
+var sdkAliasOperations = sdkOperations
+ .Where(operation => !specByKey.ContainsKey(operation.Key) && !mismatchedSdkKeys.Contains(operation.Key))
+ .Where(operation =>
+ specByStructuralKey.TryGetValue(operation.StructuralKey, out var specMatches) &&
+ specMatches.Any(specOperation => exactMatchKeys.Contains(specOperation.Key)))
+ .ToList();
+
+var sdkAliasKeys = sdkAliasOperations.Select(operation => operation.Key).ToHashSet(StringComparer.Ordinal);
+var sdkOnly = sdkOperations
+ .Where(operation => !specByKey.ContainsKey(operation.Key) && !mismatchedSdkKeys.Contains(operation.Key))
+ .Where(operation => !sdkAliasKeys.Contains(operation.Key))
+ .ToList();
+
+var missingQueryParams = exactMatchKeys
+ .Select(key =>
+ {
+ var specOperation = specByKey[key][0];
+ var sdkOperation = sdkByKey[key][0];
+ var missing = specOperation.QueryParams.Except(sdkOperation.QueryParams).OrderBy(param => param).ToList();
+ return missing.Count == 0 ? null : new MissingQueryParams(specOperation, sdkOperation, missing);
+ })
+ .Where(item => item is not null)
+ .Cast()
+ .OrderBy(item => item.SpecOperation.Key)
+ .ToList();
+
+var lines = new List
+{
+ options.Format == "markdown" ? "## OpenAPI coverage check" : "OpenAPI coverage check",
+ $"Spec source: {spec.Source}",
+ $"SDK root: {options.ClientRoot}",
+ string.Empty,
+ $"Spec operations: {specOperations.Count}",
+ $"SDK operations: {sdkOperations.Count}",
+ $"Exact matches: {exactMatchKeys.Count}",
+ $"Missing from SDK: {missingFromSdk.Count}",
+ $"SDK-only: {sdkOnly.Count}",
+ $"SDK aliases/convenience wrappers: {sdkAliasOperations.Count}",
+ $"Path parameter-name mismatches: {pathParameterMismatches.Count}",
+ $"Operations with missing query params: {missingQueryParams.Count}",
+ string.Empty
+};
+
+RenderList(lines, "Missing from SDK:", missingFromSdk);
+RenderList(lines, "SDK-only:", sdkOnly);
+RenderList(lines, "SDK aliases/convenience wrappers:", sdkAliasOperations);
+
+if (pathParameterMismatches.Count > 0)
+{
+ lines.Add("Path parameter-name mismatches:");
+ foreach (var pair in pathParameterMismatches.OrderBy(pair => pair.SpecOperation.Key))
+ {
+ lines.Add($" - {pair.SpecOperation.Key}");
+ lines.Add($" spec path: {pair.SpecOperation.Path}");
+ lines.Add($" sdk path: {pair.SdkOperation.Path}");
+ }
+
+ lines.Add(string.Empty);
+}
+
+if (missingQueryParams.Count > 0)
+{
+ lines.Add("Missing query params:");
+ foreach (var item in missingQueryParams)
+ {
+ lines.Add($" - {item.SpecOperation.Key}: {string.Join(", ", item.Missing)}");
+ }
+
+ lines.Add(string.Empty);
+}
+
+Console.WriteLine(string.Join(Environment.NewLine, lines).TrimEnd());
+
+var driftDetected =
+ missingFromSdk.Count > 0 ||
+ sdkOnly.Count > 0 ||
+ pathParameterMismatches.Count > 0 ||
+ missingQueryParams.Count > 0;
+
+return driftDetected && options.FailOnDrift ? 1 : 0;
+
+static async Task LoadSpecAsync(Options options)
+{
+ if (!string.IsNullOrWhiteSpace(options.SpecPath) && !string.IsNullOrWhiteSpace(options.SpecUrl))
+ {
+ throw new InvalidOperationException("Use either --spec or --spec-url, not both.");
+ }
+
+ var source = options.SpecPath ?? options.SpecUrl ?? DefaultSpecUrl;
+ using var httpClient = new HttpClient();
+ await using var stream = !string.IsNullOrWhiteSpace(options.SpecPath)
+ ? File.OpenRead(options.SpecPath)
+ : await httpClient.GetStreamAsync(source);
+
+ var document = new OpenApiStreamReader().Read(stream, out var diagnostic);
+ if (diagnostic.Errors.Count > 0)
+ {
+ var errors = string.Join(Environment.NewLine, diagnostic.Errors.Select(error => $" - {error.Message}"));
+ throw new InvalidOperationException($"Unable to parse OpenAPI document:{Environment.NewLine}{errors}");
+ }
+
+ return new LoadedSpec(document, source);
+}
+
+static IEnumerable GetOpenApiOperations(OpenApiDocument document)
+{
+ foreach (var path in document.Paths)
+ {
+ foreach (var operation in path.Value.Operations)
+ {
+ var pathParameters = path.Value.Parameters ?? Enumerable.Empty();
+ var operationParameters = operation.Value.Parameters ?? Enumerable.Empty();
+ var parameters = pathParameters
+ .Concat(operationParameters)
+ .Select(parameter => ResolveParameter(document, parameter));
+
+ yield return new ApiOperation(
+ Method: operation.Key.ToString().ToUpperInvariant(),
+ Path: NormalizePath(path.Key),
+ OperationId: operation.Value.OperationId,
+ QueryParams: parameters
+ .Where(parameter => parameter.In == ParameterLocation.Query)
+ .Select(parameter => parameter.Name)
+ .ToHashSet(StringComparer.Ordinal),
+ PathParams: parameters
+ .Where(parameter => parameter.In == ParameterLocation.Path)
+ .Select(parameter => parameter.Name)
+ .ToHashSet(StringComparer.Ordinal),
+ Source: "OpenAPI");
+ }
+ }
+}
+
+static OpenApiParameter ResolveParameter(OpenApiDocument document, OpenApiParameter parameter)
+{
+ if (parameter.Reference?.Id is { Length: > 0 } referenceId &&
+ document.Components?.Parameters.TryGetValue(referenceId, out var referencedParameter) == true)
+ {
+ return referencedParameter;
+ }
+
+ return parameter;
+}
+
+static IEnumerable GetCSharpOperations(string clientRoot)
+{
+ var endpointMap = GetClassEndpointMap(clientRoot);
+ var apiClientsRoot = Path.Combine(clientRoot, "Clients", "ApiClients");
+ if (!Directory.Exists(apiClientsRoot))
+ {
+ throw new DirectoryNotFoundException($"Unable to find API clients directory: {apiClientsRoot}");
+ }
+
+ foreach (var file in Directory.GetFiles(apiClientsRoot, "*Client.cs", SearchOption.AllDirectories))
+ {
+ var source = File.ReadAllText(file);
+ var className = Path.GetFileNameWithoutExtension(file);
+ var baseEndpoint = endpointMap.TryGetValue(className, out var endpoint) ? endpoint : string.Empty;
+
+ foreach (var method in GetMethodBlocks(source))
+ {
+ var requestMatch = Regex.Match(
+ method.Block,
+ @"httpClient\.GetRequest\s*\(\s*endpointUrl\s*\+\s*""([^""]*)""\s*,\s*Method\.(\w+)",
+ RegexOptions.Singleline);
+
+ if (!requestMatch.Success)
+ {
+ continue;
+ }
+
+ var path = CombineEndpointPath(baseEndpoint, requestMatch.Groups[1].Value);
+ var queryParams = Regex.Matches(method.Block, @"AddSafeQueryParameter\s*\(\s*""([^""]+)""")
+ .Select(match => match.Groups[1].Value)
+ .ToHashSet(StringComparer.Ordinal);
+ var pathParams = Regex.Matches(method.Block, @"AddUrlSegment\s*\(\s*""([^""]+)""")
+ .Select(match => match.Groups[1].Value)
+ .ToHashSet(StringComparer.Ordinal);
+
+ yield return new ApiOperation(
+ Method: requestMatch.Groups[2].Value.ToUpperInvariant(),
+ Path: path,
+ OperationId: method.Name,
+ QueryParams: queryParams,
+ PathParams: pathParams,
+ Source: Path.GetRelativePath(Directory.GetCurrentDirectory(), file));
+ }
+ }
+}
+
+static Dictionary GetClassEndpointMap(string clientRoot)
+{
+ var mailinatorClient = Path.Combine(clientRoot, "MailinatorClient.cs");
+ if (!File.Exists(mailinatorClient))
+ {
+ return new Dictionary(StringComparer.Ordinal);
+ }
+
+ var source = File.ReadAllText(mailinatorClient);
+ return Regex.Matches(source, @"(\w+Client)\s*=\s*new\s+\w+Client\s*\([^,]+,\s*""([^""]*)""\s*\)")
+ .ToDictionary(
+ match => match.Groups[1].Value,
+ match => match.Groups[2].Value,
+ StringComparer.Ordinal);
+}
+
+static IEnumerable GetMethodBlocks(string source)
+{
+ var matches = Regex.Matches(
+ source,
+ @"public\s+async\s+Task<[^>]+>\s+(\w+)\s*\([^)]*\)\s*\{",
+ RegexOptions.Singleline)
+ .Cast()
+ .ToList();
+
+ for (var index = 0; index < matches.Count; index++)
+ {
+ var match = matches[index];
+ var nextStart = index + 1 < matches.Count ? matches[index + 1].Index : source.Length;
+ yield return new MethodBlock(match.Groups[1].Value, source[match.Index..nextStart]);
+ }
+}
+
+static string CombineEndpointPath(string baseEndpoint, string relativePath)
+{
+ var path = string.IsNullOrWhiteSpace(baseEndpoint)
+ ? relativePath
+ : relativePath.StartsWith("/", StringComparison.Ordinal)
+ ? $"{baseEndpoint}{relativePath}"
+ : $"{baseEndpoint}/{relativePath}";
+
+ return NormalizePath($"/api/v2/{path}");
+}
+
+static string NormalizePath(string path)
+{
+ var normalized = Regex.Replace(path, "/+", "/");
+ if (!normalized.StartsWith("/", StringComparison.Ordinal))
+ {
+ normalized = $"/{normalized}";
+ }
+
+ return normalized.Length > 1 ? normalized.TrimEnd('/') : normalized;
+}
+
+static void RenderList(List lines, string title, List operations)
+{
+ if (operations.Count == 0)
+ {
+ return;
+ }
+
+ lines.Add(title);
+ foreach (var operation in operations.OrderBy(operation => operation.Key))
+ {
+ lines.Add($" - {OperationLabel(operation)}");
+ }
+
+ lines.Add(string.Empty);
+}
+
+static string OperationLabel(ApiOperation operation)
+{
+ return string.IsNullOrWhiteSpace(operation.OperationId)
+ ? operation.Key
+ : $"{operation.Key} ({operation.OperationId})";
+}
+
+internal sealed record ApiOperation(
+ string Method,
+ string Path,
+ string? OperationId,
+ HashSet QueryParams,
+ HashSet PathParams,
+ string Source)
+{
+ public string Key => $"{Method} {Path}";
+ public string StructuralKey => Regex.Replace(Key, @"\{[^}]+\}", "{}");
+}
+
+internal sealed record LoadedSpec(OpenApiDocument Document, string Source);
+
+internal sealed record MethodBlock(string Name, string Block);
+
+internal sealed record OperationPair(ApiOperation SpecOperation, ApiOperation SdkOperation);
+
+internal sealed record MissingQueryParams(ApiOperation SpecOperation, ApiOperation SdkOperation, List Missing);
+
+internal sealed class Options
+{
+ public string? SpecPath { get; private set; }
+ public string? SpecUrl { get; private set; }
+ public string ClientRoot { get; private set; } = Path.Combine(Directory.GetCurrentDirectory(), "mailinator-csharp-client");
+ public bool FailOnDrift { get; private set; }
+ public string Format { get; private set; } = "text";
+ public bool ShowHelp { get; private set; }
+
+ public static Options Parse(string[] args)
+ {
+ var options = new Options();
+
+ for (var index = 0; index < args.Length; index++)
+ {
+ var arg = args[index];
+ switch (arg)
+ {
+ case "--spec":
+ options.SpecPath = ReadValue(args, ref index, arg);
+ break;
+ case "--spec-url":
+ options.SpecUrl = ReadValue(args, ref index, arg);
+ break;
+ case "--client-root":
+ options.ClientRoot = ReadValue(args, ref index, arg);
+ break;
+ case "--fail-on-drift":
+ options.FailOnDrift = true;
+ break;
+ case "--format":
+ options.Format = ReadValue(args, ref index, arg);
+ if (options.Format is not ("text" or "markdown"))
+ {
+ throw new ArgumentException("--format must be text or markdown.");
+ }
+ break;
+ case "-h":
+ case "--help":
+ options.ShowHelp = true;
+ break;
+ default:
+ throw new ArgumentException($"Unknown argument: {arg}");
+ }
+ }
+
+ return options;
+ }
+
+ public static void PrintHelp()
+ {
+ Console.WriteLine(
+ """
+ Usage: dotnet run --project eng/OpenApiCoverageCheck -- [options]
+
+ Options:
+ --spec PATH OpenAPI YAML file to compare against.
+ --spec-url URL OpenAPI YAML URL to compare against.
+ --client-root PATH C# client project root. Defaults to ./mailinator-csharp-client.
+ --fail-on-drift Exit non-zero when spec and SDK differ.
+ --format FORMAT Output format: text or markdown.
+ -h, --help Show help.
+ """);
+ }
+
+ private static string ReadValue(string[] args, ref int index, string option)
+ {
+ if (index + 1 >= args.Length)
+ {
+ throw new ArgumentException($"{option} requires a value.");
+ }
+
+ index++;
+ return args[index];
+ }
+}
diff --git a/eng/README.md b/eng/README.md
new file mode 100644
index 0000000..0f001c7
--- /dev/null
+++ b/eng/README.md
@@ -0,0 +1,21 @@
+# Engineering Tools
+
+## OpenAPI Coverage Check
+
+Compare the C# client request surface against the Mailinator OpenAPI specification:
+
+```sh
+dotnet run --project eng/OpenApiCoverageCheck -- --spec path/to/mailinator-api.yaml
+```
+
+If `--spec` is omitted, the tool fetches the current Mailinator OpenAPI YAML from:
+
+```text
+https://raw.githubusercontent.com/manybrain/mailinatordocs/main/openapi/mailinator-api.yaml
+```
+
+Use `--fail-on-drift` in CI once the SDK is expected to be in sync with the spec:
+
+```sh
+dotnet run --project eng/OpenApiCoverageCheck -- --spec path/to/mailinator-api.yaml --fail-on-drift
+```
diff --git a/mailinator-csharp-client-tests/MessagesEndpointTests.cs b/mailinator-csharp-client-tests/MessagesEndpointTests.cs
index def2427..2ae60cd 100644
--- a/mailinator-csharp-client-tests/MessagesEndpointTests.cs
+++ b/mailinator-csharp-client-tests/MessagesEndpointTests.cs
@@ -133,6 +133,7 @@ public async Task FetchMessageWithDeleteQueueParamsAsync()
{
await mailinatorClient.MessagesClient.FetchMessageAsync(request);
});
+ Assert.AreEqual(System.Net.HttpStatusCode.NotFound, exception.HttpStatusCode);
}
[TestMethod, TestCategory("Messages.FetchMessageWhenMessageDoesNotExistAsync")]
@@ -143,6 +144,7 @@ public async Task FetchMessageWhenMessageDoesNotExistAsync()
{
await mailinatorClient.MessagesClient.FetchMessageAsync(request);
});
+ Assert.AreEqual(System.Net.HttpStatusCode.NotFound, exception.HttpStatusCode);
}
[TestMethod, TestCategory("Messages.FetchInboxMessageAsync")]
@@ -167,6 +169,7 @@ public async Task FetchInboxMessageWhenMessageDoesNotExistAsync()
{
await mailinatorClient.MessagesClient.FetchInboxMessageAsync(request);
});
+ Assert.AreEqual(System.Net.HttpStatusCode.NotFound, exception.HttpStatusCode);
}
[TestMethod, TestCategory("Messages.FetchSMSMessagesAsync")]
diff --git a/mailinator-csharp-client-tests/TestBase.cs b/mailinator-csharp-client-tests/TestBase.cs
index cf270c7..78ef528 100644
--- a/mailinator-csharp-client-tests/TestBase.cs
+++ b/mailinator-csharp-client-tests/TestBase.cs
@@ -11,6 +11,7 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
+using System.IO;
using System.Linq;
using System.Threading.Tasks;
@@ -19,7 +20,7 @@ namespace mailinator_csharp_client_tests
[TestClass]
public class TestBase
{
- protected readonly MailinatorClient mailinatorClient;
+ protected MailinatorClient mailinatorClient;
private Domain domain;
@@ -37,23 +38,39 @@ public class TestBase
private const string ENV_WEBHOOK_INBOX = "MAILINATOR_TEST_WEBHOOK_INBOX";
private const string ENV_WEBHOOK_CUSTOMSERVICE = "MAILINATOR_TEST_WEBHOOK_CUSTOMSERVICE";
+ private static readonly string ApiToken;
+
+ static TestBase()
+ {
+ LoadDotEnv();
+ ApiToken = GetEnvironmentVariable(ENV_API_TOKEN);
+ }
+
protected TestBase()
{
- PrivateDomain = ENV_DOMAIN_PRIVATE;
- DeleteDomain = ENV_DELETE_DOMAIN;
- PrivateInbox = ENV_INBOX;
+ PrivateDomain = GetEnvironmentVariable(ENV_DOMAIN_PRIVATE);
+ DeleteDomain = GetEnvironmentVariable(ENV_DELETE_DOMAIN);
+ PrivateInbox = GetEnvironmentVariable(ENV_INBOX);
InboxAll = "*";
- MessageIdWithAttachment = ENV_MESSAGE_WITH_ATTACHMENT_ID;
- TeamSMSNumber = ENV_PHONE_NUMBER;
- AttachmentId = ENV_ATTACHMENT_ID;
- WebhookTokenPrivateDomain = ENV_WEBHOOKTOKEN_PRIVATEDOMAIN;
- WebhookTokenCustomService = ENV_WEBHOOKTOKEN_CUSTOMSERVICE;
- AuthSecret = ENV_AUTH_SECRET;
- AuthId = ENV_AUTH_ID;
- WebhookInbox = ENV_WEBHOOK_INBOX;
- WebhookCustomService = ENV_WEBHOOK_CUSTOMSERVICE;
-
- mailinatorClient = new MailinatorClient(ENV_API_TOKEN);
+ MessageIdWithAttachment = GetEnvironmentVariable(ENV_MESSAGE_WITH_ATTACHMENT_ID);
+ TeamSMSNumber = GetEnvironmentVariable(ENV_PHONE_NUMBER);
+ AttachmentId = GetEnvironmentVariable(ENV_ATTACHMENT_ID);
+ WebhookTokenPrivateDomain = GetEnvironmentVariable(ENV_WEBHOOKTOKEN_PRIVATEDOMAIN);
+ WebhookTokenCustomService = GetEnvironmentVariable(ENV_WEBHOOKTOKEN_CUSTOMSERVICE);
+ AuthSecret = GetEnvironmentVariable(ENV_AUTH_SECRET);
+ AuthId = GetEnvironmentVariable(ENV_AUTH_ID);
+ WebhookInbox = GetEnvironmentVariable(ENV_WEBHOOK_INBOX);
+ WebhookCustomService = GetEnvironmentVariable(ENV_WEBHOOK_CUSTOMSERVICE);
+
+ }
+
+ [TestInitialize]
+ public void RequireApiToken()
+ {
+ if (string.IsNullOrWhiteSpace(ApiToken))
+ Assert.Inconclusive($"Skipping integration test: set {ENV_API_TOKEN} in the repository .env file or process environment.");
+
+ mailinatorClient = new MailinatorClient(ApiToken);
}
protected Domain Domain
@@ -140,5 +157,57 @@ public Task PostNewMessageAsync(string domain, string inbox
var request = new PostMessageRequest() { Domain = domain, Inbox = inbox, Message = message };
return mailinatorClient.MessagesClient.PostMessageAsync(request);
}
+
+ private static string GetEnvironmentVariable(string name)
+ {
+ return Environment.GetEnvironmentVariable(name);
+ }
+
+ private static void LoadDotEnv()
+ {
+ var dotEnvPath = FindDotEnv(Environment.CurrentDirectory) ?? FindDotEnv(AppDomain.CurrentDomain.BaseDirectory);
+ if (dotEnvPath == null)
+ return;
+
+ foreach (var line in File.ReadAllLines(dotEnvPath))
+ {
+ var trimmedLine = line.Trim();
+ if (trimmedLine.Length == 0 || trimmedLine.StartsWith("#"))
+ continue;
+
+ if (trimmedLine.StartsWith("export "))
+ trimmedLine = trimmedLine.Substring("export ".Length).TrimStart();
+
+ var separatorIndex = trimmedLine.IndexOf('=');
+ if (separatorIndex <= 0)
+ continue;
+
+ var name = trimmedLine.Substring(0, separatorIndex).Trim();
+ var value = trimmedLine.Substring(separatorIndex + 1).Trim();
+ if (value.Length >= 2 && ((value.StartsWith("\"") && value.EndsWith("\"")) || (value.StartsWith("'") && value.EndsWith("'"))))
+ value = value.Substring(1, value.Length - 2);
+
+ if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable(name)))
+ Environment.SetEnvironmentVariable(name, value);
+ }
+ }
+
+ private static string FindDotEnv(string startDirectory)
+ {
+ if (string.IsNullOrWhiteSpace(startDirectory))
+ return null;
+
+ var directory = new DirectoryInfo(startDirectory);
+ while (directory != null)
+ {
+ var path = Path.Combine(directory.FullName, ".env");
+ if (File.Exists(path))
+ return path;
+
+ directory = directory.Parent;
+ }
+
+ return null;
+ }
}
}
diff --git a/mailinator-csharp-client-unit-tests/ApiClientRequestTests.cs b/mailinator-csharp-client-unit-tests/ApiClientRequestTests.cs
new file mode 100644
index 0000000..9797527
--- /dev/null
+++ b/mailinator-csharp-client-unit-tests/ApiClientRequestTests.cs
@@ -0,0 +1,111 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using mailinator_csharp_client.Clients.ApiClients.Domains;
+using mailinator_csharp_client.Clients.ApiClients.Messages;
+using mailinator_csharp_client.Clients.HttpClient;
+using mailinator_csharp_client.Models.Domains.Requests;
+using mailinator_csharp_client.Models.Domains.Responses;
+using mailinator_csharp_client.Models.Messages.Entities;
+using mailinator_csharp_client.Models.Messages.Requests;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using RestSharp;
+
+namespace mailinator_csharp_client_unit_tests
+{
+ [TestClass]
+ public class ApiClientRequestTests
+ {
+ [TestMethod]
+ public async Task GetDomainAsync_BuildsGetRequestWithDomainUrlSegment()
+ {
+ var httpClient = new RecordingHttpClient();
+ var client = new DomainsClient(httpClient, "domains");
+
+ await client.GetDomainAsync(new GetDomainRequest { DomainId = "example.com" });
+
+ Assert.AreEqual(Method.Get, httpClient.Request.Method);
+ Assert.AreEqual("domains/{domain_id}", httpClient.Request.Resource);
+ Assert.AreEqual("example.com", ParameterValue(httpClient.Request, "domain_id"));
+ }
+
+ [TestMethod]
+ public async Task FetchInboxAsync_BuildsRouteAndOptionalQueryParameters()
+ {
+ var httpClient = new RecordingHttpClient();
+ var client = new MessagesClient(httpClient, "domains");
+ var request = new FetchInboxRequest
+ {
+ Domain = "example.com",
+ Inbox = "orders",
+ Skip = 10,
+ Limit = 20,
+ Sort = Sort.asc,
+ DecodeSubject = true,
+ Cursor = "next-page",
+ Full = true,
+ Delete = "30s",
+ Wait = "10s"
+ };
+
+ await client.FetchInboxAsync(request);
+
+ Assert.AreEqual(Method.Get, httpClient.Request.Method);
+ Assert.AreEqual("domains/{domain}/inboxes/{inbox}", httpClient.Request.Resource);
+ Assert.AreEqual("example.com", ParameterValue(httpClient.Request, "domain"));
+ Assert.AreEqual("orders", ParameterValue(httpClient.Request, "inbox"));
+ Assert.AreEqual("10", ParameterValue(httpClient.Request, "skip"));
+ Assert.AreEqual("20", ParameterValue(httpClient.Request, "limit"));
+ Assert.AreEqual("asc", ParameterValue(httpClient.Request, "sort"));
+ Assert.AreEqual("True", ParameterValue(httpClient.Request, "decode_subject"));
+ Assert.AreEqual("next-page", ParameterValue(httpClient.Request, "cursor"));
+ Assert.AreEqual("True", ParameterValue(httpClient.Request, "full"));
+ Assert.AreEqual("30s", ParameterValue(httpClient.Request, "delete"));
+ Assert.AreEqual("10s", ParameterValue(httpClient.Request, "wait"));
+ }
+
+ [TestMethod]
+ public async Task PostMessageAsync_BuildsPostRequestWithJsonBody()
+ {
+ var httpClient = new RecordingHttpClient();
+ var client = new MessagesClient(httpClient, "domains");
+ var message = new MessageToPost { From = "sender@example.com", Subject = "Hello", Text = "Body" };
+
+ await client.PostMessageAsync(new PostMessageRequest { Domain = "example.com", Inbox = "orders", Message = message });
+
+ Assert.AreEqual(Method.Post, httpClient.Request.Method);
+ Assert.AreEqual("domains/{domain}/inboxes/{inbox}/messages", httpClient.Request.Resource);
+ Assert.AreEqual("example.com", ParameterValue(httpClient.Request, "domain"));
+ Assert.AreEqual("orders", ParameterValue(httpClient.Request, "inbox"));
+ Assert.AreSame(message, httpClient.Request.Parameters.Single(parameter => parameter.Type == ParameterType.RequestBody).Value);
+ }
+
+ private static object ParameterValue(RestRequest request, string name)
+ {
+ return request.Parameters.Single(parameter => parameter.Name == name).Value;
+ }
+
+ private sealed class RecordingHttpClient : IHttpClient
+ {
+ public RestRequest Request { get; private set; }
+
+ public RestRequest GetRequest(string url, Method method)
+ {
+ return new RestRequest(url, method);
+ }
+
+ public Task ExecuteAsync(RestRequest request)
+ {
+ Request = request;
+ return Task.FromResult(default(T));
+ }
+
+ public Task ExecuteAsync(RestRequest request, Func customDeserializationFunction)
+ {
+ Request = request;
+ return Task.FromResult(default(T));
+ }
+ }
+ }
+}
diff --git a/mailinator-csharp-client-unit-tests/mailinator-csharp-client-unit-tests.csproj b/mailinator-csharp-client-unit-tests/mailinator-csharp-client-unit-tests.csproj
new file mode 100644
index 0000000..2b84370
--- /dev/null
+++ b/mailinator-csharp-client-unit-tests/mailinator-csharp-client-unit-tests.csproj
@@ -0,0 +1,15 @@
+
+
+ net8.0
+ false
+ true
+
+
+
+
+
+
+
+
+
+
diff --git a/mailinator-csharp-client.sln b/mailinator-csharp-client.sln
index ef5b66d..41dea74 100644
--- a/mailinator-csharp-client.sln
+++ b/mailinator-csharp-client.sln
@@ -7,6 +7,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "mailinator-csharp-client",
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "mailinator-csharp-client-tests", "mailinator-csharp-client-tests\mailinator-csharp-client-tests.csproj", "{D59A67E5-DE4B-49AF-BD14-143091B46527}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenApiCoverageCheck", "eng\OpenApiCoverageCheck\OpenApiCoverageCheck.csproj", "{C95520B0-8396-4D8C-9317-A8A958296A47}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "mailinator-csharp-client-unit-tests", "mailinator-csharp-client-unit-tests\mailinator-csharp-client-unit-tests.csproj", "{6F9324B4-AB58-4E62-90E0-9AF8B6C23B6E}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -21,6 +25,14 @@ Global
{D59A67E5-DE4B-49AF-BD14-143091B46527}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D59A67E5-DE4B-49AF-BD14-143091B46527}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D59A67E5-DE4B-49AF-BD14-143091B46527}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C95520B0-8396-4D8C-9317-A8A958296A47}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C95520B0-8396-4D8C-9317-A8A958296A47}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C95520B0-8396-4D8C-9317-A8A958296A47}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C95520B0-8396-4D8C-9317-A8A958296A47}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6F9324B4-AB58-4E62-90E0-9AF8B6C23B6E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6F9324B4-AB58-4E62-90E0-9AF8B6C23B6E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6F9324B4-AB58-4E62-90E0-9AF8B6C23B6E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6F9324B4-AB58-4E62-90E0-9AF8B6C23B6E}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/mailinator-csharp-client/Clients/ApiClients/Domains/DomainsClient.cs b/mailinator-csharp-client/Clients/ApiClients/Domains/DomainsClient.cs
index bef586a..1785fc9 100644
--- a/mailinator-csharp-client/Clients/ApiClients/Domains/DomainsClient.cs
+++ b/mailinator-csharp-client/Clients/ApiClients/Domains/DomainsClient.cs
@@ -2,6 +2,7 @@
using mailinator_csharp_client.Models.Domains.Requests;
using mailinator_csharp_client.Models.Domains.Responses;
using RestSharp;
+using System;
using System.Threading.Tasks;
namespace mailinator_csharp_client.Clients.ApiClients.Domains
@@ -53,6 +54,7 @@ public async Task GetDomainAsync(GetDomainRequest request)
///
/// CreateDomainRequest object.
///
+ [Obsolete("Deprecated: Domain create/delete endpoints are not present in the current Mailinator OpenAPI spec. This method may be removed in a future release.")]
public async Task CreateDomainAsync(CreateDomainRequest request)
{
var requestObject = httpClient.GetRequest(endpointUrl + "/{domain_id}", Method.Post);
@@ -67,6 +69,7 @@ public async Task CreateDomainAsync(CreateDomainRequest re
///
/// DeleteDomainRequest object.
///
+ [Obsolete("Deprecated: Domain create/delete endpoints are not present in the current Mailinator OpenAPI spec. This method may be removed in a future release.")]
public async Task DeleteDomainAsync(DeleteDomainRequest request)
{
var requestObject = httpClient.GetRequest(endpointUrl + "/{domain_id}", Method.Delete);
diff --git a/mailinator-csharp-client/Clients/ApiClients/Messages/MessagesClient.cs b/mailinator-csharp-client/Clients/ApiClients/Messages/MessagesClient.cs
index 4c23bb5..81c2453 100644
--- a/mailinator-csharp-client/Clients/ApiClients/Messages/MessagesClient.cs
+++ b/mailinator-csharp-client/Clients/ApiClients/Messages/MessagesClient.cs
@@ -5,6 +5,7 @@
using RestSharp;
using System.IO;
using System.Threading.Tasks;
+using System;
namespace mailinator_csharp_client.Clients.ApiClients.Messages
{
@@ -379,6 +380,7 @@ public async Task FetchInboxMessageRawAsync(FetchI
///
/// FetchLatestMessagesResponse object.
///
+ [Obsolete("Deprecated: Latest message wildcard endpoints are not present in the current Mailinator OpenAPI spec. This method may be removed in a future release.")]
public async Task FetchLatestMessagesAsync(FetchLatestMessagesRequest request)
{
var requestObject = httpClient.GetRequest(endpointUrl + "/{domain}/messages/*", Method.Get);
@@ -394,6 +396,7 @@ public async Task FetchLatestMessagesAsync(FetchLat
///
/// FetchLatestInboxMessagesResponse object.
///
+ [Obsolete("Deprecated: Latest message wildcard endpoints are not present in the current Mailinator OpenAPI spec. This method may be removed in a future release.")]
public async Task FetchLatestInboxMessagesAsync(FetchLatestInboxMessagesRequest request)
{
var requestObject = httpClient.GetRequest(endpointUrl + "/{domain}/inboxes/{inbox}/messages/*", Method.Get);
diff --git a/mailinator-csharp-client/Clients/ApiClients/Rules/RulesClient.cs b/mailinator-csharp-client/Clients/ApiClients/Rules/RulesClient.cs
index a875446..1b47226 100644
--- a/mailinator-csharp-client/Clients/ApiClients/Rules/RulesClient.cs
+++ b/mailinator-csharp-client/Clients/ApiClients/Rules/RulesClient.cs
@@ -3,6 +3,7 @@
using mailinator_csharp_client.Models.Rules.Requests;
using mailinator_csharp_client.Models.Rules.Responses;
using RestSharp;
+using System;
using System.Threading.Tasks;
namespace mailinator_csharp_client.Clients.ApiClients.Rules
@@ -30,6 +31,7 @@ public RulesClient(IHttpClient httpClient, string endpointUrl)
///
/// CreateRuleRequest object.
///
+ [Obsolete("Deprecated: Rules endpoints are not present in the current Mailinator OpenAPI spec. This method may be removed in a future release.")]
public async Task CreateRuleAsync(CreateRuleRequest request)
{
var requestObject = httpClient.GetRequest(endpointUrl + "/{domain_id}/rules", Method.Post);
@@ -46,6 +48,7 @@ public async Task CreateRuleAsync(CreateRuleRequest request)
///
/// EnableRuleRequest object.
///
+ [Obsolete("Deprecated: Rules endpoints are not present in the current Mailinator OpenAPI spec. This method may be removed in a future release.")]
public async Task EnableRuleAsync(EnableRuleRequest request)
{
var requestObject = httpClient.GetRequest(endpointUrl + "/{domain_id}/rules/{ruleId}/enable", Method.Put);
@@ -61,6 +64,7 @@ public async Task EnableRuleAsync(EnableRuleRequest request)
///
/// DisableRuleRequest object.
///
+ [Obsolete("Deprecated: Rules endpoints are not present in the current Mailinator OpenAPI spec. This method may be removed in a future release.")]
public async Task DisableRuleAsync(DisableRuleRequest request)
{
var requestObject = httpClient.GetRequest(endpointUrl + "/{domain_id}/rules/{ruleId}/disable", Method.Put);
@@ -76,6 +80,7 @@ public async Task DisableRuleAsync(DisableRuleRequest reque
///
/// GetAllRulesRequest object.
///
+ [Obsolete("Deprecated: Rules endpoints are not present in the current Mailinator OpenAPI spec. This method may be removed in a future release.")]
public async Task GetAllRulesAsync(GetAllRulesRequest request)
{
var requestObject = httpClient.GetRequest(endpointUrl + "/{domain_id}/rules", Method.Get);
@@ -90,6 +95,7 @@ public async Task GetAllRulesAsync(GetAllRulesRequest reque
///
/// GetRuleRequest object.
///
+ [Obsolete("Deprecated: Rules endpoints are not present in the current Mailinator OpenAPI spec. This method may be removed in a future release.")]
public async Task GetRuleAsync(GetRuleRequest request)
{
var requestObject = httpClient.GetRequest(endpointUrl + "/{domain_id}/rules/{ruleId}", Method.Get);
@@ -105,6 +111,7 @@ public async Task GetRuleAsync(GetRuleRequest request)
///
/// DeleteRuleRequest object.
///
+ [Obsolete("Deprecated: Rules endpoints are not present in the current Mailinator OpenAPI spec. This method may be removed in a future release.")]
public async Task DeleteRuleAsync(DeleteRuleRequest request)
{
var requestObject = httpClient.GetRequest(endpointUrl + "/{domain_id}/rules/{ruleId}", Method.Delete);
diff --git a/mailinator-csharp-client/mailinator-csharp-client.csproj b/mailinator-csharp-client/mailinator-csharp-client.csproj
index bc35154..4021225 100644
--- a/mailinator-csharp-client/mailinator-csharp-client.csproj
+++ b/mailinator-csharp-client/mailinator-csharp-client.csproj
@@ -8,16 +8,16 @@
https://github.com/manybrain/mailinator-csharp-client
Client Library used to interact with the Mailinator API
- © Manybrain 2025
+ © Manybrain 2026
Marian Melnychuk
Manybrain; ApiClient
git
MIT
README.md
MailinatorApiClient
- 1.0.6
- 1.0.6
- 1.0.6
+ 1.0.7
+ 1.0.7
+ 1.0.7
True