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/0,wait_for_mail/1,assert_no_mail_sent/0— read the current process's 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 at least one mail was sent in the current test process.
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 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)
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)
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 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.