# `Mailglass.Webhook.Reconciler`
[🔗](https://github.com/szTheory/mailglass/blob/v1.0.0/lib/mailglass/webhook/reconciler.ex#L2)

Reconcile orphan webhook events against committed deliveries.

An orphan webhook event is one inserted by `Mailglass.Webhook.Ingest`
with `delivery_id: nil + needs_reconciliation: true` because the
matching `mailglass_deliveries` row had not yet committed when the
webhook arrived (a real race in production for low-latency
providers).

The canonical `reconcile/2` function is compiled in every install so
operators can run the same maintenance sweep manually even when Oban is
absent. When Oban is present, this module also exposes an Oban worker
entrypoint via `perform/1` so adopters can schedule background runs.

For each orphan older than 60 seconds (grace window for late commits):

  1. `Mailglass.Events.Reconciler.find_orphans/1` returns the candidate
     batch (tenant-scoped, age-bounded, newest `max_age_minutes` only)
  2. `Mailglass.Events.Reconciler.attempt_link/1` looks up the Delivery
     via `(provider, provider_message_id)` from the orphan's metadata
  3. On match: append a NEW `:reconciled` event (D-18 — append, never
     UPDATE the orphan row; preserves the SQLSTATE 45A01 append-only
     invariant) + call `Projector.update_projections/2` for the
     matched Delivery + post-commit broadcast on the Projector PubSub
     topic (Phase 3 D-04)
  4. On no-match: leave the orphan row untouched; next sweep retries

After 7 days (`max_age_minutes: 7 * 24 * 60`), `find_orphans/1` filters
the row out of the scan (admin LiveView shows it as "older than 7 days
— unlikely to reconcile" per D-19).

## Optional-dep gating

`available?/0` reports whether the Oban worker entrypoint is compiled.
`Mailglass.Application` uses that to decide whether to warn about
missing scheduled background workers. The manual CLI path still calls
`reconcile/2` directly in both modes.

## Concurrency

`unique: [period: 60]` dedupes overlapping cron runs when Oban is
present. `concurrency: 1` is the implicit default for
`:mailglass_reconcile` queue; adopters who raise it accept the
reconciliation race (Ecto optimistic locking on the Delivery row still
makes the final write correct, but duplicate `:reconciled` events
become possible if two workers see the same orphan — the partial
UNIQUE index on `idempotency_key` (`"reconciled:#{orphan.id}"`)
structurally prevents this anyway).

# `available?`
*since 0.1.0* 

```elixir
@spec available?() :: true
```

Returns `true` when the Oban worker entrypoint is compiled.

# `reconcile`

```elixir
@spec reconcile(String.t() | nil, pos_integer()) ::
  {:ok, %{scanned: non_neg_integer(), linked: non_neg_integer()}}
```

Run the reconciliation sweep for the given tenant (or all tenants
when `tenant_id` is `nil`).

Returns `{:ok, %{scanned: n, linked: m}}` on success.

Exposed as a public function so `mix mailglass.reconcile` can invoke
the same code path; also useful in tests and for ops engineers who
want to run a sweep out-of-band without waiting for the next cron tick.

---

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