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

Behaviour for suppression-list storage backends.

Phase 2 ships two callbacks — `check/2` (pre-send lookup) and
`record/2` (add/update entry). Phase 3 may extend with more
callbacks when the Outbound preflight lands. Default impl is
`Mailglass.SuppressionStore.Ecto` (Postgres-backed).

Adopters swap via:

    config :mailglass, suppression_store: MyApp.SuppressionStore

## Semantics

`check/2` returns `{:suppressed, %Entry{}}` when the recipient is
on the list under any matching scope (address, domain, or
address_stream with the stream parameter). Returns `:not_suppressed`
otherwise. Never raises except on infrastructure failure.

`record/2` inserts an Entry; on UNIQUE collision
`(tenant_id, address, scope, COALESCE(stream, ''))` it UPDATES
`reason`/`source`/`expires_at`/`metadata` (admin re-adds become
idempotent at the application layer).

# `lookup_key`

```elixir
@type lookup_key() :: %{
  :tenant_id =&gt; String.t(),
  :address =&gt; String.t(),
  optional(:stream) =&gt; atom() | nil
}
```

Lookup key accepted by `c:check/2`.

`tenant_id` and `address` are required. `stream` is required when
the caller intends to match an `:address_stream`-scoped entry.

# `record_attrs`

```elixir
@type record_attrs() :: map()
```

Attr map accepted by `c:record/2`; passes through to `Mailglass.Suppression.Entry.changeset/1`.

# `check`

```elixir
@callback check(
  lookup_key(),
  keyword()
) ::
  {:suppressed, Mailglass.Suppression.Entry.t()}
  | :not_suppressed
  | {:error, term()}
```

# `record`

```elixir
@callback record(
  record_attrs(),
  keyword()
) ::
  {:ok, Mailglass.Suppression.Entry.t()} | {:error, Ecto.Changeset.t() | term()}
```

---

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