# `MailglassInbound.Config`
[🔗](https://github.com/szTheory/mailglass/blob/v2.3.0/lib/mailglass_inbound/config.ex#L1)

Validated configuration accessor for the `:mailglass_inbound` app env.

Mirrors the *style* of `Mailglass.Config` (NimbleOptions `@schema` declared
before `@moduledoc`, `validate_at_boot!/0`) but reads the **`:mailglass_inbound`**
app env, never core `Mailglass.Config`. Adding inbound keys to core would invert
the package dependency — core reads `:mailglass` only and cannot validate config
for a package it does not depend on (boundary law).

## Configuration

    config :mailglass_inbound,
      retention: [
        records_days: 90,
        evidence_days: 90,
        execution_runs_days: 90,
        replay_runs_days: 30
      ],
      rate_limit: [
        tenant:        [capacity: 1000, per_minute: 1000],
        sender_domain: [capacity: 200,  per_minute: 200],
        recipient:     [capacity: 500,  per_minute: 500]
      ]

`:infinity` on any retention class disables that window. Only the knobs the
runtime actually reads are shipped — no speculative per-tenant override maps
(honest-surface).

## Schema

* `:schema` (`t:String.t/0`) - Names the Postgres SCHEMA holding all mailglass inbound tables. `"public"` is the explicit pre-2.0 opt-out. Must be a valid unquoted Postgres identifier. Read from the `:mailglass_inbound` app env only (never core `:mailglass`). The default value is `"mailglass"`.

* `:retention` (`t:keyword/0`) - Retention windows (in days) per inbound table class. `:infinity` on a class
  disables that window. Defaults: records 90, evidence 90, execution_runs 90,
  replay_runs 30.

  The windows must respect the FK lineage so the child-first prune never trips
  an `on_delete: :nothing` foreign key (CR-02): `replay_runs` rows reference
  both `records` and `evidence`, and `evidence` references `records`. So a
  parent must outlive every child that references it —
  `evidence_days >= max(execution_runs_days, replay_runs_days)` and
  `records_days >= evidence_days`. `retention/0` CLAMPS any configured override
  up to satisfy this invariant rather than letting prune crash on an FK
  violation.

  The default value is `[]`.

  * `:records_days` - The default value is `90`.

  * `:evidence_days` - The default value is `90`.

  * `:execution_runs_days` - The default value is `90`.

  * `:replay_runs_days` - The default value is `30`.

* `:rate_limit` (`t:keyword/0`) - Post-verify ingress rate-limit buckets. Three buckets evaluated fail-fast
  in order tenant -> recipient -> sender_domain. Each bucket has a `capacity`
  (token-bucket BURST size) and a `per_minute` SUSTAINED refill rate. Following
  the core `Mailglass.RateLimiter` convention, the defaults set
  `per_minute == capacity`, so "N/min" is literally the sustained throughput:
  the bucket refills its full capacity each minute. Defaults: tenant 1000/min,
  recipient 500/min, sender_domain 200/min. The default value is `[]`.

  * `:tenant` (`t:keyword/0`) - The default value is `[capacity: 1000, per_minute: 1000]`.

    * `:capacity` (`t:non_neg_integer/0`) - Required.

    * `:per_minute` (`t:non_neg_integer/0`) - Required.

  * `:sender_domain` (`t:keyword/0`) - The default value is `[capacity: 200, per_minute: 200]`.

    * `:capacity` (`t:non_neg_integer/0`) - Required.

    * `:per_minute` (`t:non_neg_integer/0`) - Required.

  * `:recipient` (`t:keyword/0`) - The default value is `[capacity: 500, per_minute: 500]`.

    * `:capacity` (`t:non_neg_integer/0`) - Required.

    * `:per_minute` (`t:non_neg_integer/0`) - Required.

# `rate_limit`
*since 1.2.0* 

```elixir
@spec rate_limit() :: keyword()
```

Returns the validated rate-limit buckets as a keyword list with every bucket
present (defaults merged over any configured overrides).

# `retention`
*since 1.2.0* 

```elixir
@spec retention() :: keyword()
```

Returns the validated retention windows as a keyword list with every class
present (defaults merged over any configured overrides), CLAMPED to satisfy the
FK-lineage invariant.

The inbound prune deletes child-first against `on_delete: :nothing` FKs, so a
parent window can never be shorter than a child that references it. This
accessor enforces:

  * `evidence_days >= max(execution_runs_days, replay_runs_days)` — evidence is
    referenced by both `:fresh` and `:replay` runs.
  * `records_days >= evidence_days` — records are referenced by evidence (and by
    runs, but `evidence_days` already dominates the run windows).

A configured override that would invert the lineage is silently clamped UP to
the smallest safe value rather than allowed to crash the sweep on a
`foreign_key_violation`. `:infinity` (window disabled) is treated as the
maximum, so an `:infinity` child forces its parents to `:infinity` too.

# `schema`
*since 2.0.0* 

```elixir
@spec schema() :: String.t()
```

Returns the configured Postgres schema name (default `"mailglass"`).

Serves from `:persistent_term` in O(1) on the hot path. Warmed at boot by
`validate_at_boot!/0`; on a cold-cache miss (a boot-skipped context) it
self-heals by re-reading and re-validating the `:schema` `:mailglass_inbound`
app env (never core `:mailglass`), then caching the validated value.
`"public"` is the explicit pre-2.0 opt-out.

Reuses core's `Mailglass.Identifier.validate!/2` — one validator across the
family, no inbound-local regex and no inbound-local error type. The value is
always validated once at the cache-write boundary so an adopter's
`schema: "public"` opt-out can never be masked by a silent default.

# `validate_at_boot!`
*since 1.2.0* 

```elixir
@spec validate_at_boot!() :: :ok
```

Validate the `:mailglass_inbound` retention + rate_limit config at boot.

Returns `:ok`. Raises `NimbleOptions.ValidationError` if the configured shape
is invalid (e.g. a negative retention value, a non-`:infinity` atom on a
retention class, or a negative bucket capacity). Raises
`Mailglass.ConfigError` with `type: :invalid` if `:schema` is not a valid
unquoted Postgres identifier.

Inbound's own `MailglassInbound.Application.start/2` now calls this at boot to
warm the `:schema` `:persistent_term` cache and fail fast on a bad identifier.
Only `:schema` is cached — the on-demand `retention/0` / `rate_limit/0` reads
stay on the uncached `validated/0` path and are unaffected.

---

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