# `Mailglass.Telemetry`
[🔗](https://github.com/szTheory/mailglass/blob/v2.5.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 , send/batch in ,
persist/events in , webhook_verify/webhook_ingest in ,
preview_render in ).

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

**Whitelisted keys:** `:tenant_id, :mailable, :provider, :stream, :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`.
Telemetry metadata must never include recipient or message-content PII.

Enforcement is lint-time ( 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  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 (, ).

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

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

# `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 (, ).

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).
 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.  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 (, ).

Emits `[:mailglass, :outbound, :send, :start | :stop | :exception]`.
Metadata whitelist per : `: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*
