mailglass emits :telemetry events for outbound rendering and dispatch, plus
webhook signature verification, ingest, per-event normalization, orphan
detection, and reconcile sweeps. This guide documents only the event families
and metadata keys that the current code emits.
What mailglass emits today
Outbound spans from Mailglass.Telemetry
| Event path | Type | Metadata keys |
|---|---|---|
[:mailglass, :render, :message, :start | :stop | :exception] | full span | tenant_id, mailable |
[:mailglass, :outbound, :send, :start | :stop | :exception] | full span | caller-supplied whitelist keys such as tenant_id, mailable, status, delivery_id, latency_ms |
[:mailglass, :outbound, :dispatch, :start | :stop | :exception] | full span | caller-supplied whitelist keys such as provider, status, delivery_id, latency_ms |
Durable provider feedback
| Event path | Type | Measurements | Metadata keys |
|---|---|---|---|
[:mailglass, :delivery, :feedback, :stop] | single emit after commit | count (always 1) | tenant_id, delivery_id, provider, stream, mailable, status |
Feedback status is one of sent, delivered, bounced, complained,
deferred, rejected, opened, clicked, or unsubscribed. Mailglass emits
the event only for a newly persisted fact; idempotent webhook and unsubscribe
replays do not emit it again. Internal transitions such as queued,
dispatched, suppressed, and reconciled are excluded.
Webhook spans and emits from Mailglass.Webhook.Telemetry
| Event path | Type | Stop metadata keys |
|---|---|---|
[:mailglass, :webhook, :ingest, :start | :stop | :exception] | full span | provider, tenant_id, status, event_count, duplicate, failure_reason, delivery_id_matched |
[:mailglass, :webhook, :signature, :verify, :start | :stop | :exception] | full span | provider, status, failure_reason |
[:mailglass, :webhook, :normalize, :stop] | single emit | provider, event_type, mapped |
[:mailglass, :webhook, :orphan, :stop] | single emit | provider, event_type, tenant_id, age_seconds |
[:mailglass, :webhook, :duplicate, :stop] | single emit | provider, event_type |
[:mailglass, :webhook, :reconcile, :start | :stop | :exception] | full span | tenant_id, scanned_count, linked_count, remaining_orphan_count, status |
Whitelist and privacy posture
The shipped whitelist in Mailglass.Telemetry allows keys such as:
tenant_idmailableproviderstreamstatusmessage_iddelivery_idevent_idlatency_msrecipient_countbytesretry_count
Webhook helpers also emit the shipped operational keys:
event_countduplicatefailure_reasondelivery_id_matchedevent_typemappedage_secondsscanned_countlinked_countremaining_orphan_count
mailglass does not emit recipient addresses, message bodies, subjects, raw payloads, raw request bodies, IPs, or user agents in telemetry metadata.
Alert on provider feedback
Use the durable feedback family for provider-outcome metrics rather than deriving outcomes from the dispatch span:
:telemetry.attach(
"mailglass-provider-feedback",
[:mailglass, :delivery, :feedback, :stop],
fn _event, %{count: 1}, metadata, _config ->
MyApp.Observability.increment("mailglass.delivery.feedback",
tags: [
provider: metadata.provider,
stream: metadata.stream,
status: metadata.status
]
)
end,
nil
)For a low-volume launch, page on every :complained event. Do not wait for a
percentage threshold whose denominator is only tens or hundreds of messages.
Provider-side reputation and inbox placement remain provider truth; this event
reports the facts Mailglass durably received.
Reading the support model correctly
- Provider lifecycle facts come from outbound dispatch spans and normalized provider webhook events.
- Replay facts are operator-triggered audit facts on one exact stored webhook row. Replay is not its own telemetry family in this phase.
- Reconcile facts come from the background-first
[:mailglass, :webhook, :reconcile, *]span and from appended:reconciledledger events.
If a replay completed with no change, that does not mean reconcile ran. If a
reconcile sweep linked an orphan, that does not prove a provider retried
anything. Keep those fact sets separate in alerts and runbooks.
Minimal attachment examples
Log webhook ingest failures
:telemetry.attach(
"mailglass-webhook-ingest-log",
[:mailglass, :webhook, :ingest, :stop],
fn _event, measurements, metadata, _config ->
if metadata.status != :ok do
Logger.warning(
"mailglass webhook ingest status=#{metadata.status} provider=#{metadata.provider} " <>
"events=#{metadata.event_count} duration=#{measurements.duration}"
)
end
end,
nil
)Track orphan backlog pressure
:telemetry.attach(
"mailglass-webhook-orphans",
[:mailglass, :webhook, :orphan, :stop],
fn _event, _measurements, metadata, _config ->
MyApp.Metrics.increment("mailglass.webhook.orphan",
tags: [provider: metadata.provider, event_type: metadata.event_type]
)
end,
nil
)Track reconcile sweep outcomes
:telemetry.attach(
"mailglass-webhook-reconcile",
[:mailglass, :webhook, :reconcile, :stop],
fn _event, _measurements, metadata, _config ->
MyApp.Metrics.gauge("mailglass.webhook.reconcile.remaining", metadata.remaining_orphan_count)
MyApp.Metrics.increment("mailglass.webhook.reconcile.linked", metadata.linked_count)
end,
nil
)Optional backend recipes
These are optional integration patterns. mailglass does not require any
dashboard or backend beyond :telemetry.
LiveDashboard metrics
Define metrics in your adopter app and mount Phoenix.LiveDashboard if you
already use it:
summary("mailglass.webhook.ingest.duration", unit: {:native, :millisecond})
counter("mailglass.webhook.duplicate.count")
last_value("mailglass.webhook.reconcile.remaining")OpenTelemetry bridge
Attach handlers that translate shipped event names into your own spans or metrics if you already depend on OpenTelemetry:
:telemetry.attach(
"mailglass-otel-bridge",
[:mailglass, :outbound, :dispatch, :stop],
fn _event, measurements, metadata, _config ->
MyApp.Observability.record_mailglass_dispatch(measurements.duration, metadata)
end,
nil
)Sentry breadcrumb or issue enrichment
:telemetry.attach(
"mailglass-sentry-breadcrumbs",
[:mailglass, :webhook, :signature, :verify, :stop],
fn _event, _measurements, metadata, _config ->
if metadata.status != :ok do
MyApp.ErrorReporter.add_breadcrumb("mailglass webhook signature failure", metadata)
end
end,
nil
)Honeycomb-style event forwarding
:telemetry.attach(
"mailglass-honeycomb-events",
[:mailglass, :webhook, :ingest, :stop],
fn event, measurements, metadata, _config ->
MyApp.Observability.emit(event, measurements, metadata)
end,
nil
)What this guide does not promise
- No built-in observability dashboard or incident console
- No provider-side truth beyond the facts mailglass persists or emits locally
- No replay telemetry family beyond the existing delivery timeline and audit facts