ETS-backed implementation of Mailglass.SuppressionStore (D-28).
Test-speed + narrow production use case (single-node, read-heavy,
sub-100-entry lists). Default impl remains
Mailglass.SuppressionStore.Ecto โ adopters override via:
config :mailglass, :suppression_store, Mailglass.SuppressionStore.ETSKey shape
ETS keys: {tenant_id, address, scope, stream_or_nil} (mirrors
Ecto UNIQUE constraint (tenant_id, address, scope, COALESCE(stream, ''))).
Lookup algorithm
check/2 tries three scopes in order (matching Ecto's OR-union):
{tenant_id, address, :address, nil}{tenant_id, domain(address), :domain, nil}{tenant_id, address, :address_stream, stream}(only when stream is set)
First hit wins. Expiry filter at read time (expired entries not returned).
UPSERT behaviour
record/2 with the same {tenant_id, address, scope, stream} key
overwrites the existing entry (equivalent to Ecto's on_conflict: {:replace, [...]}).
Test override pattern (RESEARCH ยง5.3)
Tests scope by unique tenant_id to avoid cross-test interference โ no
per-pid ownership needed (ETS table is global). Tests that need a clean
slate between runs call Mailglass.SuppressionStore.ETS.reset/0.