Mailglass.Suppression.Entry (Mailglass v0.1.0)

Copy Markdown View Source

Ecto schema for a row in mailglass_suppressions.

Atom Sets (D-07, D-10, D-11)

  • :scope:address | :domain | :address_stream. NO default; changeset validate_required enforces (MAIL-07 prevention).

  • :stream:transactional | :operational | :bulk (nullable; populated only when scope = :address_stream).

  • :reason:hard_bounce | :complaint | :unsubscribe | :manual | :policy | :invalid_recipient.

Coupling invariants (D-07)

  • scope = :address_stream REQUIRES stream.
  • scope IN (:address, :domain) REJECTS stream.

Enforced both at the changeset layer (validate_scope_stream_coupling/1) and at the DB layer (mailglass_suppressions_stream_scope_check CHECK constraint — Plan 02). Belt-and-suspenders: either layer alone would suffice, but lint-time errors (changeset) are cheaper than runtime errors (Postgres).

Address normalization

The column is CITEXT, so the DB compares case-insensitively. Additionally, downcase_address/1 lowercases at cast time — defense in depth, and makes "did I downcase?" questions moot for reads that bypass citext (e.g. analytics exports).

Summary

Functions

Closed reason atom set.

Closed scope atom set.

Closed stream atom set.

Builds a changeset for a new %Entry{} from an attr map.

Types

t()

@type t() :: %Mailglass.Suppression.Entry{
  __meta__: term(),
  address: String.t() | nil,
  expires_at: DateTime.t() | nil,
  id: Ecto.UUID.t() | nil,
  inserted_at: DateTime.t() | nil,
  metadata: map(),
  reason: atom() | nil,
  scope: :address | :domain | :address_stream | nil,
  source: String.t() | nil,
  stream: :transactional | :operational | :bulk | nil,
  tenant_id: String.t() | nil
}

Functions

__reasons__()

(since 0.1.0)
@spec __reasons__() :: [atom()]

Closed reason atom set.

__scopes__()

(since 0.1.0)
@spec __scopes__() :: [atom()]

Closed scope atom set.

__streams__()

(since 0.1.0)
@spec __streams__() :: [atom()]

Closed stream atom set.

changeset(attrs)

(since 0.1.0)
@spec changeset(map()) :: Ecto.Changeset.t()

Builds a changeset for a new %Entry{} from an attr map.

Enforces three invariants at the Elixir layer:

  1. :scope is required with no default (MAIL-07 prevention — D-11).
  2. Scope/stream coupling (D-07) via validate_scope_stream_coupling/1.
  3. Address normalization via downcase_address/1 — belt-and-suspenders with the underlying CITEXT column.