From 1815057df5d4c1b758652b04e8cbfb3ebd989986 Mon Sep 17 00:00:00 2001 From: Chris Kenst Date: Mon, 23 Mar 2026 15:49:32 -0700 Subject: [PATCH 1/9] Deprecating obsolete endpoints --- AI_INSTRUCTIONS.md | 139 ++++++++ CHANGELOG.md | 20 ++ EXAMPLES.md | 312 ++++++++++++++++++ README.md | 311 +---------------- ROADMAP.md | 91 +++++ .../ApiClients/Domains/DomainsClient.cs | 3 + .../ApiClients/Messages/MessagesClient.cs | 3 + .../Clients/ApiClients/Rules/RulesClient.cs | 7 + 8 files changed, 577 insertions(+), 309 deletions(-) create mode 100644 AI_INSTRUCTIONS.md create mode 100644 CHANGELOG.md create mode 100644 EXAMPLES.md create mode 100644 ROADMAP.md diff --git a/AI_INSTRUCTIONS.md b/AI_INSTRUCTIONS.md new file mode 100644 index 0000000..73969cf --- /dev/null +++ b/AI_INSTRUCTIONS.md @@ -0,0 +1,139 @@ +# AI Instructions + +This document explains how the Mailinator C# client maps to the Mailinator OpenAPI specification, and how to audit/update the SDK when the spec changes. + +**OpenAPI specification (source of truth):** [mailinator-api.yaml](https://github.com/manybrain/mailinatordocs/blob/main/openapi/mailinator-api.yaml) + +## Codebase Structure + +This repository is organized around the same logical groupings as the Mailinator API: + +- **API clients:** `mailinator-csharp-client/Clients/ApiClients/*/*Client.cs` + - Each folder under `ApiClients/` corresponds to an OpenAPI **tag** (e.g. `Messages`, `Domains`, `Rules`, `Stats`, `Authenticators`, `Webhooks`). + - Public `*Async` methods on these clients are the SDK’s API surface. +- **HTTP plumbing:** `mailinator-csharp-client/Clients/HttpClient/HttpClient.cs` + - Wraps RestSharp, sets the base URL, and (when constructed with an API token) sets the `Authorization` header. + - Sets User-Agent based on the executing assembly version. +- **Models:** `mailinator-csharp-client/Models//...` + - `Entities/` contain reusable schema types. + - `Requests/` contain method input models (path/query/body values). + - `Responses/` contain response body models and usually compose `Entities/`. + +## Request / Execution Pattern + +Requests are constructed inside the `*Client` methods: + +1. Create a `RestRequest` via `IHttpClient.GetRequest(relativeUrl, Method.)`. +2. Set path params with `AddUrlSegment`. +3. Set query params with `AddSafeQueryParameter` (only adds when the value is non-null). +4. Set JSON bodies with `AddJsonBody`. +5. Execute with `ExecuteAsync` (JSON → model deserialization) or the overload that accepts a custom deserializer (for bytes/raw responses). + +Helper extensions live in `mailinator-csharp-client/Helpers/RequestExtensions.cs`. + +## Base URL and Path Composition + +The SDK base URI is defined in `mailinator-csharp-client/MailinatorClient.cs`: + +- `https://api.mailinator.com/api/v2` + +Each API client is initialized with an `endpointUrl` prefix (for example `"domains"`). Methods then append the rest of the path. Example: + +- `endpointUrl = "domains"` +- method relative URL: `"/{domain}/inboxes/{inbox}"` +- effective request path: `/api/v2/domains/{domain}/inboxes/{inbox}` + +**Rule:** always target `/api/v2/...` paths; do not introduce `/v2/...`. + +## Authentication vs Webhooks + +- Most endpoints require an API token: construct `new MailinatorClient(apiToken)` so `HttpClient` sets the `Authorization` header. +- Webhook injection endpoints intentionally do **not** use the API token. `new MailinatorClient()` (parameterless) initializes `WebhooksClient` using an unauthenticated `HttpClient`, and webhook auth is passed via the `whtoken` query parameter. + +--- + +## Gap Analysis Workflow (Spec vs SDK) + +Use this workflow whenever you want to audit the SDK against the OpenAPI spec, identify missing/extra coverage, and bring them into alignment. + +### Step 1 — Fetch the OpenAPI Specification + +Retrieve the raw YAML from: + +``` +https://raw.githubusercontent.com/manybrain/mailinatordocs/main/openapi/mailinator-api.yaml +``` + +Extract every `paths` entry. For each operation, record: + +- HTTP method (`get`, `post`, `put`, `delete`, ...) +- Path template (e.g. `/api/v2/domains/{domain}/inboxes/{inbox}`) +- `operationId` +- Tag (maps to an `ApiClients//` directory) +- Path/query parameters (and which are optional) +- Request body schema (if present) +- Response schema(s) + +### Step 2 — Catalog the SDK + +For each `*Client.cs` under `mailinator-csharp-client/Clients/ApiClients/`: + +1. Enumerate public `*Async` methods. +2. Capture the RestSharp HTTP verb used (`Method.Get`, `Method.Post`, ...). +3. Capture the relative URL string passed to `GetRequest(...)`. +4. Capture all `AddUrlSegment(...)` and `AddSafeQueryParameter(...)` usage. +5. Note the request/response model types used. + +Also capture the `endpointUrl` prefixes assigned in `mailinator-csharp-client/MailinatorClient.cs`, since those affect the effective path. + +### Step 3 — Identify Gaps + +Produce a gap report with four sections: + +#### A. In the spec but missing from the SDK +List every spec operation that has no corresponding SDK method (match by HTTP verb + effective path template). + +#### B. In the SDK but not in the spec +List every SDK method whose HTTP verb + effective path template does not exist in the spec. Flag for clarification (it may be legacy or undocumented). + +#### C. URL/path mismatches +Ensure the SDK’s effective path templates exactly match the spec’s paths (including `/api/v2/` and segment names). + +#### D. Query parameter gaps +For each SDK method, compare the query parameters it sends against the spec’s declared parameters. List missing parameters and any parameter name mismatches. + +### Step 4 — Build a Plan + +Before making changes, write a plan that includes: + +1. **New client methods to add** (grouped by OpenAPI tag / `ApiClients//`). +2. **Path fixes** (file + method + exact change). +3. **Query parameter additions** (file + method + parameter names). +4. **Model updates** (new/changed request/response/entities). +5. **Behavior changes** (pagination, defaults, delete flags, cursor handling, etc.). + +Present the plan for approval before implementing. + +### Step 5 — Implement + +Follow existing patterns in the target `*Client.cs`: + +- Use `httpClient.GetRequest(endpointUrl + "", Method.)`. +- Set path params with `AddUrlSegment`. +- Add optional query params with `AddSafeQueryParameter("param_name", value?.ToString())`. +- Add bodies with `AddJsonBody(...)`. +- Return a `Models/**/Responses/*Response` type. + +Place new/updated model types in the matching `Models//...` folder and ensure namespaces follow the folder structure. + +### Step 6 — Verify + +After implementing: + +1. Build the solution: `dotnet build` +2. Run tests (if configured): `dotnet test` +3. Manually sanity-check at least one updated method by confirming: + - the effective path template matches the spec, + - required path/query params are present, + - the response model matches the spec’s schema shape. + diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..24f3236 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,20 @@ +# 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*. + +## [Unreleased] + +### Added + +- `ROADMAP.md` +- `CHANGELOG.md` +- `AI_INSTRUCTIONS.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..9113dc1 --- /dev/null +++ b/EXAMPLES.md @@ -0,0 +1,312 @@ +# 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" }); +``` + +Create + delete a domain: + +```csharp +using System; +using mailinator_csharp_client; +using mailinator_csharp_client.Models.Domains.Requests; + +var client = new MailinatorClient("yourApiTokenHere"); + +var created = await client.DomainsClient.CreateDomainAsync( + new CreateDomainRequest { Name = $"test{DateTime.UtcNow.Ticks}.testinator.com" }); + +var deleted = await client.DomainsClient.DeleteDomainAsync( + new DeleteDomainRequest { DomainId = "yourDomainIdHere" }); +``` + +## Rules + +Create + delete a rule: + +```csharp +using System.Collections.Generic; +using mailinator_csharp_client; +using mailinator_csharp_client.Models.Rules.Entities; +using mailinator_csharp_client.Models.Rules.Requests; + +var client = new MailinatorClient("yourApiTokenHere"); + +var rule = new RuleToCreate +{ + Name = "rulename", + Priority = 15, + Description = "Description", + Match = MatchType.ANY, + Conditions = new List + { + new Condition + { + Operation = OperationType.PREFIX, + ConditionData = new ConditionData { Field = "to", Value = "raul" } + } + }, + Actions = new List + { + new ActionRule + { + Action = ActionType.WEBHOOK, + ActionData = new ActionData { Url = "https://www.google.com" } + } + } +}; + +var created = await client.RulesClient.CreateRuleAsync( + new CreateRuleRequest { DomainId = "yourDomainIdHere", Rule = rule }); + +var deleted = await client.RulesClient.DeleteRuleAsync( + new DeleteRuleRequest { DomainId = "yourDomainIdHere", RuleId = "yourRuleIdHere" }); +``` + +Enable + disable a rule: + +```csharp +using mailinator_csharp_client; +using mailinator_csharp_client.Models.Rules.Requests; + +var client = new MailinatorClient("yourApiTokenHere"); + +var enabled = await client.RulesClient.EnableRuleAsync( + new EnableRuleRequest { DomainId = "yourDomainIdHere", RuleId = "yourRuleIdHere" }); + +var disabled = await client.RulesClient.DisableRuleAsync( + new DisableRuleRequest { DomainId = "yourDomainIdHere", RuleId = "yourRuleIdHere" }); +``` + +List rules + fetch a rule: + +```csharp +using mailinator_csharp_client; +using mailinator_csharp_client.Models.Rules.Requests; + +var client = new MailinatorClient("yourApiTokenHere"); + +var all = await client.RulesClient.GetAllRulesAsync( + new GetAllRulesRequest { DomainId = "yourDomainIdHere" }); + +var rule = await client.RulesClient.GetRuleAsync( + new GetRuleRequest { DomainId = "yourDomainIdHere", RuleId = "yourRuleIdHere" }); +``` + +## 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, raw, latest: + +```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" }); + +var latest = await client.MessagesClient.FetchLatestMessagesAsync( + new FetchLatestMessagesRequest { Domain = "yourDomainNameHere" }); +``` + +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..97812df 100644 --- a/README.md +++ b/README.md @@ -14,314 +14,7 @@ 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: - -```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; - - MailinatorClient mailinatorClient = new MailinatorClient("yourApiTokenHere"); - - //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); -``` - -##### 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); - // ... - ``` +Usage examples live in `EXAMPLES.md`. ##### Build with tests @@ -339,4 +32,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..1b06fa8 --- /dev/null +++ b/ROADMAP.md @@ -0,0 +1,91 @@ +# 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 (to fill in) + +- Latest published package version: +- Target frameworks: +- API coverage vs spec: +- Known gaps / bugs: + +## 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 +- Path parameter-name mismatches: 4 +- Operations with missing query params: 0 + +Re-run locally: + +- Fetch the spec YAML and compare it to `mailinator-csharp-client/Clients/ApiClients/**` operations (method + effective path + query params). + +### 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) + +## 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/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); From 4823c8585d451c9e3f2595af0d1451937f7f9930 Mon Sep 17 00:00:00 2001 From: Chris Kenst Date: Wed, 25 Mar 2026 17:59:33 -0700 Subject: [PATCH 2/9] Set next version to 1.0.7 --- CHANGELOG.md | 2 ++ mailinator-csharp-client/mailinator-csharp-client.csproj | 8 ++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 24f3236..f054f76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ The format is based on *Keep a Changelog* and this project aims to follow *Seman ## [Unreleased] +## [1.0.7] - + ### Added - `ROADMAP.md` 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 From 864dbd65527193c833d10bbe748086f9a81e72f4 Mon Sep 17 00:00:00 2001 From: Chris Kenst Date: Sun, 12 Apr 2026 16:07:26 -0700 Subject: [PATCH 3/9] Update README.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 97812df..cf27ac6 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ 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). -Usage examples live in `EXAMPLES.md`. +Usage examples live in [EXAMPLES.md](https://github.com/manybrain/mailinator-csharp-client/blob/master/EXAMPLES.md). ##### Build with tests From d9264cb9a76a1969084c267f8413a4cee86783db Mon Sep 17 00:00:00 2001 From: Chris Kenst Date: Sun, 12 Apr 2026 16:07:48 -0700 Subject: [PATCH 4/9] Update EXAMPLES.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- EXAMPLES.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/EXAMPLES.md b/EXAMPLES.md index 9113dc1..d5a730b 100644 --- a/EXAMPLES.md +++ b/EXAMPLES.md @@ -128,9 +128,13 @@ var rule = new RuleToCreate } }; +// DEPRECATED: RulesClient.CreateRuleAsync is obsolete in this release. +// Prefer the current replacement API from the SDK documentation for new code. var created = await client.RulesClient.CreateRuleAsync( new CreateRuleRequest { DomainId = "yourDomainIdHere", Rule = rule }); +// DEPRECATED: RulesClient.DeleteRuleAsync is obsolete in this release. +// Prefer the current replacement API from the SDK documentation for new code. var deleted = await client.RulesClient.DeleteRuleAsync( new DeleteRuleRequest { DomainId = "yourDomainIdHere", RuleId = "yourRuleIdHere" }); ``` From a3da4c8f526d17426ed03b955777425713ef999d Mon Sep 17 00:00:00 2001 From: Chris Kenst Date: Sun, 12 Apr 2026 16:08:08 -0700 Subject: [PATCH 5/9] Update EXAMPLES.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- EXAMPLES.md | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/EXAMPLES.md b/EXAMPLES.md index d5a730b..397e3df 100644 --- a/EXAMPLES.md +++ b/EXAMPLES.md @@ -141,19 +141,9 @@ var deleted = await client.RulesClient.DeleteRuleAsync( Enable + disable a rule: -```csharp -using mailinator_csharp_client; -using mailinator_csharp_client.Models.Rules.Requests; - -var client = new MailinatorClient("yourApiTokenHere"); - -var enabled = await client.RulesClient.EnableRuleAsync( - new EnableRuleRequest { DomainId = "yourDomainIdHere", RuleId = "yourRuleIdHere" }); - -var disabled = await client.RulesClient.DisableRuleAsync( - new DisableRuleRequest { DomainId = "yourDomainIdHere", RuleId = "yourRuleIdHere" }); -``` - +> **Deprecated:** `EnableRuleAsync` and `DisableRuleAsync` are obsolete in this release. +> This example has been removed so new integrations do not adopt endpoints planned for removal. +> For new code, use the currently supported Rules API operations instead. List rules + fetch a rule: ```csharp From 2695f3286a1b9851f1133bfcc412dba96ca6591e Mon Sep 17 00:00:00 2001 From: Chris Kenst Date: Sun, 14 Jun 2026 16:26:11 -0700 Subject: [PATCH 6/9] Re-ran gap analysis using new tool --- ROADMAP.md | 8 +- .../OpenApiCoverageCheck.csproj | 14 + eng/OpenApiCoverageCheck/Program.cs | 408 ++++++++++++++++++ eng/README.md | 21 + mailinator-csharp-client.sln | 6 + 5 files changed, 456 insertions(+), 1 deletion(-) create mode 100644 eng/OpenApiCoverageCheck/OpenApiCoverageCheck.csproj create mode 100644 eng/OpenApiCoverageCheck/Program.cs create mode 100644 eng/README.md diff --git a/ROADMAP.md b/ROADMAP.md index 1b06fa8..bc93b04 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -25,12 +25,14 @@ This snapshot compares the SDK’s implemented operations to the Mailinator Open - 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: 0 +- 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) @@ -67,6 +69,10 @@ Path template parameter names differ from the spec (non-breaking, but worth alig - 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). 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.sln b/mailinator-csharp-client.sln index ef5b66d..4624224 100644 --- a/mailinator-csharp-client.sln +++ b/mailinator-csharp-client.sln @@ -7,6 +7,8 @@ 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 Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -21,6 +23,10 @@ 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 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 6343bc171aa6f3074d938240878f4782d989699b Mon Sep 17 00:00:00 2001 From: Chris Kenst Date: Sun, 14 Jun 2026 16:43:02 -0700 Subject: [PATCH 7/9] Updated changelog --- CHANGELOG.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f054f76..d451c2b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,9 +4,8 @@ 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*. -## [Unreleased] -## [1.0.7] - +## [1.0.7] - (Unreleased) ### Added From 7f0deb69c9f624d1571afc5447d156bc959d715d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 14 Jun 2026 23:43:45 +0000 Subject: [PATCH 8/9] Mark FetchLatestMessages example as deprecated --- EXAMPLES.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/EXAMPLES.md b/EXAMPLES.md index 397e3df..33450c5 100644 --- a/EXAMPLES.md +++ b/EXAMPLES.md @@ -232,6 +232,8 @@ var smtp = await client.MessagesClient.FetchMessageSmtpLogAsync( var raw = await client.MessagesClient.FetchMessageRawAsync( new FetchMessageRawRequest { Domain = "yourDomainNameHere", MessageId = "yourMessageIdHere" }); +// DEPRECATED: MessagesClient.FetchLatestMessagesAsync is obsolete in this release. +// Prefer non-"latest" message retrieval operations for new code. var latest = await client.MessagesClient.FetchLatestMessagesAsync( new FetchLatestMessagesRequest { Domain = "yourDomainNameHere" }); ``` From 8031976ce3e2bc5b008bd4fd29fcef556b949a14 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 14 Jun 2026 23:43:14 +0000 Subject: [PATCH 9/9] docs: mark deprecated domain create/delete example --- EXAMPLES.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/EXAMPLES.md b/EXAMPLES.md index 33450c5..3b2fc71 100644 --- a/EXAMPLES.md +++ b/EXAMPLES.md @@ -78,6 +78,9 @@ var domain = await client.DomainsClient.GetDomainAsync( Create + delete a domain: +> **Deprecated:** `CreateDomainAsync` and `DeleteDomainAsync` are obsolete in this release. +> For new code, use the currently supported Domains API operations instead. + ```csharp using System; using mailinator_csharp_client;