# `ExWapp.Session.Worker.SendHealth`
[🔗](https://github.com/elchemista/ex_wapp/blob/v0.1.5/lib/ex_wapp/session/worker/send_health.ex#L1)

Tracks how well outbound sending is going, and decides when to trip the guard.

Owns the `send_health` field of the session state: error and drop counters,
the one-minute rolling windows behind the per-minute thresholds, and the
paused/hard-stopped gates that block sending.

Deciding is separate from acting: `guard_trigger_reason/2` is pure and answers
"have we crossed a threshold", while stopping or disconnecting the session
stays with `ExWapp.Session.Worker`, which owns the transport and the process.

# `metrics`

```elixir
@type metrics() :: %{
  consecutive: non_neg_integer(),
  errors_min: non_neg_integer(),
  drops_min: non_neg_integer()
}
```

# `trigger_reason`

```elixir
@type trigger_reason() :: {atom(), non_neg_integer()} | nil
```

# `broadcast_event`

```elixir
@spec broadcast_event(ExWapp.Session.Worker.t(), atom(), map()) :: :ok
```

Publishes a health event, never failing the caller if delivery breaks.

# `clear_consecutive_send_errors`

```elixir
@spec clear_consecutive_send_errors(ExWapp.Session.Worker.t()) ::
  ExWapp.Session.Worker.t()
```

Clears the consecutive-error streak after a send finally succeeds.

# `default`

```elixir
@spec default() :: map()
```

The counters a fresh session starts from.

# `ensure_send_enabled`

```elixir
@spec ensure_send_enabled(ExWapp.Session.Worker.t(), String.t()) ::
  {:ok, ExWapp.Session.Worker.t()} | {:error, atom(), ExWapp.Session.Worker.t()}
```

Rejects a send while the guard holds the session paused or hard-stopped.

A rejection is itself recorded as a send error, so repeated attempts against a
closed gate keep the counters honest.

# `get`

```elixir
@spec get(ExWapp.Session.Worker.t(), atom(), term()) :: term()
```

Reads one counter, falling back to `default` when it was never set.

# `guard_metrics`

```elixir
@spec guard_metrics(ExWapp.Session.Worker.t()) :: metrics()
```

The three numbers the guard thresholds are compared against.

# `guard_trigger_reason`

```elixir
@spec guard_trigger_reason(metrics(), map()) :: trigger_reason()
```

Names the threshold that has been crossed, or `nil` while all are within range.

Pure: given the metrics and the configured limits it decides nothing else.

# `increment`

```elixir
@spec increment(ExWapp.Session.Worker.t(), atom(), integer()) ::
  ExWapp.Session.Worker.t()
```

Adds to one counter, treating a missing counter as zero.

# `increment_outbound_queue_errors`

```elixir
@spec increment_outbound_queue_errors(ExWapp.Session.Worker.t(), term()) ::
  ExWapp.Session.Worker.t()
```

Records an outbound-queue failure, which is counted apart from send errors.

# `put`

```elixir
@spec put(ExWapp.Session.Worker.t(), atom(), term()) :: ExWapp.Session.Worker.t()
```

Writes one counter.

# `register_drop_timeout`

```elixir
@spec register_drop_timeout(ExWapp.Session.Worker.t(), String.t(), map()) ::
  ExWapp.Session.Worker.t()
```

Records a message dropped after its confirmation never arrived.

# `register_send_error`

```elixir
@spec register_send_error(ExWapp.Session.Worker.t(), term(), map()) ::
  ExWapp.Session.Worker.t()
```

Records a failed send and broadcasts it to health subscribers.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
