# `Mailglass.Outbound.Delivery`
[🔗](https://github.com/szTheory/mailglass/blob/v0.1.0/lib/mailglass/outbound/delivery.ex#L1)

One row per (Message, recipient, provider) tuple. Mutable: projection
columns are updated by `Mailglass.Outbound.Projector` (Plan 06).

Field order per CONTEXT.md "Claude's Discretion":
id → tenant_id → foreign keys → state → metadata/flags → timestamps.

`@primary_key` is UUIDv7 via the `Mailglass.Schema` macro.

## Atom Sets

- `:stream` — `:transactional | :operational | :bulk` (D-10)
- `:last_event_type` — full Anymail event taxonomy + mailglass
  internal `:dispatched` / `:suppressed` (D-14 project-level)

## Projection columns (D-13)

`dispatched_at`, `delivered_at`, `bounced_at`, `complained_at`,
`suppressed_at`, `terminal`, `last_event_type`, `last_event_at` are the
only Elixir-modifiable facts. `Mailglass.Outbound.Projector` (Plan 06)
owns writes to these columns; `metadata` is a free-form jsonb bag for
adopter-supplied non-PII extras.

## Optimistic locking (D-18)

`:lock_version` defaults to `1`. Consumers chain
`Ecto.Changeset.optimistic_lock(:lock_version)` onto the changeset when
updating — concurrent dispatch attempts raise `Ecto.StaleEntryError` on
the loser.

# `t`

```elixir
@type t() :: %Mailglass.Outbound.Delivery{
  __meta__: term(),
  bounced_at: DateTime.t() | nil,
  complained_at: DateTime.t() | nil,
  delivered_at: DateTime.t() | nil,
  dispatched_at: DateTime.t() | nil,
  id: Ecto.UUID.t() | nil,
  idempotency_key: String.t() | nil,
  inserted_at: DateTime.t() | nil,
  last_error: map() | nil,
  last_event_at: DateTime.t() | nil,
  last_event_type: atom() | nil,
  lock_version: integer() | nil,
  mailable: String.t() | nil,
  metadata: map(),
  provider: String.t() | nil,
  provider_message_id: String.t() | nil,
  recipient: String.t() | nil,
  recipient_domain: String.t() | nil,
  status: :queued | :sent | :dispatched | :failed | :suppressed | nil,
  stream: :transactional | :operational | :bulk | nil,
  suppressed_at: DateTime.t() | nil,
  tenant_id: String.t() | nil,
  terminal: boolean() | nil,
  updated_at: DateTime.t() | nil
}
```

# `__event_types__`
*since 0.1.0* 

```elixir
@spec __event_types__() :: [atom()]
```

Closed event-type atom set. Tested against api_stability.md (Phase 6 check).

# `__streams__`
*since 0.1.0* 

```elixir
@spec __streams__() :: [atom()]
```

Closed stream atom set.

# `changeset`
*since 0.1.0* 

```elixir
@spec changeset(map()) :: Ecto.Changeset.t()
```

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

Auto-populates `:recipient_domain` from `:recipient` (denormalization
per D-13) — a cheap cast-time computation that saves a `SPLIT_PART()`
at query time for rate-limit and analytics reads.

# `changeset`

```elixir
@spec changeset(t(), map()) :: Ecto.Changeset.t()
```

---

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