mailglass - Email, made visible.

Mailglass

Mail you can see through.

CI Hex.pm HexDocs License

Mailglass is a batteries-included transactional email framework for Phoenix. It composes on top of Swoosh and ships the framework layer Swoosh deliberately leaves out: HEEx-native components with Outlook MSO/VML fallbacks, a LiveView preview/admin dashboard, normalized webhook events, an append-only event ledger with Postgres trigger immutability, multi-tenant routing, message streams, RFC 8058 List-Unsubscribe with signed tokens, suppression lists, and webhook-driven auto-suppression.

It is shipped as three sibling packages: mailglass (core), mailglass_admin (mountable LiveView dashboard), and mailglass_inbound (inbound routing; stable 2.0). It is for senior Phoenix teams building production transactional email — welcome flows, password resets, magic links, receipts, notifications — who today rebuild the same 40% of framework plumbing on every project.

Requirements

Demo App

For a realistic local click-around, run the B2B SaaS Ops demo (needs Docker):

make demo

It builds, starts, waits until healthy, then prints the URLs for the dashboard, preview, and outbound/inbound operator journeys (default http://localhost:4015) over seeded data. Stop with make demo-down. Ports are configurable so the demo runs alongside other library demos without collisions — see guides/run-the-demo.md for the full walkthrough.

The demo lives in reference/demo_app; the narrower reference/host_app remains the maintained trust-proof baseline.

Current package compatibility

For the current package line, add the linked core and admin packages together. The declaration below is production-capable, which is required when mounting the production operator surface. Add the independently released inbound package when your app receives mail:

# mix.exs
def deps do
[
{:mailglass, "~> 2.6"},
{:mailglass_admin, "~> 2.6"},
{:mailglass_inbound, "~> 2.3"}
]
end

Preview-only adopters that do not mount the production operator may instead add only: :dev to the mailglass_admin dependency. A dev-scoped dependency is not compiled or available in production.

Fetch deps, run the installer, and migrate:

mix deps.get
mix mailglass.install
mix ecto.migrate

The installer generates: a MyApp.Mailing context, the three-table migration (mailglass_deliveries, mailglass_events, mailglass_suppressions plus the immutability trigger), router mounts for the dev preview and webhook plug, a default mailable and layout, an Oban worker stub (when Oban is installed), and a config/runtime.exs configuration block.

Quickstart

Run the full onboarding path first:

mix deps.get
mix mailglass.install
mix ecto.migrate
mix compile

The installer generates a config block in config/runtime.exs. Confirm your repo and adapter are wired:

# config/runtime.exs
config :mailglass,
repo: MyApp.Repo,
adapter:
{Mailglass.Adapters.Swoosh,
swoosh_adapter: {Swoosh.Adapters.Postmark, api_key: System.fetch_env!("POSTMARK_API_KEY")}},
telemetry: [default_logger: true]

Define a mailable:

defmodule MyApp.UserMailer do
use Mailglass.Mailable, stream: :transactional
def welcome(user) do
new()
|> to(user.email)
|> from({"MyApp", "support@example.com"})
|> subject("Welcome to MyApp")
|> html_body("<h1>Welcome to MyApp</h1>")
|> text_body("Welcome to MyApp")
|> Mailglass.Message.put_function(:welcome)
end
end

Send it — synchronously, asynchronously (via Oban when available), or in a batch:

MyApp.UserMailer.welcome(user) |> Mailglass.deliver()
MyApp.UserMailer.welcome(user) |> Mailglass.deliver_later()
Mailglass.deliver_many(Enum.map(users, &MyApp.UserMailer.welcome/1))

Preview mailables in dev at http://localhost:4000/dev/mail — sidebar of discovered mailables, device width and dark-mode toggles, HTML/Text/Raw/Headers tabs, live-editable assigns.

Deliverability Doctor

Run the DNS-only doctor against one explicit domain at a time:

mix mail.doctor --domain example.com
mix mail.doctor --domain example.com --dkim-selector default --dkim-selector selector2
mix mail.doctor --domain example.com --verbose
mix mail.doctor --domain example.com --format json

mix mail.doctor reports DNS truth and remediation guidance for SPF, DKIM, DMARC, MX, and BIMI. It can return honest cannot_verify outcomes when DNS alone is insufficient, and it does not promise inbox placement certainty or a deliverability grade.

API Stability

The canonical v2.x contract for the core package lives in docs/api_stability.md.

The compatibility guide retains the historical 1.x promises and defines the current v2 deprecation and support-matrix policy in guides/compatibility-and-deprecations.md.

Use that document, not root-module reachability, as the source of truth for:

mailglass_admin has its own narrow contract inventory, and mailglass_inbound has its own stable 2.0 contract inventory in mailglass_inbound/docs/api_stability.md; it remains an independent package release line rather than part of the linked core/admin v2.x group.

For release posture, support floors, retained legacy bridges, and upgrade expectations, use the compatibility guide rather than inferring policy from the stability inventory alone.

Feature highlights

Packages

Package Status What it is
mailglass v2.x contract documented in docs/api_stability.md Core library: mailables, rendering, delivery pipeline, event ledger, webhook ingest, streams, unsubscribe, suppressions, tenancy.
mailglass_admin Narrow v2.x admin contract documented separately Mountable LiveView dashboard with stable router/auth/operator seams and internal UI implementation details.
mailglass_inbound Stable 2.0 contract documented separately Inbound routing (Action Mailbox equivalent): recipient/subject/header matchers, ingress plugs per provider, storage adapters, Oban routing.

Roadmap

Full trajectory in .planning/ROADMAP.md and .planning/PROJECT.md.

Documentation

Contributing

Mailglass is developed in public. Contributor conventions, decision log, and phase-by-phase roadmap live in CLAUDE.md and .planning/PROJECT.md; a dedicated CONTRIBUTING.md lands in Phase 7.

Reproduce the default CI gate locally:

mix verify.foundation
mix verify.cold_start
mix compile --no-optional-deps --warnings-as-errors

License

MIT. The license is declared in mix.exs and applies across all sibling packages.