Accrue.Workers.Mailer (accrue v1.0.0)

Copy Markdown View Source

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.

Summary

Functions

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

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

Functions

enrich(type, assigns)

@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(type)

@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.