diff --git a/.gitignore b/.gitignore
index 591f80e..de0bceb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -49,4 +49,6 @@ source/node_modules
# Reverts
!build/
-!build/GetBuildVersion.psm1
\ No newline at end of file
+!build/GetBuildVersion.psm1
+
+.ai-context/
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 0000000..a23e823
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1,189 @@
+---
+title: WebSmsNet - Claude Code Instructions
+tags: [claude, onboarding, dotnet, api-client, sms, linkmobility, websms]
+---
+
+# WebSmsNet
+
+Unofficial .NET client library for the [LINK Mobility websms Messaging REST API 1.0.0](https://developer.linkmobility.eu/sms-api/rest-api). The library provides a typed C# client for sending text and binary SMS, parsing inbound webhook notifications (text, binary, delivery report), and easy registration through ASP.NET Core dependency injection. It ships as three NuGet packages: `WebSmsNet`, `WebSmsNet.Abstractions`, and `WebSmsNet.AspNetCore`.
+
+> **AI agents:** strict procedural rules live in [`ai_instructions.md`](./ai_instructions.md). That file is the contract for agent behavior and overrides this file where they disagree.
+
+## Tech Stack
+
+| Layer | Technology |
+| ---------------- | ---------------------------------------------------------------------------- |
+| Language | C# 13 (`13`) |
+| Framework | .NET 9 (`net9.0`) |
+| HTTP | `System.Net.Http.HttpClient` via `IHttpClientFactory` (ASP.NET Core package) |
+| Serialization | `System.Text.Json` (web defaults + camel-case enum converter) |
+| DI integration | ASP.NET Core (`Microsoft.Extensions.DependencyInjection`, `Microsoft.Extensions.Http`) |
+| Testing | xUnit 2.9 + FluentAssertions 6.12 + Coverlet (`WebSmsNet.Tests`) |
+| Build | MSBuild (SDK-style csproj) — `GeneratePackageOnBuild` + `GenerateDocumentationFile` |
+| CI/CD | GitHub Actions (`main.yml`, `codeql-analysis.yml`, `sonar-analysis.yml`) |
+| License | MIT |
+
+> Note: the testing stack (xUnit + FluentAssertions) differs from the AMANDA-Technology org default (NUnit + Shouldly). Match the **existing** project style when adding tests here — do not migrate the test framework as a side-effect of another change.
+
+## Solution Structure
+
+```
+WebSmsNet.sln
+ src/
+ WebSmsNet.Abstractions/ -- Models, enums, serializers, DI-agnostic helpers (NuGet package)
+ Configuration/ -- WebSmsApiOptions, AuthenticationType, HttpClient extension
+ Connectors/ -- Connector interfaces (IMessagingConnector)
+ Helpers/ -- BinaryContent helper (UDH-aware Base64 splitter/joiner)
+ Models/ -- Request/response DTOs and webhook models
+ Enums/ -- Public enums (WebSmsStatusCode, AddressType, MessageType, ...)
+ Serialization/ -- WebSmsJsonSerialization, WebSmsWebhookRequestConverter
+ IWebSmsApiClient.cs -- Aggregate client contract
+ WebSmsNet/ -- Core implementation (NuGet package)
+ Connectors/ -- Connector implementations (MessagingConnector)
+ WebSmsApiClient.cs -- Aggregate client, wires connectors to the connection handler
+ WebSmsApiConnectionHandler.cs -- HTTP POST dispatcher with virtual extension points
+ WebSmsNet.AspNetCore/ -- DI integration (NuGet package)
+ Configuration/ -- AddWebSmsApiClient(IServiceCollection, ...) extension
+ Helpers/ -- WebSmsWebhook parsing/matching helpers for webhook endpoints
+ tests/
+ WebSmsNet.Tests/ -- xUnit tests (DI, serialization, webhook parse, live send when env set)
+ build/
+ GetBuildVersion.psm1 -- PowerShell version helper used by the release workflow
+ .github/
+ workflows/ -- main.yml (build/publish), codeql-analysis.yml, sonar-analysis.yml
+ dependabot.yml
+```
+
+### Dependency Graph
+
+```
+WebSmsNet.Tests --> WebSmsNet.AspNetCore --> WebSmsNet --> WebSmsNet.Abstractions
+```
+
+## Build Commands
+
+```bash
+# Restore, build, test the whole solution
+dotnet restore WebSmsNet.sln
+dotnet build WebSmsNet.sln
+dotnet build WebSmsNet.sln -c Release # also produces .nupkg (GeneratePackageOnBuild)
+dotnet test WebSmsNet.sln
+
+# Run just the tests (env vars required for the live-send tests — see below)
+dotnet test tests/WebSmsNet.Tests/WebSmsNet.Tests.csproj
+
+# Pack explicitly
+dotnet pack WebSmsNet.sln -c Release
+```
+
+### Env vars for live integration tests
+
+The current `MessagingTests` fixture constructs a real `WebSmsApiClient` against `https://api.linkmobility.eu/` and reads credentials / recipient from the environment. Tests that hit the API will fail without these; pure serialization / parsing tests do not need them:
+
+| Variable | Purpose |
+| ------------------------------- | ------------------------------------------------- |
+| `Websms_AccessToken` | Bearer token used by `SendTextMessage` / `SendBinaryMessage` |
+| `Websms_RecipientAddressList` | A test recipient MSISDN (E.164, single entry) |
+
+## Key Conventions
+
+### Architecture Pattern
+- **Aggregate client + connectors.** `IWebSmsApiClient` is the single entry point and exposes one property per API area. Today only `Messaging` (`IMessagingConnector`) exists; future endpoint groups add new connector properties alongside it.
+- **Connection handler in the middle.** `WebSmsApiConnectionHandler` wraps a single `HttpClient`, owns JSON serialization, and is the only thing that talks HTTP. Connectors call `connectionHandler.Post(endpoint, data, cancellationToken)` — they do not touch `HttpClient` directly.
+- **Three construction paths, one handler contract.** `WebSmsApiClient` has constructors for `WebSmsApiOptions`, `HttpClient`, and `WebSmsApiConnectionHandler`. The DI path resolves the handler through `AddHttpClient` (typed client).
+- **Extensible via virtuals.** `WebSmsApiConnectionHandler` exposes `SerializerOptions`, `OnBeforePost`, `OnResponseReceived`, `EnsureSuccess`, and virtual `Post`. Consumers derive a custom handler to add auditing, retries, custom error mapping, etc. — see the README "Custom connection handler" section.
+- **Endpoint path constants live next to the connector.** `MessagingConnector.MessagingApiBasePath = "/rest/smsmessaging"` — do not inline endpoint strings scattered across the codebase.
+
+### Code Style
+- **Sealed records for DTOs and responses.** `MessageSendResponse` is a `sealed record` with `init` properties and `required`. New response DTOs should follow the same shape.
+- **Classes for request bodies.** `SmsSendRequest`, `TextSmsSendRequest`, `BinarySmsSendRequest` are classes today (with a mutable, settable base) — this preserves the existing public API. New mutable request shapes may stay as classes; new immutable ones should be records.
+- **Nullable reference types enabled** (`enable`) in every csproj.
+- **XML doc comments on every public / protected member.** All shipping projects set `true` — missing doc comments produce build warnings.
+- **`[JsonPropertyName]` on every serialized property.** Names match the websms JSON exactly (camelCase, including quirks like `validityPeriode`).
+- **`[SuppressMessage("ReSharper", "...")]`** is used liberally for R# noise on public API types (e.g., `UnusedMember.Global`, `ClassNeverInstantiated.Global`). Match the existing style.
+- **`[Optional] CancellationToken`** is used across public async methods — keep the convention.
+
+### Serialization
+- `System.Text.Json` with `JsonSerializerDefaults.Web`, `DefaultIgnoreCondition = WhenWritingNull`, `WriteIndented = false`.
+- Enums serialize as **camelCase strings** via `JsonStringEnumConverter(JsonNamingPolicy.CamelCase)` — except `WebSmsStatusCode`, which is serialized as its **integer value** via `[JsonConverter(typeof(JsonNumberEnumConverter))]` on each response property.
+- Inbound webhook payloads discriminate on the `messageType` string (`"text"`, `"binary"`, `"deliveryReport"`). The `WebSmsWebhookRequestConverter` reads the discriminator and re-deserializes into the matching subtype of `WebSmsWebhookRequest.Base`.
+- Both the converter and the enum converter are pre-registered in `WebSmsJsonSerialization.DefaultOptions`. Use these options everywhere (including webhook parsing) so inbound and outbound behavior stay consistent.
+
+### Authentication
+- Two modes, selected by `WebSmsApiOptions.AuthenticationType`:
+ - `Bearer` — requires `AccessToken`, sent as `Authorization: Bearer `.
+ - `Basic` — requires `Username` + `Password`, sent as `Authorization: Basic `.
+- The `HttpClient` is configured once by `HttpClientExtension.ApplyWebSmsApiOptions`. No per-request header mutation.
+- Missing credentials throw `InvalidOperationException` at client construction / first use. There is no silent fallback.
+
+### Webhook Handling
+- Consumers receive three shapes from the websms platform: `WebSmsWebhookRequest.Text`, `.Binary`, `.DeliveryReport`, all deriving from `WebSmsWebhookRequest.Base`.
+- Parse with `WebSmsWebhook.Parse(string json)` or `WebSmsWebhook.Parse(Stream requestStream, ...)`. Dispatch with `Match(onText, onBinary, onDeliveryReport)` — both an `Action` and a `Func` overload exist.
+- Respond with `WebSmsWebhook.CreateOkResponse()` / `CreateErrorResponse(string message)`. The helper uses `WebSmsStatusCode.Ok` / `InternalError` and a status message.
+- Binary payloads are Base64-encoded segments optionally prefixed by a UDH; `BinaryContent.Parse` / `BinaryContent.CreateMessageContentParts` split and reassemble concatenated SMS segments.
+
+### Testing Pattern (current state)
+- **Framework:** xUnit 2.9 + FluentAssertions 6.12 + Coverlet. `[Fact]` for individual tests.
+- **Locations:** a single test project (`tests/WebSmsNet.Tests`). Tests cover DI wiring, serialization/deserialization of `MessageSendResponse`, webhook parsing, `BinaryContent` round-trip, and live send (requires env vars).
+- **Live-hit tests:** construct the client in a field initializer that reads `Websms_AccessToken` / `Websms_RecipientAddressList` from the environment and throws if absent. Do not hardcode credentials.
+- **Assertions:** FluentAssertions (`result.Should().Be(...)`). Match existing style when adding tests — do not mix in NUnit or Shouldly.
+
+## Important File Locations
+
+| Purpose | Path |
+| ------------------------------ | -------------------------------------------------------------------------- |
+| AI rules | `ai_instructions.md` |
+| Main client interface | `src/WebSmsNet.Abstractions/IWebSmsApiClient.cs` |
+| Client implementation | `src/WebSmsNet/WebSmsApiClient.cs` |
+| HTTP connection handler | `src/WebSmsNet/WebSmsApiConnectionHandler.cs` |
+| Messaging connector (interface)| `src/WebSmsNet.Abstractions/Connectors/IMessagingConnector.cs` |
+| Messaging connector (impl) | `src/WebSmsNet/Connectors/MessagingConnector.cs` |
+| API options | `src/WebSmsNet.Abstractions/Configuration/WebSmsApiOptions.cs` |
+| HttpClient configuration | `src/WebSmsNet.Abstractions/Configuration/HttpClientExtension.cs` |
+| Authentication types | `src/WebSmsNet.Abstractions/Configuration/AuthenticationType.cs` |
+| DI registration | `src/WebSmsNet.AspNetCore/Configuration/WebSmsApiServiceCollectionExtension.cs` |
+| Webhook helpers | `src/WebSmsNet.AspNetCore/Helpers/WebSmsWebhook.cs` |
+| Binary content helper | `src/WebSmsNet.Abstractions/Helpers/BinaryContent.cs` |
+| JSON defaults | `src/WebSmsNet.Abstractions/Serialization/WebSmsJsonSerialization.cs` |
+| Webhook polymorphic converter | `src/WebSmsNet.Abstractions/Serialization/WebSmsWebhookRequestConverter.cs`|
+| Request DTOs | `src/WebSmsNet.Abstractions/Models/*SmsSendRequest.cs` |
+| Response DTOs | `src/WebSmsNet.Abstractions/Models/MessageSendResponse.cs` |
+| Webhook DTOs | `src/WebSmsNet.Abstractions/Models/WebSmsWebhookRequest.cs`, `WebSmsWebhookResponse.cs` |
+| Enums | `src/WebSmsNet.Abstractions/Models/Enums/` |
+| Tests | `tests/WebSmsNet.Tests/MessagingTests.cs` |
+| CI build & publish | `.github/workflows/main.yml` |
+| CodeQL | `.github/workflows/codeql-analysis.yml` |
+| SonarCloud | `.github/workflows/sonar-analysis.yml` |
+| Release version helper | `build/GetBuildVersion.psm1` |
+
+## Known Constraints and Gotchas
+
+1. **Live tests need real credentials.** `MessagingTests.SendTextMessage` / `SendBinaryMessage` construct a `WebSmsApiClient` in a field initializer and will throw if `Websms_AccessToken` / `Websms_RecipientAddressList` are missing — the *whole test class* cannot instantiate. When running tests locally or in CI without credentials, expect these to fail at test discovery/setup. Parse / serialize / DI tests do not need env vars but live alongside the failing-constructor fixture.
+2. **`WebSmsStatusCode` is an integer on the wire.** The enum's members use explicit websms codes (e.g., `Ok = 2000`). Serialize via `JsonNumberEnumConverter` — never as a camelCase string, or the API will reject the payload.
+3. **Webhook discriminator is `messageType`.** `WebSmsWebhookRequestConverter` dispatches on `"text"`, `"binary"`, `"deliveryReport"`; any other value throws `JsonException`. If websms ever adds a new webhook type, extend the switch there and add a sibling type under `WebSmsWebhookRequest`.
+4. **`WebSmsApiConnectionHandler` owns its `HttpClient` lifecycle only when constructed via `WebSmsApiOptions`.** In the DI path, `IHttpClientFactory` owns the socket. Do not dispose the handler-held client in derived classes.
+5. **POST-only API surface.** The handler currently only exposes `Post(...)`. If a future endpoint needs GET/PUT/DELETE, extend the handler with matching virtual methods — do not instantiate `HttpClient` from a connector.
+6. **`GeneratePackageOnBuild` is on for all three library projects.** Every `dotnet build` produces `.nupkg` files in `bin//`. `.gitignore` excludes `bin/` and `obj/`.
+7. **Target framework is `net9.0`, LangVersion 13.** Do not mix `net10.0` / C# 14 language features into this repo without an explicit framework bump — other AMANDA-Technology libraries (BexioApiNet, CashCtrlApiNet) target `net10.0`, this one currently does not.
+8. **`validityPeriode` (sic).** The property name on the wire is misspelled by the websms API. `SmsSendRequest.ValidityPeriod` maps to `[JsonPropertyName("validityPeriode")]` — keep the misspelling on the JSON side.
+9. **No pagination, search, bulk, or binary-file endpoints.** Unlike BexioApiNet / CashCtrlApiNet, the websms Messaging API is a single POST-per-call surface. Do not import the `FetchAll` / `SearchCriteria` / `PostBulkAsync` abstractions from those libraries.
+
+## Related AMANDA-Technology Repositories
+
+These are sibling .NET API-client libraries by the same org and are useful as style references, though their tech stacks may differ (`net10.0`, NUnit + Shouldly, different API shape):
+
+- [BexioApiNet](https://github.com/AMANDA-Technology/BexioApiNet) — Bexio REST API v3.0.0 (accounting/banking). Has both `CLAUDE.md` and `ai_instructions.md`; its connector + `ConnectionHandler` + DI layout directly inspired this one.
+- [CashCtrlApiNet](https://github.com/AMANDA-Technology/CashCtrlApiNet) — CashCtrl REST API v1 (Swiss cloud ERP). Connector-per-domain pattern, form-encoded bodies, similar three-package split.
+
+When taking inspiration from those repos, copy the *patterns* (connector/handler split, DI registration shape, XML doc discipline) rather than the *tech* (framework version, testing libraries) — this project is older and has its own stack as documented above.
+
+## AI Agent Workflow (Quick Reference)
+
+For full rules, see [`ai_instructions.md`](./ai_instructions.md). At a glance:
+
+- **Personas:** documentation/onboarding → *developer* (C# library). Infrastructure-only issues (workflows, Dockerfiles, scripts) → *devops*. Architecture blueprints / cross-repo specs → *architect*.
+- **Issue complexity:** `trivial` (one-file, one-line fix), `simple` (single feature / doc), `complex` (multi-file feature), `epic` (multi-phase initiative).
+- **Scope discipline.** Make the minimum change that fulfills the task. Do not refactor unrelated code or migrate the test framework, target framework, or language version as a side effect.
+- **Build before finishing.** Run `dotnet build` (0 errors, 0 warnings — XML doc warnings count) and `dotnet test` where feasible.
+- **Commit before finishing.** Every agent run ends with either a clean tree or a new commit. Never leave staged/unstaged diffs behind.
+- **Secrets.** Never write tokens, recipient MSISDNs, or customer data into source, tests, fixtures, or docs. Reference env vars by name only.
diff --git a/ai_instructions.md b/ai_instructions.md
new file mode 100644
index 0000000..02383c6
--- /dev/null
+++ b/ai_instructions.md
@@ -0,0 +1,186 @@
+---
+title: AI Agent Instructions for WebSmsNet
+tags: [ai, instructions, contributing, conventions]
+---
+
+# AI Agent Instructions — WebSmsNet
+
+This file contains **strict, procedural instructions** for AI coding agents working in this repository. Follow these rules to the letter — they keep the library production-grade and aligned with the LINK Mobility websms Messaging REST API.
+
+- This file: **strict rules for AI agents** (what to do, what never to do).
+- [`CLAUDE.md`](./CLAUDE.md): **high-level overview** (tech stack, structure, file locations, gotchas).
+- [`README.md`](./README.md): **end-user documentation** (installation, usage, custom handler).
+
+If anything here conflicts with `CLAUDE.md`, **this file wins for agent behavior**.
+
+## 1. Mission & Source of Truth
+
+1. This library is a typed .NET client for the **LINK Mobility websms Messaging REST API 1.0.0**. Source of truth: .
+2. Every endpoint, DTO field, enum value, status code and query / body parameter must match the websms docs exactly. If the docs and the code disagree, **the docs are right** — open a change.
+3. Never invent endpoints, fields or behavior that the websms docs do not describe. If the docs are ambiguous, stop and ask rather than guessing.
+4. The API's JSON is snake-ish camelCase with a handful of quirks (e.g., `validityPeriode` is misspelled on the wire). Preserve those on the JSON side via `[JsonPropertyName]`; the C# name should be the correctly-spelled `ValidityPeriod`.
+
+## 2. Tech Stack (non-negotiable)
+
+| Concern | Value |
+| -------------- | ------------------------------------------------------------------ |
+| Language | C# 13 (`13`) |
+| Framework | .NET 9 (`net9.0`) — all three library projects and the test project |
+| JSON | `System.Text.Json` — **never** add Newtonsoft.Json |
+| DI | `Microsoft.Extensions.DependencyInjection` + `Microsoft.Extensions.Http` (typed clients) |
+| Tests | xUnit 2.9 + FluentAssertions 6.12 + Coverlet |
+| Nullability | `enable` everywhere |
+| XML docs | `true` on all three shipping projects |
+| Packaging | `true` — build produces `.nupkg` for each library |
+
+If a change would require bumping any of these (e.g., moving to `net10.0` or swapping xUnit for NUnit), **stop and escalate**. This project intentionally lags its siblings (BexioApiNet, CashCtrlApiNet) and must not be migrated silently.
+
+## 3. Architecture Patterns (do not deviate)
+
+### 3.1 Aggregate client + connectors
+- `IWebSmsApiClient` (in `WebSmsNet.Abstractions`) is the aggregate root. It exposes one property per API area.
+- Today the only area is `Messaging` (`IMessagingConnector`). Adding a new API area means:
+ 1. Add a new connector interface under `src/WebSmsNet.Abstractions/Connectors/IConnector.cs`.
+ 2. Add the implementation under `src/WebSmsNet/Connectors/Connector.cs`, constructor-injecting `WebSmsApiConnectionHandler`.
+ 3. Add the property to `IWebSmsApiClient` and assign it in `WebSmsApiClient`.
+ 4. No new DI registration is needed — connectors are wired directly by `WebSmsApiClient`. `AddWebSmsApiClient` only registers the handler and the client.
+
+### 3.2 Single `WebSmsApiConnectionHandler` for HTTP
+- All HTTP access goes through `WebSmsApiConnectionHandler`. Connectors **never** new up `HttpClient` or call it directly.
+- The handler today exposes one public verb, `Task Post(string endpoint, object data, CancellationToken)`. Endpoints that need other verbs must be added as sibling virtual methods on the handler (e.g., `Get`, `Delete`, `GetBinary`) — not inline in connectors.
+- The handler is designed to be subclassed. The `SerializerOptions`, `OnBeforePost`, `OnResponseReceived`, `EnsureSuccess`, and `Post` members are all `virtual` / `protected virtual`. Do not tighten these to `private` / `sealed` — the README documents deriving from the handler as the public extension point.
+- Three construction paths exist, all ending in the same handler:
+ - `WebSmsApiClient(WebSmsApiOptions)` — owns a new `HttpClient` (non-DI).
+ - `WebSmsApiClient(HttpClient)` — caller supplies the client.
+ - `WebSmsApiClient(WebSmsApiConnectionHandler)` — caller supplies a fully-custom handler.
+ Keep all three working when touching the client.
+
+### 3.3 DI registration
+- `AddWebSmsApiClient(IServiceCollection, Action)`:
+ 1. `services.Configure(configureOptions)`.
+ 2. `services.AddHttpClient(...)` — typed client; reads `IOptions` inside the configure delegate and calls `ApplyWebSmsApiOptions`.
+ 3. `services.AddScoped(...)` — resolves `WebSmsApiConnectionHandler` from the provider and constructs `WebSmsApiClient`.
+- Do **not** replace this with `services.AddScoped(...)` — that defeats `IHttpClientFactory` and brings back the classic socket-exhaustion bug.
+- Do **not** change the scope from `Scoped` to `Singleton` without verifying `HttpClient` lifetime semantics — typed clients are scoped by default.
+
+### 3.4 Endpoint paths as constants
+- Paths live in a `private const string` on the connector (e.g., `MessagingConnector.MessagingApiBasePath = "/rest/smsmessaging"`). Endpoint *variants* (`"/text"`, `"/binary"`) are concatenated at call time.
+- Do not inline literal path strings outside the connector. If a group of related endpoints grows beyond a few, promote the paths to a static sibling class (mirroring the `*Configuration` / `Endpoints` patterns in BexioApiNet / CashCtrlApiNet).
+
+### 3.5 Request and response shapes
+- **Request bodies** inherit from `SmsSendRequest` (an abstract class with `required` properties + `get; set;`). `TextSmsSendRequest` and `BinarySmsSendRequest` are `sealed`. Keep the abstract base mutable — consumers compose requests property-by-property.
+- **Response bodies** are `sealed record` with `required ... { get; init; }`. Match this for any new response DTO.
+- Every serialized property has `[JsonPropertyName("...")]` matching the websms wire name exactly (camelCase, including quirks). Do not rely on `JsonSerializerDefaults.Web` name inference.
+- Nullable → nullable. If websms can return / accept `null`, the property type is nullable (`string?`, `int?`, `ContentCategory?`).
+
+### 3.6 Enums
+- Live in `src/WebSmsNet.Abstractions/Models/Enums/`. Each value has an XML `` with the human-readable meaning.
+- Default enum serialization is **camelCase string** via the global `JsonStringEnumConverter(JsonNamingPolicy.CamelCase)` in `WebSmsJsonSerialization.DefaultOptions`.
+- **Exception:** `WebSmsStatusCode` serializes as its **integer** websms code (e.g., `2000`). It is the only enum whose on-the-wire form is numeric. Apply this on each property that uses it:
+ ```csharp
+ [JsonPropertyName("statusCode")]
+ [JsonConverter(typeof(JsonNumberEnumConverter))]
+ public required WebSmsStatusCode StatusCode { get; init; }
+ ```
+ When adding a new response type that includes a status code, repeat this attribute pair. Do not "simplify" by removing either attribute.
+
+### 3.7 Webhook polymorphism
+- `WebSmsWebhookRequest` is a **static container class** with nested `Base`, `TextAndBinaryBase`, `Text`, `Binary`, `DeliveryReport`. The polymorphism is read through `WebSmsWebhookRequestConverter`, which dispatches on `messageType`.
+- Adding a new webhook kind means:
+ 1. Add a nested class under `WebSmsWebhookRequest` inheriting from `Base` (or `TextAndBinaryBase`).
+ 2. Add a case to `WebSmsWebhookRequestConverter.Read` for the new discriminator value.
+ 3. Add a value to the `WebhookMessageType` enum.
+ 4. Extend `WebSmsWebhook.Match(...)` with an additional `onXxx` parameter — **breaking API change**, call it out.
+- Inbound webhook parsing always uses `WebSmsJsonSerialization.DefaultOptions`. Do not parse webhook JSON with ad-hoc options.
+
+### 3.8 Extension points on the handler
+- `OnBeforePost(endpoint, data, cancellationToken)` — auditing / logging / pre-flight mutation.
+- `OnResponseReceived(response, cancellationToken)` — runs **before** `EnsureSuccess`; returns the response (possibly replaced) for chaining.
+- `EnsureSuccess(response)` — defaults to `response.EnsureSuccessStatusCode()`. Override to translate websms error bodies into richer exceptions.
+- `SerializerOptions` — defaults to `WebSmsJsonSerialization.DefaultOptions`. Override to add custom converters or change casing.
+- When extending the handler, **preserve this order**: `OnBeforePost` → HTTP call → `OnResponseReceived` → `EnsureSuccess` → `ReadFromJsonAsync`. Tests and consumers rely on it.
+
+## 4. Model & DTO Rules
+
+1. **Records for responses**, **classes for mutable requests** — match what exists today. Do not silently convert a class to a record (it changes equality and breaks consumers).
+2. Use `required` for properties that websms always sends back or always needs. Use `init` for immutable properties on records; `get; set;` on request classes.
+3. **Enable nullable reference types.** If websms can return `null`, the property type must be nullable.
+4. Map property names with `[JsonPropertyName("...")]` that match websms exactly, including any misspellings on the websms side.
+5. Place new DTOs under `src/WebSmsNet.Abstractions/Models//` when an area grows beyond the current flat layout. Enums go in `Models/Enums/`.
+6. Every `public` / `protected` type and member needs an XML ``. Missing docs produce build warnings, which we treat as errors.
+7. `[SuppressMessage("ReSharper", "...")]` is used liberally on public API surface types to silence ReSharper's "unused" noise. Match the existing pattern rather than disabling R# project-wide.
+
+## 5. Serialization Rules
+
+1. Go through `WebSmsJsonSerialization.DefaultOptions` for all serialization / deserialization — outbound bodies via the handler, inbound webhooks via `WebSmsWebhook.Parse`.
+2. Do not mutate `DefaultOptions` at call sites except for local diagnostic formatting (tests may set `WriteIndented = true` on a local copy).
+3. If a new converter is needed, add it to the `Converters` collection in `WebSmsJsonSerialization.DefaultOptions` and keep a sibling `JsonConverter` class in `src/WebSmsNet.Abstractions/Serialization/`.
+4. Never write a Newtonsoft.Json converter. Never pull `Newtonsoft.Json` into the solution.
+
+## 6. Testing Rules
+
+1. **Framework:** xUnit 2.9 + FluentAssertions 6.12. Use `[Fact]` for individual tests. Do not introduce `[Theory]` where a `[Fact]` suffices.
+2. **Do not migrate the testing stack.** NUnit, Shouldly, NSubstitute, Bogus, WireMock.Net are used by sibling libraries (BexioApiNet, CashCtrlApiNet) — they are **not** adopted here. A test-framework migration is its own PR, not a side-effect.
+3. **New connector methods require a test.** At minimum: a DI-wiring test verifying the method is reachable through `IWebSmsApiClient`, and a serialization round-trip for the request / response DTOs involved.
+4. **Live-send tests** use env vars (`Websms_AccessToken`, `Websms_RecipientAddressList`) and **must throw** at setup when the vars are absent, matching the existing `MessagingTests` pattern. Do not silently `Skip` tests or add a "local dev" fallback that hardcodes credentials.
+5. **No mocked handler frameworks.** For isolation, construct a fake `WebSmsApiConnectionHandler` subclass in the test project — the handler's virtual members make this straightforward. Do not pull in NSubstitute / Moq.
+6. Do **not** add test-only code paths to production types. If a test needs an override, derive from the handler in the test project.
+7. Never commit real tokens, recipient MSISDNs, or customer data in test fixtures. Read them from environment variables.
+
+## 7. Build & Verification Rules
+
+1. Before completing any task, run `dotnet build WebSmsNet.sln` and confirm **0 errors and 0 warnings**. XML-doc warnings count as errors for this project — all three shipping projects have `GenerateDocumentationFile` on.
+2. Run `dotnet test` where feasible. The live-send fixture will fail without env vars — that is expected locally. Verify the parse/serialize/DI tests still pass.
+3. Never introduce `.Result` or `.Wait()` on tasks. Use `async` / `await` end-to-end. The handler's `Post` is already fully async.
+4. Keep `ImplicitUsings` and `Nullable` enabled in every csproj. Do not disable them per-file.
+5. Do not commit artifacts (`bin/`, `obj/`, `*.nupkg`). They are in `.gitignore`.
+
+## 8. Secrets & Security
+
+1. Never write real websms tokens, basic-auth passwords, customer MSISDNs, or webhook bodies containing real phone numbers into source, tests, fixtures, or docs.
+2. `Websms_AccessToken` and `Websms_RecipientAddressList` are supplied via the environment only. Reference them by name — never by value.
+3. Never log or serialize `Authorization` headers, `AccessToken`, or `Password` values. If adding logging in a custom handler, redact these fields.
+4. Do not commit `.env`, `appsettings.Development.json`, or any file containing credentials.
+
+## 9. Workflow Expectations for AI Agents
+
+1. **Scope discipline.** Apply the minimum change that fulfills the task. Do not refactor unrelated code "on the way through".
+2. **No speculative features.** Implement only what the issue / blueprint requests. Hypothetical future needs are not a reason to add abstractions.
+3. **No silent framework / library migrations.** Target framework, C# language version, and testing stack are all explicit in this document. Changing any of them is its own ticket.
+4. **Incremental verification.** Build after each significant change, not only at the end.
+5. **Commit before finishing.** Every agent run ends with either a clean tree or a new commit — never staged/unstaged diffs.
+6. **Update docs alongside code.** If a convention changes, update this file, `CLAUDE.md`, and `README.md` (if user-facing) in the same change.
+7. If the websms docs describe something this library does not yet support, do not silently stub it. Either implement it end-to-end or open a tracked backlog item.
+
+## 10. Forbidden Patterns
+
+Do not:
+- Instantiate `HttpClient` directly in new code. Always route through the injected / constructed `WebSmsApiConnectionHandler`.
+- Throw bare exceptions from connector methods for ordinary API failures — rely on `EnsureSuccess` (and its override points) so consumers can plug in custom error translation.
+- Convert `sealed record` response types to `class` or vice-versa without explicit need — both changes are breaking.
+- Add Newtonsoft.Json, AutoMapper, MediatR, NUnit, Shouldly, NSubstitute, Moq, or any framework not already on the dependency list.
+- Skip XML doc comments on public members — they are compiled into the NuGet packages and missing docs break the build.
+- Hardcode endpoint paths in multiple places — use the `const string` pattern on the connector.
+- Silently change the `WebSmsStatusCode` serialization from integer to string, or drop `[JsonNumberEnumConverter]` from a response property.
+- Bump `` from `net9.0` to `net10.0` or `` beyond 13 as a side-effect. That is an explicit, standalone change.
+- Rename `ValidityPeriod` → `ValidityPeriode` to "fix" the C# property name. The correctly-spelled C# name with the wire-misspelled `[JsonPropertyName]` is intentional.
+
+## 11. Persona Routing
+
+When an issue arrives, choose the persona that matches the work:
+
+- **developer** — C# / .NET implementation, bug fixes, new connector methods, serialization work, test authoring. This is the default for anything under `src/` or `tests/`.
+- **devops** — `.github/workflows/*.yml`, `build/*.psm1`, Dockerfiles, dependabot config, release packaging changes. Not subject to TDD.
+- **architect** — cross-repo blueprints, large feature specs, breaking-API decisions. Produces design docs, not code.
+- **verification** — reviews a feature branch's diff + build + tests before merge. Does not author feature code.
+
+Onboarding / documentation tasks (this issue) are handled by *developer* with a docs-only diff.
+
+## 12. When in Doubt
+
+1. Re-read the relevant section of .
+2. Compare with the fully-implemented example in this repo: `MessagingConnector` + `TextSmsSendRequest` + `MessageSendResponse` + `MessagingTests` is the canonical reference.
+3. Cross-check against the sibling libraries for *patterns only* — do not copy their tech choices:
+ - [BexioApiNet](https://github.com/AMANDA-Technology/BexioApiNet) — connector + handler + DI patterns, `ai_instructions.md` template.
+ - [CashCtrlApiNet](https://github.com/AMANDA-Technology/CashCtrlApiNet) — three-package split, connector-per-domain shape.
+4. If still unclear, stop and surface the question in the task result rather than guessing.