SquatchMail.Adapters.Watchtower (SquatchMail v0.1.0)

Copy Markdown View Source

An opt-in Swoosh adapter that enforces SquatchMail.Guard's guardrails ahead of a wrapped real adapter.

This is the LaraSend-parity "proxy mode" from RESEARCH.md, applied to guardrails specifically: instead of only observing sends via telemetry (see SquatchMail.Capture), Watchtower sits directly in the send path so a suppressed recipient or an auto-paused account can actually block the send rather than just be recorded after the fact.

This is entirely optional — most hosts should prefer SquatchMail.Capture (pure observation, zero risk of ever blocking a legitimate send by mistake) and only reach for Watchtower if they want suppressions and the complaint-rate auto-pause enforced before SES ever sees the request, rather than after the fact via SquatchMail.Guard.check/1 called manually.

Usage

Configure a mailer to use Watchtower with the real ("watched") adapter and its own config flattened alongside it, under :watched_adapter:

config :my_app, MyApp.Mailer,
  adapter: SquatchMail.Adapters.Watchtower,
  watched_adapter: Swoosh.Adapters.AmazonSES,
  region: "us-east-1",
  access_key: System.fetch_env!("AWS_ACCESS_KEY_ID"),
  secret: System.fetch_env!("AWS_SECRET_ACCESS_KEY")

Watchtower itself declares no required_config of its own beyond :watched_adapter — everything else in config is passed straight through to the watched adapter untouched, including its own required keys, which are validated in validate_config/1 by delegating to the watched adapter (with :watched_adapter stripped out first).

All-or-nothing batches

deliver_many/2 checks every recipient across the entire batch before sending any of it. If any recipient anywhere in the batch is suppressed (or the account is paused), the whole call returns {:error, {:suppressed, addresses}} (or {:error, :complaint_rate_paused}) and none of the emails are sent — not even the ones with clean recipients. This is a deliberate simplicity/safety trade-off: partial sends would mean silently reordering or splitting a batch the caller asked to send atomically, and would require inventing a per-email result shape for a mixed outcome. A caller that wants partial delivery on suppression should call deliver/2 per email instead.

Capture interplay

A blocked send still returns {:error, reason} from deliver/2, which means Swoosh.Mailer still emits its [:swoosh, :deliver, :stop] telemetry (with that error in metadata) exactly as it would for any other adapter failure — so SquatchMail.Capture still records the attempt. See SquatchMail.Capture for how {:suppressed, _} is recorded with status "suppressed" rather than "failed".

Summary

Functions

Validates the watched adapter's own config.

Functions

validate_config(config)

Validates the watched adapter's own config.

Requires :watched_adapter to be present, then delegates to that adapter's validate_config/1 with :watched_adapter stripped from the config (so the watched adapter only ever sees keys it understands). Adapters that don't export validate_config/1 (not part of the Swoosh.Adapter behaviour's required callbacks in every version) are treated as passing validation.