Test assertions extending Swoosh.TestAssertions (TEST-01, D-05).
Lives in lib/ (not test/support/) because it's exported for
adopter consumption. Adopters import Mailglass.TestAssertions in
their test helpers or use Mailglass.MailerCase which imports it.
Matcher styles
# 1. Presence (bare call)
assert_mail_sent()
# 2. Keyword match (Swoosh familiarity)
assert_mail_sent(subject: "Welcome", to: "user@example.com")
# 3. Struct pattern (macro — no explicit quoting)
assert_mail_sent(%{mailable: MyApp.UserMailer})
# 4. Predicate fn
assert_mail_sent(fn msg -> msg.stream == :transactional end)Supported keyword matcher keys
:subject, :to, :mailable, :stream, :tenant — extensible in
future versions. Any other key raises ExUnit.AssertionError.
Process-local + PubSub-backed assertions
last_mail/0reads Fake-backed delivery storage without consuming the process mailbox.wait_for_mail/1,assert_no_mail_sent/0, andassert_mail_sent/0,1read the current process mailbox (Mailglass.Adapters.Fake.Storagesends{:mail, %Message{}}to the owner viasend/2on every delivery).assert_mail_delivered/2,assert_mail_bounced/2— consume PubSub broadcasts fromMailglass.Outbound.Projector.broadcast_delivery_updated/3. Use when asserting webhook-received events (Phase 4) or Fake-triggered events (Mailglass.Adapters.Fake.trigger_event/3).
Async-safe
Process mailbox + ETS-per-owner (Fake) + PubSub subscription (all
cleaned up on process exit) means async: true tests see only their
own mail. Use Mailglass.MailerCase for the canonical setup.
PII policy (T-3-06-01)
Failure messages embed caller-supplied values (e.g. subject or to
address) because adopter test failures need that context. These values
appear only in the adopter's own test output — not in telemetry,
log streams, or cross-tenant surfaces.
Summary
Functions
Blocks until a {:delivery_updated, _, :bounced, _} broadcast arrives.
See assert_mail_delivered/2 for usage notes.
Blocks until a {:delivery_updated, _, :delivered, _} broadcast
arrives for the given delivery id (or %Delivery{}). Flunks on timeout.
Asserts that the HTML body of a message matches a binary or regex pattern.
Asserts that at least one mail was sent in the current test process.
Asserts that the text body of a message matches a binary or regex pattern.
Asserts that no mail was sent in the current test process.
Returns the most recent %Mailglass.Message{} sent from the current
owner's Fake ETS bucket; or nil if none.
Blocks until a {:mail, %Message{}} arrives or timeout elapses. Returns the message.
Flunks on timeout with a descriptive failure message.
Functions
@spec assert_mail_bounced(Mailglass.Outbound.Delivery.t() | binary(), timeout()) :: :ok
Blocks until a {:delivery_updated, _, :bounced, _} broadcast arrives.
See assert_mail_delivered/2 for usage notes.
@spec assert_mail_delivered(Mailglass.Outbound.Delivery.t() | binary(), timeout()) :: :ok
Blocks until a {:delivery_updated, _, :delivered, _} broadcast
arrives for the given delivery id (or %Delivery{}). Flunks on timeout.
The current test process MUST be subscribed to either
Mailglass.PubSub.Topics.events(tenant_id) or
Mailglass.PubSub.Topics.events(tenant_id, delivery_id) before
this assertion runs. Mailglass.MailerCase handles the tenant-wide
subscription in setup.
Accepts
delivery_id :: binary()— the UUID stringdelivery :: %Mailglass.Outbound.Delivery{}— the struct (.idextracted)
@spec assert_mail_html_matches(Mailglass.Message.t(), String.t() | Regex.t()) :: :ok
Asserts that the HTML body of a message matches a binary or regex pattern.
Asserts that at least one mail was sent in the current test process.
Four matcher styles supported; see module doc.
Style 1: presence (bare call)
assert_mail_sent()Style 2: keyword list
assert_mail_sent(subject: "Welcome", to: "user@example.com")
assert_mail_sent(mailable: MyApp.UserMailer, stream: :transactional)Supported keys: :subject, :to, :mailable, :stream, :tenant
Style 3: struct pattern (no quoting needed)
assert_mail_sent(%{mailable: MyApp.UserMailer})Style 4: predicate function
assert_mail_sent(fn msg -> msg.stream == :transactional end)
@spec assert_mail_text_matches(Mailglass.Message.t(), String.t() | Regex.t()) :: :ok
Asserts that the text body of a message matches a binary or regex pattern.
Asserts that no mail was sent in the current test process.
Reads the current process mailbox. Flunks if any {:mail, _} message
is present.
@spec last_mail() :: Mailglass.Message.t() | nil
Returns the most recent %Mailglass.Message{} sent from the current
owner's Fake ETS bucket; or nil if none.
Reads directly from the Fake ETS table — no process mailbox consumed.
The returned message is still present in the mailbox for subsequent
assert_mail_sent/0,1 calls.
@spec wait_for_mail(timeout()) :: Mailglass.Message.t()
Blocks until a {:mail, %Message{}} arrives or timeout elapses. Returns the message.
Flunks on timeout with a descriptive failure message.
Unlike assert_mail_sent/0 (which uses assert_received and checks
the mailbox synchronously), wait_for_mail/1 blocks the test for up
to timeout milliseconds — useful when the delivery may arrive
slightly after the assertion site.