Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ MAILINATOR_TEST_API_TOKEN=
# Optional values used by endpoint-specific integration tests.
MAILINATOR_TEST_DOMAIN_PRIVATE=
MAILINATOR_TEST_INBOX=
# Existing email in MAILINATOR_TEST_DOMAIN_PRIVATE, used by header and summary retrieval tests.
MAILINATOR_TEST_MESSAGE_ID=
MAILINATOR_TEST_PHONE_NUMBER=
MAILINATOR_TEST_MESSAGE_WITH_ATTACHMENT_ID=
MAILINATOR_TEST_ATTACHMENT_ID=
Expand All @@ -13,4 +15,6 @@ MAILINATOR_TEST_WEBHOOKTOKEN_CUSTOMSERVICE=
MAILINATOR_TEST_AUTH_SECRET=
MAILINATOR_TEST_AUTH_ID=
MAILINATOR_TEST_WEBHOOK_INBOX=
# Set to 1 to run domain webhook tests, which create four messages.
MAILINATOR_TEST_RUN_WEBHOOKS=0
MAILINATOR_TEST_WEBHOOK_CUSTOMSERVICE=
35 changes: 35 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,41 @@ 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.8] - TBD

### Added

- Domain and inbox webhook injection via `PostWebhookMessageAsync` and `PostWebhookInboxMessageAsync`, supporting both webhook authentication forms, rich JSON payloads, offline unit tests, and opt-in integration tests.

- `MessagesClient.GetMessageTextAsync`, `GetMessageTextPlainAsync`, and `GetMessageTextHtmlAsync` with request/response models, offline route and JSON deserialization tests, and opt-in read-only integration tests.

- `MessagesClient.GetMessageSummaryAsync` with request/response models, offline request and deserialization tests, and an opt-in live test for an existing email.
- `MessagesClient.GetMessageHeadersAsync` with request/response models, offline request and deserialization tests, and an opt-in live test for an existing email.
- `MessagesClient.ListDomainMessagesAsync` and `ListDomainMessagesRequest` for domain-wide message listing, with optional inbox filtering and all documented listing parameters. Returns the existing `FetchInboxResponse` model.
- Offline tests for domain listing query parameters, default behavior, and wildcard filtering.

### Security

- Updated the transitive `System.Text.Json` dependency to `10.0.11` to remediate CVE-2024-43485.

### Changed

- Updated `Newtonsoft.Json` to `13.0.4`.
- Updated `RestSharp` to `114.0.0`.
- Updated the OpenAPI coverage tool's `Microsoft.OpenApi.Readers` dependency to `1.6.31`.
- Updated the offline unit-test stack to `Microsoft.NET.Test.Sdk` `18.9.0` and MSTest `4.4.0`.
- Migrated the live integration-test project to SDK-style `PackageReference` and the same current test stack.
- Updated integration-test exception assertions and binding redirects for MSTest 4 compatibility.
- Added NuGet package lock files.

### Deprecated

- `AuthenticatorsClient.GetAuthenticatorsAsync`, `GetAuthenticatorAsync`, and `GetAuthenticatorByIdAsync`; use `GetAuthenticatorsByIdAsync` for the documented stored-authenticator operation.

### Fixed

- Forwarded the optional `delete` query parameter in `FetchInboxMessageAsync`.


## [1.0.7] - 2026-08-15

Expand Down
5 changes: 5 additions & 0 deletions Directory.Build.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<Project>
<PropertyGroup>
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
</PropertyGroup>
</Project>
126 changes: 123 additions & 3 deletions EXAMPLES.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ var response = await client.MessagesClient.FetchInboxAsync(request);

## Authenticators

Instant TOTP code + list authenticators:
Instant TOTP code + get a stored authenticator:

```csharp
using mailinator_csharp_client;
Expand All @@ -54,8 +54,6 @@ 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" });
```
Expand All @@ -78,6 +76,73 @@ var domain = await client.DomainsClient.GetDomainAsync(

## Messages

### List domain messages

List messages across all inboxes in a domain:

```csharp
using mailinator_csharp_client.Models.Messages.Requests;

// Uses the authenticated client created in Setup.
var request = new ListDomainMessagesRequest
{
Domain = "your_private_domain.com",
Limit = 20
};

var response = await client.MessagesClient.ListDomainMessagesAsync(request);
var messages = response.Messages;

// Fetch the next page only when the API supplies a cursor.
if (!string.IsNullOrEmpty(response.Cursor))
{
request.Cursor = response.Cursor;
var nextPage = await client.MessagesClient.ListDomainMessagesAsync(request);
}
```

`Inbox` is an optional query filter: omit it or set it to `"*"` for all inboxes, or supply an inbox name/prefix such as `"orders*"`. The request also supports `Skip`, `Limit`, `Sort`, `DecodeSubject`, `Cursor`, `Full`, `Wait`, and `Delete`. Set `Full = true` to request full message content. `Delete` schedules deletion after retrieval (for example, `"30s"`); it is omitted by default.

The result is a `FetchInboxResponse`, sharing the inbox-listing response model and pagination cursor.

### Get message summary

```csharp
using mailinator_csharp_client.Models.Messages.Requests;

// Uses the authenticated client created in Setup.
var response = await client.MessagesClient.GetMessageSummaryAsync(
new GetMessageSummaryRequest
{
Domain = "your_private_domain.com",
MessageId = "your-message-id"
});

var summary = response.Summary;
```

`Summary` reuses the `Message` model and contains the subject, domain, sender (`From`), message ID, recipient (`To`), and timestamp (`Time`). This endpoint does not return body or attachment content.

### Get message headers

```csharp
using mailinator_csharp_client.Models.Messages.Requests;

// Uses the authenticated client created in Setup.
var response = await client.MessagesClient.GetMessageHeadersAsync(
new GetMessageHeadersRequest
{
Domain = "your_private_domain.com",
MessageId = "your-message-id"
});

var headers = response.Headers;
```

Use a message ID returned by inbox or domain listing. `Headers` is a `Dictionary<string, object>` that preserves custom header names. Values can be strings or JSON arrays (for example, `received`), matching the existing full-message header model.

### Post a message

Post (inject) a message:

```csharp
Expand Down Expand Up @@ -218,3 +283,58 @@ var customServiceInboxWebhook = await client.WebhooksClient.PrivateCustomService

- Ensure you’re using an API token from your Mailinator team settings.
- For webhook injection, use webhook tokens (`whtoken`) instead of your API token.

## Get message content

Use a message ID returned by inbox or domain listing:

```csharp
var extracted = await client.MessagesClient.GetMessageTextAsync(
new GetMessageTextRequest { Domain = "your-private-domain.com", MessageId = "your-message-id" });
var plain = await client.MessagesClient.GetMessageTextPlainAsync(
new GetMessageTextPlainRequest { Domain = "your-private-domain.com", MessageId = "your-message-id" });
var html = await client.MessagesClient.GetMessageTextHtmlAsync(
new GetMessageTextHtmlRequest { Domain = "your-private-domain.com", MessageId = "your-message-id" });

string extractedText = extracted.Text;
string plainText = plain.TextPlain;
string htmlBody = html.TextHtml;
```

These endpoints return JSON wrappers with `text`, `text/plain`, and `text/html` fields respectively. The SDK preserves their content, including HTML markup and any quoted-printable artifacts such as `=C2=A0` in extracted text. Empty strings are preserved. These operations do not delete the message.

## Domain and inbox webhooks

These endpoints authenticate with webhook tokens; no API token is needed. Request types are in `mailinator_csharp_client.Models.Webhooks.Requests` and `WebhookMessage` is in `mailinator_csharp_client.Models.Webhooks.Entities`.

```csharp
var client = new MailinatorClient();
var webhookToken = Environment.GetEnvironmentVariable("MAILINATOR_WEBHOOK_TOKEN");
var payload = new WebhookMessage
{
To = "orders",
From = "sender@example.com",
Subject = "Order notification",
Text = "Order received",
Html = "<p>Order received</p>"
};

var domainResult = await client.WebhooksClient.PostWebhookMessageAsync(
new PostWebhookMessageRequest
{
Domain = "your-private-domain.com",
WebhookToken = webhookToken,
Webhook = payload
});

var inboxResult = await client.WebhooksClient.PostWebhookInboxMessageAsync(
new PostWebhookInboxMessageRequest
{
Domain = "your-private-domain.com",
Inbox = "orders",
WebhookToken = webhookToken,
Webhook = payload
});
```

Both methods also accept the webhook token in `Domain`; omit `WebhookToken` for that form. The payload's `To` field is required by the specification. `Headers` accepts a dictionary of string values, and `AdditionalProperties` accepts custom JSON fields. Both responses expose `Status` and `Id`. Existing private/custom-service webhook methods remain available.
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,16 @@ var response = await client.MessagesClient.FetchInboxAsync(

All API operations are asynchronous and end in `Async`. Operations are grouped under `MessagesClient`, `DomainsClient`, `AuthenticatorsClient`, `StatsClient`, `WebhooksClient`, and `RulesClient`.

To list messages across a domain, use `MessagesClient.ListDomainMessagesAsync(new ListDomainMessagesRequest { Domain = "your-private-domain.com" })`. The optional `Inbox` query filter scopes results to an inbox; omit it to include all inboxes. See the [domain listing example](EXAMPLES.md#list-domain-messages) for pagination.

## API reference

Retrieve message metadata without the body with `MessagesClient.GetMessageSummaryAsync(new GetMessageSummaryRequest { Domain = "your-private-domain.com", MessageId = "your-message-id" })`. See the [message summary example](EXAMPLES.md#get-message-summary).

Retrieve message content with `MessagesClient.GetMessageTextAsync`, `GetMessageTextPlainAsync`, or `GetMessageTextHtmlAsync`. See the [message content examples](EXAMPLES.md#get-message-content).

Retrieve SMTP headers for an existing message with `MessagesClient.GetMessageHeadersAsync(new GetMessageHeadersRequest { Domain = "your-private-domain.com", MessageId = "your-message-id" })`. See the [header retrieval example](EXAMPLES.md#get-message-headers).

- [Mailinator API reference](https://www.mailinator.com/documentation/docs/api/index.html) describes the REST API.
- [REFERENCE.md](REFERENCE.md) lists the operations currently exposed by this SDK.
- [EXAMPLES.md](EXAMPLES.md) contains examples for common SDK workflows.
Expand Down Expand Up @@ -97,3 +105,5 @@ dotnet test mailinator-csharp-client-unit-tests/mailinator-csharp-client-unit-te
The separate legacy integration suite calls the live Mailinator API and requires deliberate account configuration. Some tests create or delete remote resources. Read [TESTING.md](TESTING.md) before running it.

To compare the SDK request surface with the OpenAPI specification, use the [OpenAPI coverage check](eng/README.md#openapi-coverage-check).

Domain and inbox webhook injection is available through `WebhooksClient.PostWebhookMessageAsync` and `PostWebhookInboxMessageAsync`, using a webhook token with a tokenless `new MailinatorClient()`. See the [webhook examples](EXAMPLES.md#domain-and-inbox-webhooks).
9 changes: 9 additions & 0 deletions REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ An authenticated client exposes `MessagesClient`, `DomainsClient`, `Authenticato
| Operation | Request | Response |
| --- | --- | --- |
| `FetchInboxAsync` | `FetchInboxRequest` | `FetchInboxResponse` |
| `ListDomainMessagesAsync` | `ListDomainMessagesRequest` | `FetchInboxResponse` |
| `GetMessageHeadersAsync` | `GetMessageHeadersRequest` | `GetMessageHeadersResponse` |
| `GetMessageSummaryAsync` | `GetMessageSummaryRequest` | `GetMessageSummaryResponse` |
| `GetMessageTextAsync` | `GetMessageTextRequest` | `GetMessageTextResponse` |
| `GetMessageTextPlainAsync` | `GetMessageTextPlainRequest` | `GetMessageTextPlainResponse` |
| `GetMessageTextHtmlAsync` | `GetMessageTextHtmlRequest` | `GetMessageTextHtmlResponse` |
| `FetchInboxMessageAsync` | `FetchInboxMessageRequest` | `FetchInboxMessageResponse` |
| `FetchMessageAsync` | `FetchMessageRequest` | `FetchMessageResponse` |
| `FetchSMSMessagesAsync` | `FetchSMSMessagesRequest` | `FetchSMSMessagesResponse` |
Expand Down Expand Up @@ -74,6 +80,8 @@ These operations use webhook tokens rather than the API token supplied to `Maili

| Operation | Request | Response |
| --- | --- | --- |
| `PostWebhookMessageAsync` | `PostWebhookMessageRequest` | `PostWebhookMessageResponse` |
| `PostWebhookInboxMessageAsync` | `PostWebhookInboxMessageRequest` | `PostWebhookMessageResponse` |
| `PrivateWebhookAsync` | `PrivateWebhookRequest` | `PrivateWebhookResponse` |
| `PrivateInboxWebhookAsync` | `PrivateInboxWebhookRequest` | `PrivateWebhookResponse` |
| `PrivateCustomServiceWebhookAsync` | `PrivateCustomServiceWebhookRequest` | `PrivateCustomServiceWebhookResponse` |
Expand All @@ -100,6 +108,7 @@ The following public methods are marked `[Obsolete]`. They remain in the current
| --- | --- | --- |
| `MessagesClient` | `FetchLatestMessagesAsync`, `FetchLatestInboxMessagesAsync` | The wildcard “latest” endpoints are not in the current OpenAPI specification. |
| `DomainsClient` | `CreateDomainAsync`, `DeleteDomainAsync` | Domain create/delete endpoints are not in the current OpenAPI specification. |
| `AuthenticatorsClient` | `GetAuthenticatorsAsync`, `GetAuthenticatorAsync`, `GetAuthenticatorByIdAsync` | These list/get endpoints are not in the current OpenAPI specification. |
| `RulesClient` | All operations | Rules endpoints are not in the current OpenAPI specification. |

See [ROADMAP.md](ROADMAP.md) for known specification gaps and planned alignment work.
Loading