Append-only row in mailglass_events.
Exposes changeset/1 for INSERTS only. There is NO update/delete
helper — the mailglass_events_immutable_trigger (Plan 02) raises
SQLSTATE 45A01 on any UPDATE/DELETE; Mailglass.Repo.transact/1
(Plan 01) translates to Mailglass.EventLedgerImmutableError.
Absence of an update helper also prevents code that looks like it could work but blows up in production.
Atom Sets
:type— full Anymail taxonomy + mailglass internal:dispatchedand:suppressed(D-14 project-level).:reject_reason—:invalid | :bounced | :timed_out | :blocked | :spam | :unsubscribed | :other(nullable; D-14).
Relationships
delivery_id is a logical :binary_id reference — NO FK to
mailglass_deliveries (ARCHITECTURE §4.3; Pitfall 4 in RESEARCH).
Orphan webhooks insert with delivery_id: nil and are linked later
by Mailglass.Events.Reconciler (Plan 05).
Idempotency
:idempotency_key is a nullable string backed by a partial UNIQUE
index (mailglass_events_idempotency_key_idx WHERE idempotency_key IS NOT NULL). Plan 05's Events.append/1 will pass
on_conflict: :nothing, conflict_target: {:unsafe_fragment, "(idempotency_key) WHERE idempotency_key IS NOT NULL"} — replays are
no-ops.
Summary
Functions
Closed reject_reason atom set.
Closed type atom set (Anymail taxonomy + internal). Cross-checked in api_stability.md.
Builds an INSERT-only changeset for a new %Event{}.
Types
@type t() :: %Mailglass.Events.Event{ __meta__: term(), delivery_id: Ecto.UUID.t() | nil, id: Ecto.UUID.t() | nil, idempotency_key: String.t() | nil, inserted_at: DateTime.t() | nil, metadata: map(), needs_reconciliation: boolean() | nil, normalized_payload: map(), occurred_at: DateTime.t() | nil, reject_reason: atom() | nil, tenant_id: String.t() | nil, trace_id: String.t() | nil, type: atom() | nil }
Functions
@spec __reject_reasons__() :: [atom()]
Closed reject_reason atom set.
@spec __types__() :: [atom()]
Closed type atom set (Anymail taxonomy + internal). Cross-checked in api_stability.md.
@spec changeset(map()) :: Ecto.Changeset.t()
Builds an INSERT-only changeset for a new %Event{}.
Intentionally the only public mutation point. No update or delete
helper is exposed — UPDATE and DELETE at the Repo layer raise
Mailglass.EventLedgerImmutableError via the DB trigger, and the
Elixir surface deliberately offers no path that looks like it could
work.