> ## Documentation Index
> Fetch the complete documentation index at: https://repost.sh/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Reliable Sends in Kotlin

> Idempotency keys, the five delivery states, the exception taxonomy, coroutine cancellation, and bounded admission in the Repost Kotlin client.

Every send settles in a known state, even under timeouts, cancellation, or overload. This page covers the outcome model end to end: how idempotency makes retries safe, what each delivery state means, and how structured concurrency and backpressure behave.

## Idempotency and delivery outcomes

Repost dedupes on the idempotency key. Omit it and the runtime generates a stable key per operation and reuses it across retries; pass one (the `idempotencyKey` parameter on any send) to make a send safe to repeat.

Every failure is a `RepostException` carrying a stable `errorCode`, a best-known `deliveryState`, the `idempotencyKey`, and an `isRetryable` flag — never a raw server body or nested throwable. The five delivery states:

| `DeliveryState`     | Meaning                                                                                                              | What to do                                                                                        |
| ------------------- | -------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
| `ACCEPTED`          | Repost accepted the event.                                                                                           | Done — you get a `SendResult`, not an exception.                                                  |
| `NOT_SENT`          | The request definitely never left.                                                                                   | Safe to retry with the same key.                                                                  |
| `POSSIBLY_SENT`     | The request may have been received; the response was lost, ambiguous (HTTP 409), or the deadline elapsed mid-flight. | **Reuse the same idempotency key and reconcile** — never mint a new key, or you risk a duplicate. |
| `REJECTED`          | Repost definitively rejected it (a non-retryable 4xx).                                                               | Fix the request; do not retry blindly.                                                            |
| `CANCELLED_UNKNOWN` | You cancelled after the request may have been sent.                                                                  | Same as `POSSIBLY_SENT`: reconcile with the same key.                                             |

```kotlin theme={"languages":{"custom":["/languages/repost.json"]}}
try {
    val result = repost.webhooks.order.created(customerId = "customer-123") { id = "order-123" }
    println("accepted ${result.id}")
} catch (failure: RepostException) {
    when {
        // Repost may already have the event. Reuse the SAME idempotency key and
        // reconcile — never mint a new key, or you risk a duplicate delivery.
        failure.deliveryState == DeliveryState.POSSIBLY_SENT -> reconcile(failure.idempotencyKey)
        // Definitely not sent and safe to retry with the same key.
        failure.isRetryable -> retryLater(failure.idempotencyKey)
        // Terminal failure; the code is stable and carries no payload or credentials.
        else -> throw failure
    }
}
```

The subclass names the category: `RepostConfigurationException`, `RepostValidationException`, `RepostSerializationException`, `RepostTransportException` (connect/TLS/timeout/cancel/overload), `RepostPublishException` (server rejection or retryable failure), and `RepostDescriptorVersionException` (regenerate or upgrade).

## Coroutines and cancellation

A `suspend` send suspends the coroutine without blocking a thread and honours structured concurrency: cancelling the coroutine cancels the underlying operation, and the coroutine sees a native `CancellationException`. To reconcile after a cancellation, keep the operation handle from `createdOperation(...)` and read its `outcome()`, which settles even when the send is cancelled:

```kotlin theme={"languages":{"custom":["/languages/repost.json"]}}
val operation = repost.webhooks.order.createdOperation("customer-123", Order { id = "order-123" })

// Cancelling the coroutine that awaits the send also cancels this operation; here we
// cancel it directly. The delivery outcome still settles and never throws.
operation.cancel(true)
operation.outcome().thenAccept { outcome ->
    if (outcome.deliveryState == DeliveryState.CANCELLED_UNKNOWN) {
        // The request may have reached Repost. Reconcile with the same key.
        reconcile(outcome.idempotencyKey)
    }
}
```

Cancelling before the request commits settles `NOT_SENT`; cancelling after it may have been written settles `CANCELLED_UNKNOWN`. Either way the operation settles exactly once and closes only the work it owns.

## Bounded admission

`maxInFlightOperations` is a hard bulkhead, not a queue: when it is exceeded, a send fails immediately with an `OVERLOADED` error and `NOT_SENT` — treat it as backpressure, back off, and retry. The runtime never buffers unbounded work or paces your traffic.

## Continue

<Columns cols={3} className="gap-y-4">
  <Card title="Observability" icon="activity" href="/docs/send/kotlin/observability" cta="Observe" arrow="true">
    Watch in-flight operations, dropped events, and attempt lifecycles.
  </Card>

  <Card title="Testing" icon="flask-conical" href="/docs/send/kotlin/testing" cta="Test" arrow="true">
    Assert on outcomes and recorded requests without the network.
  </Card>

  <Card title="SDKs" icon="package" href="/docs/send/sdks" cta="All languages" arrow="true">
    The behavior every generated client shares, in every language.
  </Card>
</Columns>
