# `Mailglass.Telemetry`
[🔗](https://github.com/szTheory/mailglass/blob/v0.1.0/lib/mailglass/telemetry.ex#L1)

Telemetry integration for mailglass.

## Event Naming Convention

All mailglass events follow the 4-level path plus a phase suffix:

    [:mailglass, :domain, :resource, :action, :start | :stop | :exception]

Named span helpers wrap `:telemetry.span/3` for each domain. Domain helpers
land in their owning phase (render in Phase 1, send/batch in Phase 3,
persist/events in Phase 2, webhook_verify/webhook_ingest in Phase 4,
preview_render in Phase 5).

## Phase 1 Events

### Render pipeline

  * `[:mailglass, :render, :message, :start | :stop | :exception]`
    — Measurements on `:start`: `%{system_time: integer}`
    — Measurements on `:stop`: `%{duration: native_time}`
    — Metadata: `%{tenant_id: string, mailable: atom}`

## Metadata Policy (D-31)

**Whitelisted keys:** `:tenant_id, :mailable, :provider, :status,
:message_id, :delivery_id, :event_id, :latency_ms, :recipient_count,
:bytes, :retry_count`.

**Forbidden (PII):** `:to, :from, :body, :html_body, :subject, :headers,
:recipient, :email`.

Enforcement is lint-time (Phase 6 custom Credo check `NoPiiInTelemetryMeta`)
plus a runtime StreamData property test that asserts every emitted stop
event's metadata keys are a subset of the whitelist across 1000 varied
inputs.

## Handler Isolation

`:telemetry.span/3` wraps each attached handler in a try/catch. A handler
that raises is detached automatically and `[:telemetry, :handler, :failure]`
is emitted — the caller's pipeline is unaffected. Mailglass does **not**
add a parallel try/rescue wrapper (would duplicate or — worse — swallow
the meta-event operators rely on).

## Default Logger

Call `attach_default_logger/1` at boot (or configure
`[telemetry: [default_logger: true]]` in the Application env) to log every
Mailglass event:

    Mailglass.Telemetry.attach_default_logger()
    Mailglass.Telemetry.attach_default_logger(level: :warning)

# `attach_default_logger`
*since 0.1.0* 

```elixir
@spec attach_default_logger(keyword()) :: :ok | {:error, :already_exists}
```

Attaches the default logger handler for the Phase 1 event set.

Returns `:ok` on first attach and `{:error, :already_exists}` if a handler
with the same ID is already attached (useful for idempotent boot paths).

## Options

  * `:level` — log level passed to `Logger.log/2`. Default: `:info`.

# `dispatch_span`
*since 0.1.0* 

```elixir
@spec dispatch_span(map(), (-&gt; any())) :: any()
```

Named span helper wrapping the adapter.deliver/2 call (Phase 3, D-26).

Emits `[:mailglass, :outbound, :dispatch, :start | :stop | :exception]`.
Provider latency is the fat tail — this span captures it.

# `events_append_span`
*since 0.1.0* 

```elixir
@spec events_append_span(map(), (-&gt; result)) :: result when result: term()
```

Named span helper for the events-append write path. Phase 2 surface.

Equivalent to `span([:mailglass, :events, :append], metadata, fun)`.
`:stop` metadata SHOULD include `inserted?: boolean` and
`idempotency_key_present?: boolean` per D-04.

# `execute`
*since 0.1.0* 

```elixir
@spec execute([atom()], map(), map()) :: :ok
```

One-shot wrapper around `:telemetry.execute/3` for non-span counter events.

Callers are expected to prepend `:mailglass` to the event path.

# `persist_outbound_multi_span`
*since 0.1.0* 

```elixir
@spec persist_outbound_multi_span(map(), (-&gt; any())) :: any()
```

Named span helper wrapping each Multi commit in the send pipeline (Phase 3, D-26).

Emits `[:mailglass, :persist, :outbound, :multi, :start | :stop | :exception]`.
Metadata carries `:step_name` (`:persist_queued | :persist_dispatched | :persist_failed`).

# `persist_span`
*since 0.1.0* 

```elixir
@spec persist_span([atom()], map(), (-&gt; result)) :: result when result: term()
```

Named span helper for persist-layer write paths (projector, reconciler).
Phase 2 surface.

Event path: `[:mailglass, :persist | suffix]`. Examples:

    Mailglass.Telemetry.persist_span([:delivery, :update_projections], meta, fn -> ... end)
    Mailglass.Telemetry.persist_span([:reconcile, :link], meta, fn -> ... end)

# `render_span`
*since 0.1.0* 

```elixir
@spec render_span(map(), (-&gt; result)) :: result when result: term()
```

Named span helper for the render pipeline. Phase 1 surface.

Equivalent to `span([:mailglass, :render, :message], metadata, fun)`.

# `send_span`
*since 0.1.0* 

```elixir
@spec send_span(map(), (-&gt; any())) :: any()
```

Named span helper for the Outbound hot path (Phase 3, D-26).

Emits `[:mailglass, :outbound, :send, :start | :stop | :exception]`.
Metadata whitelist per D-31: `:tenant_id, :mailable, :stream, :delivery_id, :status, :latency_ms`.

# `span`
*since 0.1.0* 

```elixir
@spec span([atom()], map(), (-&gt; result)) :: result when result: term()
```

Wraps a zero-arity function in `:telemetry.span/3`, emitting `:start`,
`:stop`, and (on exception) `:exception` events under `event_prefix`.

The same metadata map is emitted on every phase. The function's return
value is returned unchanged.

## Examples

    Mailglass.Telemetry.span([:mailglass, :render, :message],
      %{tenant_id: "acme", mailable: MyMailer},
      fn -> render(message) end)

---

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