Skip to content

szTheory/mailglass

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

664 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

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; v0.5+). 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

  • Elixir ~> 1.18 and OTP 27+
  • Phoenix ~> 1.8
  • Phoenix LiveView ~> 1.1
  • Ecto / Ecto SQL ~> 3.13
  • PostgreSQL 14+ (trigger support required; citext used for case-insensitive address match)
  • Swoosh ~> 1.25 (compose any Swoosh adapter for transport)

Installation

Add mailglass to your dependencies:

# mix.exs
def deps do
  [
    {:mailglass, "~> 0.3"},
    {:mailglass_admin, "~> 0.3", 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.

  • --domain is required, and each run checks exactly one domain.
  • --dkim-selector is repeatable so you can name the selectors your mail stream actually uses.
  • --verbose includes supporting evidence inline.
  • --format json emits the shared machine-readable result shape with schema_version: 1.

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:

  • which Mailglass modules, behaviours, Mix tasks, telemetry families, structs, and documented fields are stable
  • which exported surfaces are intentionally internal
  • which hooks exist only for first-party sibling-package integration

mailglass_admin has its own narrow contract inventory, and mailglass_inbound is outside the v1.x stability promise for this milestone.

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

  • HEEx-native components (container, section, row, column, heading, text, button, img, link, hr, preheader) with MSO VML fallbacks for Outlook. No Node toolchain.
  • Pure render pipeline — HEEx → Premailex CSS inlining → data-mg-* strip → auto-plaintext via Floki walker. ~4ms on a ten-component template.
  • Append-only event ledgermailglass_events table protected by a Postgres trigger that raises SQLSTATE 45A01 on UPDATE/DELETE.
  • Native mailable settersMailglass.Message.to/2, from/2, subject/2, html_body/2, text_body/2, header/3, attach/2, and put_tag/2 keep the common path free of direct Swoosh.Email.* calls while update_swoosh/2 remains the escape hatch.
  • Stream-aware deliverability:transactional, :operational, and :bulk are enforced message streams. RFC 8058 one-click unsubscribe headers are injected automatically for :bulk and can be opted into on :operational.
  • Idempotency — partial UNIQUE index on idempotency_key WHERE idempotency_key IS NOT NULL; replay-safe webhooks and delivery retries.
  • Multi-tenant from day onetenant_id on every record, Mailglass.Tenancy behaviour, SingleTenant default resolver, runtime per-tenant adapter resolution through tenancy callbacks plus named adapter_ref routes, and an Oban tenancy middleware (conditionally compiled).
  • Fake adapter as the release gate — deterministic, in-memory, time-advanceable; merge-blocking in CI so the full pipeline is testable without real provider credentials.
  • Swoosh as transport — compose on any Swoosh adapter (Postmark, SendGrid, Mailgun, SES, Resend, local SMTP, etc.).
  • Normalized webhook events — Anymail event taxonomy verbatim (queued, sent, bounced, delivered, opened, clicked, complained, unsubscribed, …) with reject_reason enum. Postmark, SendGrid, Mailgun, SES, and Resend are all shipped first-party providers, and matched :bounced, :complained, and :unsubscribed events project suppressions automatically.
  • Test assertionsassert_mail_sent/1, last_mail/0, wait_for_mail/1, plus MailerCase, WebhookCase, AdminCase templates.
  • Telemetry spans on every entry point with a PII whitelist (counts, IDs, and latencies — never addresses or bodies).
  • Optional deps gated via Mailglass.OptionalDeps.*: oban, opentelemetry, mjml, gen_smtp, sigra.

Packages

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

Roadmap

  • v0.2 — Production-credible core — native Mailglass.Message setter API, mix mailglass.upgrade.v0_2, message-stream policy, RFC 8058 unsubscribe, webhook-driven suppression projection, linked release hardening, and release-blocking Tier 1 docs.
  • v0.5 — Deliverability + admin — prod-mountable admin, mix mail.doctor deliverability checks, per-tenant adapter resolver, per-domain rate limiting.
  • v1.0 — API stability lock, production references, long-lived deprecation policy.

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.