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

Per-`{tenant_id, recipient_domain}` ETS token bucket (SEND-02).

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/3` 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. Default: 100 tokens @ 100/min.

## Configuration

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

Missing `:rate_limit` key uses built-in defaults.

## Telemetry

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

**No PII** — recipient_domain is NOT emitted (domain is less
sensitive than full address, but to stay inside the Phase 1 D-31
whitelist we omit it; operators who need it can add a domain-aware
handler that reads context from other sources).

# `check`
*since 0.1.0* 

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

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

## Arguments

- `tenant_id` — binary tenant id
- `recipient_domain` — binary domain (e.g. `"gmail.com"`)
- `stream` — `:transactional | :operational | :bulk`

---

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