# mailglass_admin
Mountable LiveView surfaces for mailglass. Today that means:
- a dev-preview dashboard for mailable iteration
- a production operator dashboard for delivery inspection and targeted webhook replay inside an adopter-owned auth boundary
The operator surface is delivery-centric. It helps an authenticated operator
inspect provider lifecycle facts, replay facts, and reconcile facts for one
selected delivery. The canonical support runbook lives in
`guides/operator-incident-support.md`.
## API Stability
The canonical operator trust contract lives in
[`docs/operator-trust.md`](docs/operator-trust.md).
The canonical `v1.x` admin surface inventory lives in
[`docs/api_stability.md`](docs/api_stability.md).
The canonical matched-sibling compatibility and deprecation policy lives in the
core repo guide
[`../guides/compatibility-and-deprecations.md`](../guides/compatibility-and-deprecations.md)
and is re-exposed here through
[`docs/compatibility-and-deprecations.md`](docs/compatibility-and-deprecations.md).
The trust contract is intentionally narrow:
- stable: router macros, their documented options, the `MailglassAdmin.Auth`
behaviour, and the operator auth/session/replay semantics
- internal: LiveView modules, component modules, DOM/CSS shape, preview assigns
plumbing, layouts, and internal mount wiring
Do not treat ExDoc visibility, public function reachability, or framework
callback exports as the contract by themselves.
Use `docs/operator-trust.md` for the stable router/auth/session/replay
semantics. Use the compatibility guide for release-line matching,
support-matrix truth, retained compatibility bridges, and upgrade posture. Use
the admin stability page only for the package surface inventory.
## Installation
Add `mailglass_admin` to your adopter app's `mix.exs`:
def deps do
[
{:mailglass, "~> 0.3"},
{:mailglass_admin, "~> 0.3", only: :dev}
]
end
Then `mix deps.get`.
## Mount the dev preview
Add four lines to `lib/my_app_web/router.ex`:
import MailglassAdmin.Router
if Application.compile_env(:my_app, :dev_routes) do
scope "/dev" do
pipe_through :browser
mailglass_admin_routes "/mail"
end
end
Restart `mix phx.server`, visit `/dev/mail`. Done.
The `if Application.compile_env(:my_app, :dev_routes) do ... end` wrapper is
the Phoenix 1.8 convention (same gate that protects `live_dashboard` and
`Plug.Swoosh.MailboxPreview`). `mailglass_admin` does not check `Mix.env()`
itself — dev-only is the adopter's responsibility.
## Mount the production operator surface
Import the same router helpers, but mount the operator surface inside your
normal authenticated browser scope:
import MailglassAdmin.Router
scope "/ops" do
pipe_through [:browser, :require_authenticated_user]
mailglass_operator_routes "/mail",
auth: MyApp.MailglassAdminAuth,
session: [
subject_id: "current_user_id",
tenant_id: "current_tenant_id",
auth_method: "auth_method",
recent_auth_at: "recent_auth_at"
],
on_mount: [{MyAppWeb.UserAuth, :require_authenticated_user}],
unauthorized_path: "/users/log-in"
end
`auth:` stays adopter-owned. `mailglass_admin` does not ship a login system,
session schema, or recent-auth prompt. It expects your app to decide who may
enter the operator surface and how "recent authentication" is satisfied.
## Operator replay contract
The operator surface now supports targeted webhook replay from the selected
delivery detail pane.
- Replay starts from one selected delivery, but the server resolves that UI
selection to one exact stored `mailglass_webhook_events` row before any
side effect runs.
- When exactly one raw webhook target is safe, the confirmation modal
preselects it. When multiple targets are safe, the operator must choose
one explicitly. When no exact target is safe, the modal explains why replay
is unavailable instead of guessing.
- Confirming replay calls your adopter-owned `auth:` module with
`:destructive_action` at action time. Mount-time authorization is not
enough for replay.
- Replay reuses mailglass's existing webhook normalization and idempotency
semantics. A replay can honestly result in either new work or a duplicate
/ no-op convergence outcome.
- Every replay attempt is ledger-audited with requested, succeeded, or failed
facts that stay visible in the delivery timeline.
## Operator support boundary
- Provider lifecycle facts come from the delivery timeline, matched webhook
events, and the shipped telemetry families documented in `guides/telemetry.md`.
- Replay facts are operator-triggered audit facts for one exact stored webhook
target.
- Reconcile facts come from the background-first orphan sweep and
`mix mailglass.reconcile`.
- `mailglass_admin` does not ship a separate observability dashboard,
cross-tenant incident console, or unauthenticated support route.
## LiveReload setup (optional)
When your adopter app runs under `:phoenix_live_reload`, mailglass_admin can
refresh the preview automatically on file save. Add a `live_reload.notify`
entry to your endpoint:
config :my_app, MyAppWeb.Endpoint,
live_reload: [
notify: [
"mailglass:admin:reload": [~r"lib/.*mailer.*\.ex$"]
]
]
The topic is prefixed `mailglass:admin:reload` (not bare `mailglass_admin_reload`)
to match the package's `mailglass:`-prefixed PubSub naming convention. When
LiveReload is not configured the preview still works — the adopter just
refreshes the browser manually.
## `preview_props/0` contract
Each `Mailglass.Mailable` module can declare preview scenarios by defining
`preview_props/0`:
defmodule MyApp.UserMailer do
use Mailglass.Mailable, stream: :transactional
def preview_props do
[
welcome_default: %{user: %User{name: "Ada"}, team: %Team{name: "Analytical Engines"}},
welcome_enterprise: %{user: %User{name: "Ada"}, team: %Team{name: "Analytical Engines"}, plan: :enterprise}
]
end
def welcome(assigns), do: ...
end
Each tuple is a discrete scenario; the sidebar nests scenarios under the
mailable module name (`MyApp.UserMailer -> welcome_default`). Scenarios
appear in insertion order. Mailables without `preview_props/0` still show
up in the sidebar as `No previews defined` — they remain discoverable even
before you write any scenarios.
## What this ships
- Auto-discovered mailable sidebar (collapsible scenario groups)
- Four tabs per scenario: HTML, Text, Raw (RFC 5322 envelope), Headers
- Type-inferred assigns form — edit any top-level assign inline and
re-render
- Device toggle (375 / 768 / 1024) + chrome dark toggle
- Graceful failure badges for mailables whose `preview_props/0` raises
- A production operator mount with a separate `live_session`, explicit
session whitelist, and adopter-owned auth seam
- Delivery, timeline, and suppression visibility in the operator UI
- Targeted webhook replay from the selected delivery detail view with
action-time auth checks, exact-target confirmation, and durable timeline
audit visibility
## What this does NOT ship
- Hosted authentication, user storage, or a recent-auth UX. Keep auth and
step-up ownership in the adopter app.
- Bulk replay, "replay latest", or delivery-wide replay guessing. The operator
surface replays one exact stored webhook target at a time.
- Suppression removal flows. The recent-auth seam exists, but suppression
reversal remains a later phase.
- Search, filter, or pagination over mailables. v0.5.
- Inbound-mail (`mailglass_inbound`) Conductor LiveView — separate sibling
package, v0.5+.
- Stable DOM/component/LiveView implementation APIs. Those remain internal even
when they are visible in generated docs or reachable in source.
## License
MIT. Released alongside `mailglass` via coordinated linked-version Release
Please tags.