Mailglass

Mail you can see through.

CIHex.pmHexDocsLicense

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 1.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:

docker compose -f compose.demo.yml up demo

Open http://localhost:4015 to inspect seeded preview, outbound operator, and inbound operator journeys. The demo lives in reference/demo_app; the narrower reference/host_app remains the maintained trust-proof baseline.

Installation

Add mailglass to your dependencies:

# mix.exs
def deps do
[
{:mailglass, "~> 1.4"},
{:mailglass_admin, "~> 1.4", only: [:dev]}
]
end

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

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 v1.x contract inventory for the core package lives in docs/api_stability.md.

The canonical 1.x compatibility, deprecation, and support-matrix policy lives 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 1.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 v1.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

PackageStatusWhat it is
mailglassv1.x contract inventory documented in docs/api_stability.mdCore library: mailables, rendering, delivery pipeline, event ledger, webhook ingest, streams, unsubscribe, suppressions, tenancy.
mailglass_adminNarrow v1.x admin contract documented separatelyMountable LiveView dashboard with stable router/auth/operator seams and internal UI implementation details.
mailglass_inboundStable 1.0 contract documented separatelyInbound 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.