Observes every Swoosh.Mailer.deliver/2 and deliver_many/2 call in the
host application via :telemetry, with zero changes to the host's mailer
or adapter.
Swoosh.Mailer wraps each send in :telemetry.span/3, emitting
[:swoosh, :deliver | :deliver_many, :start | :stop | :exception]. This
module attaches to :stop and :exception only — never :start, since
there's nothing to persist until a send has actually finished — and
records the outcome through SquatchMail.Capture.Recorder.
Design constraints
Telemetry handlers run synchronously in the caller's process — the
same process that just called Mailer.deliver/2. This module therefore:
- never raises: every step is wrapped and logged on failure rather than propagated, so a SquatchMail bug can never break the host's mail sending;
- never blocks the caller: the handler only builds attrs and hands them
to
SquatchMail.Capture.Recorder.record/1(aGenServer.cast/2), returning immediately regardless of how long the actual database write takes, or whether the queue is full and the attrs get dropped.
Adapter result normalization
The result telemetry metadata is adapter-specific. This module
recognizes:
Swoosh.Adapters.AmazonSESandSwoosh.Adapters.ExAwsAmazonSES(which delegates straight toAmazonSES, producing the same shape):%{id: message_id, request_id: request_id}— atom-keyed.- Raw
AWS.SESv2.send_email/3responses (for hosts using a custom adapter built directly on theawspackage):%{"MessageId" => message_id}— string-keyed, PascalCase, straight off the wire.
Any other adapter's result is still recorded — just without a
message_id, which means SES event ingestion won't be able to correlate
delivery/bounce events back to it later. Unrecognized shapes are never
treated as an error.
Configuration
See SquatchMail.Config for :enabled and the nested :capture options
(:store_html, :store_text, :sample_rate, :max_queue).
Summary
Functions
@spec attach() :: :ok
Attaches the capture handler.
No-ops (logging nothing, doing nothing) when Swoosh isn't loaded — it's an optional dependency, and a host observing nothing through it is a valid configuration, not an error. Safe to call more than once: re-attaching detaches the previous handler first rather than erroring or double-firing.
@spec detach() :: :ok
Detaches the capture handler.