# `Accrue.Workers.Mailer`
[🔗](https://github.com/szTheory/accrue/blob/accrue-v1.0.0/lib/accrue/workers/mailer.ex#L1)

Oban worker that delivers a transactional email asynchronously.

## Flow

1. `Accrue.Mailer.Default.deliver/2` enqueues a job with string-keyed
   `%{"type" => "...", "assigns" => %{...}}` args (Oban-safe scalars only).
 2. `perform/1` rehydrates the assigns (locale/timezone/customer hydration
    via `enrich/2`), resolves the template module (honoring `:email_overrides`
    rung 2 MFA and rung 3 atom), builds a Mailglass message for the Mailglass
    mailers, and delivers via `Mailglass.deliver/1`.

## Queue

Host applications MUST configure an Oban queue named `:accrue_mailers`.
Recommended concurrency: 20.

## Idempotency

Delivery-level idempotency uses a business-event key stamped onto the
Mailglass message metadata. That keeps the enqueue boundary scalar-only
while letting the delivery path dedupe on the approved event key.

# `enrich`

```elixir
@spec enrich(atom(), map()) :: map()
```

Enriches raw Oban-arg assigns with locale, timezone, and (optionally)
the hydrated `Accrue.Billing.Customer` struct (D6-03 precedence ladder).

Precedence for locale:

  1. `assigns[:locale]` / `assigns["locale"]`
  2. `customer.preferred_locale` (hydrated via `assigns[:customer_id]`)
  3. `Accrue.Config.default_locale/0`
  4. Hardcoded `"en"` fallback

Same ladder for timezone (swap `preferred_timezone` +
`Accrue.Config.default_timezone/0` + `"Etc/UTC"`).

Unknown locales/zones emit `[:accrue, :email, :locale_fallback]` /
`[:accrue, :email, :timezone_fallback]` telemetry with
`%{requested: value}` metadata (no PII) and fall back to `"en"` /
`"Etc/UTC"`. `enrich/2` NEVER raises — pitfall 5 defense.

# `resolve_template`

```elixir
@spec resolve_template(atom()) :: module()
```

Resolves the template module for an email type. Honors `:email_overrides`
(Pay-style ladder D-23):

  * Rung 2 (MFA): `{Mod, :fun, args}` calls `Mod.fun(type, *args)` at
    runtime, letting hosts pick a template dynamically based on request-
    time context. The type atom is prepended to `args` as the first
    argument so MFA callbacks receive it.
  * Rung 3 (atom): `YourModule` replaces the default template module.
  * No override: falls through to `default_template/1`.

---

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