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(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_inboundapp env only (never core:mailglass). The default value is"mailglass".:retention(keyword/0) - Retention windows (in days) per inbound table class.:infinityon 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: :nothingforeign key (CR-02):replay_runsrows reference bothrecordsandevidence, andevidencereferencesrecords. So a parent must outlive every child that references it —evidence_days >= max(execution_runs_days, replay_runs_days)andrecords_days >= evidence_days.retention/0CLAMPS 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 is90.:evidence_days- The default value is90.:execution_runs_days- The default value is90.:replay_runs_days- The default value is30.
:rate_limit(keyword/0) - Post-verify ingress rate-limit buckets. Three buckets evaluated fail-fast in order tenant -> recipient -> sender_domain. Each bucket has acapacity(token-bucket BURST size) and aper_minuteSUSTAINED refill rate. Following the coreMailglass.RateLimiterconvention, the defaults setper_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(keyword/0) - The default value is[capacity: 1000, per_minute: 1000].:capacity(non_neg_integer/0) - Required.:per_minute(non_neg_integer/0) - Required.
:sender_domain(keyword/0) - The default value is[capacity: 200, per_minute: 200].:capacity(non_neg_integer/0) - Required.:per_minute(non_neg_integer/0) - Required.
:recipient(keyword/0) - The default value is[capacity: 500, per_minute: 500].:capacity(non_neg_integer/0) - Required.:per_minute(non_neg_integer/0) - Required.
Summary
Functions
Returns the validated rate-limit buckets as a keyword list with every bucket present (defaults merged over any configured overrides).
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.
Returns the configured Postgres schema name (default "mailglass").
Validate the :mailglass_inbound retention + rate_limit config at boot.
Functions
@spec rate_limit() :: keyword()
Returns the validated rate-limit buckets as a keyword list with every bucket present (defaults merged over any configured overrides).
@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:freshand:replayruns.records_days >= evidence_days— records are referenced by evidence (and by runs, butevidence_daysalready 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.
@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.
@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.