Pre-send guardrails: suppression enforcement and the complaint-rate auto-pause.
Every send SquatchMail observes or proxies should be checked against
check/1 first. Two independent conditions can block a send:
- Suppression - any recipient address with an active suppression
row (hard bounce, soft bounce not yet expired, complaint, or manual).
See
SquatchMail.Tracker.suppressed_addresses/1/list_suppressions/1for how "active" is defined (expires_at IS NULL OR expires_at > now()). - Complaint-rate auto-pause - if the fraction of sent emails
complained about, over the trailing
SquatchMail.Config.complaint_rate_window_days/0days, is at or aboveSquatchMail.Config.complaint_rate_threshold/0(0.1%by default — SES's own account-suspension threshold), sending is paused entirely regardless of recipient. BelowSquatchMail.Config.complaint_rate_min_volume/0sent emails in the window the breaker never trips, even at 100% complaints, so a handful of early sends can't produce a false positive (1 complaint out of 5 sends is not a 20% complaint rate in any meaningful sense). Disable the breaker entirely withcomplaint_rate_pause: false.
This module never sends anything itself, nor does it touch the network —
it only reads from SquatchMail.Tracker. See SquatchMail.Adapters.Watchtower
for a Swoosh adapter that enforces this automatically ahead of a wrapped
real adapter, and resend/2 for re-sending a previously-captured email
through the host's own mailer.
Performance note
check/1 runs one query for suppressions (Tracker.suppressed_addresses/1,
WHERE address IN (...) AND (expires_at IS NULL OR expires_at > now())
— a single round trip regardless of recipient count) and, when the
breaker is enabled, one aggregate query for the complaint rate — every
call, no caching. For a proxy adapter sitting in the hot send path this
means two extra round trips per send (one, if the address list is empty
and the rate check is disabled). That's an accepted cost for correctness
today; if it shows up in profiling, the complaint rate is the obvious
candidate to cache (e.g. recomputed on a timer rather than per-send)
since it only needs to change a few times a day, not per-request.
Summary
Functions
Checks recipients against SquatchMail's guardrails ahead of a send.
Returns the current trailing-window complaint rate (a fraction,
0.0..1.0), regardless of whether it exceeds the configured threshold
or how many emails have been sent. Useful for a dashboard health panel.
Returns true if sending is currently auto-paused due to the complaint
rate exceeding its threshold at or above the configured minimum volume.
Re-sends a previously-captured email through the host's own configured Swoosh mailer.
Types
Functions
@spec check(recipients_input()) :: :ok | {:error, block_reason()}
Checks recipients against SquatchMail's guardrails ahead of a send.
Accepts a %Swoosh.Email{} (its :to/:cc/:bcc addresses are
extracted), a single address, or a list of addresses.
Returns :ok if the send should proceed, or {:error, reason} if it
should be blocked:
{:error, {:suppressed, addresses}}- one or more recipients are actively suppressed;addresseslists every suppressed recipient found, not just the first.{:error, :complaint_rate_paused}- the account-wide complaint rate is at or above threshold; sending is paused regardless of recipient.
The complaint-rate check runs first — it's a single aggregate query regardless of recipient count and blocks the entire send outright before bothering to look at individual addresses.
@spec complaint_rate() :: float()
Returns the current trailing-window complaint rate (a fraction,
0.0..1.0), regardless of whether it exceeds the configured threshold
or how many emails have been sent. Useful for a dashboard health panel.
Returns 0.0 when there have been no sends in the window (nothing to
divide by, and nothing to complain about).
@spec paused?() :: boolean()
Returns true if sending is currently auto-paused due to the complaint
rate exceeding its threshold at or above the configured minimum volume.
Always false when complaint_rate_pause: false is configured.
@spec resend(SquatchMail.Email.t(), module()) :: {:ok, term()} | {:error, block_reason() | term()}
Re-sends a previously-captured email through the host's own configured Swoosh mailer.
mailer is the host's Swoosh.Mailer module (the same one the original
send went through, or any other). Builds a fresh %Swoosh.Email{} from
the stored subject/bodies/recipients and delivers it — this is a genuine
new send (and will itself be captured again if telemetry capture is
enabled), not a replay of the original SES request. Runs through
check/1 first, same as any other send, so resending to a suppressed
address or while paused is rejected the same way.