# `Mailglass.RateLimiter`
[🔗](https://github.com/szTheory/mailglass/blob/v1.0.0/lib/mailglass/rate_limiter.ex#L1)

Multi-bucket ETS token bucket rate limiter (RATE-01).

Hot path is `:ets.update_counter/4` — no GenServer mailbox
serialization. The `TableOwner` GenServer exists only to own the
table (see D-22). ≈1-3μs on OTP 27 with `decentralized_counters: true`
plus `write_concurrency: :auto`.

## Invariants

- **`:transactional` bypass (D-24):** `check/1` with
  `stream == :transactional` returns `:ok` BEFORE any ETS read.
  Password-reset / magic-link / verify-email MUST NOT be throttled
  because a marketing campaign saturated the bucket. Documented as
  a reserved invariant in `docs/api_stability.md`; this is NOT a
  tunable.
- **Leaky-bucket continuous refill (D-23):** capacity tokens refill
  at `capacity / 60_000` tokens/ms.

## Configuration

    config :mailglass, :rate_limit,
      tenant_recipient: [
        default: [capacity: 100, per_minute: 100],
        overrides: [
          {{"premium-tenant", "gmail.com"}, [capacity: 500, per_minute: 500]}
        ]
      ],
      global_recipient: [
        default: [capacity: 1000, per_minute: 1000]
      ],
      sender_domain: [
        default: [capacity: 500, per_minute: 500]
      ]

## Telemetry

Single-emit `[:mailglass, :outbound, :rate_limit, :stop]` with:
- Measurements: `%{duration_us: integer()}`
- Metadata: `%{allowed: boolean(), tenant_id: String.t()}`

**No PII** — domains are NOT emitted in telemetry (to stay inside
the Phase 1 D-31 whitelist).

# `check`
*since 0.5.0* 

```elixir
@spec check(Mailglass.Message.t()) :: :ok | {:error, Mailglass.RateLimitError.t()}
```

Returns `:ok` when the delivery is allowed, or `{:error, %RateLimitError{}}`
when any bucket is depleted. `:transactional` stream always returns `:ok`.

# `check`
*since 0.1.0* 

```elixir
@spec check(String.t(), String.t(), atom()) ::
  :ok | {:error, Mailglass.RateLimitError.t()}
```

Backward compatibility shim for `check/3`. Delegates to `check/1` by
building a synthetic message.

---

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