# `Accrue.Workers.Mailer`
[🔗](https://github.com/szTheory/accrue/blob/accrue-v0.3.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 `%Swoosh.Email{}`, and
   delivers via `Accrue.Mailer.Swoosh`.

## Queue

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

## Pitfall 7 defense

`unique: [period: 60, fields: [:args, :worker]]` prevents double-dispatch
when both a Billing action AND a webhook reducer try to enqueue the same
email within 60s. DO NOT TOUCH this option — it's the only guard against
the action+webhook duplication pitfall.

# `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`.

# `template_for`

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

Public accessor for the default template module for a given email
type. Used by `mix accrue.mail.preview` (D6-08) and by tests. Honors
the full 13-type catalogue plus the `:payment_succeeded` legacy alias.

---

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