Builds the gen_smtp/Swoosh connection options for an SMTP integration.
Extracted so that sending and "Test Connection" are driven by literally the
same options — a check that connects differently from the sender is a check
that can lie in either direction. It is a pure function of the credentials map
and depends on nothing else in the tree (in particular not on
PhoenixKit.Integrations, which would otherwise close a
Integrations → Validators → Mailer → Integrations cycle).
TLS
gen_smtp supplies no TLS options of its own, and OTP's :ssl now defaults
to verify: :verify_peer with no CA store. Left alone, that means:
- implicit TLS (465,
ssl: true) dies on connect with{:options, :incompatible, [verify: :verify_peer, cacerts: :undefined]}; - STARTTLS (
tls: :always) fails the handshake with:tls_failed.
So the options below are load-bearing, not decoration. They ride on sockopts
for implicit TLS (gen_smtp hands those straight to :ssl.connect/4) and on
tls_options for STARTTLS.
Note that passing tls_options replaces gen_smtp's default
[{versions, ['tlsv1', 'tlsv1.1', 'tlsv1.2']}] wholesale (it merges with
lists:ukeymerge/3). That is deliberate: we take OTP's defaults, which drop
the long-dead TLS 1.0/1.1 and allow TLS 1.3.
Certificate verification is not optional when credentials are on the wire
If no CA store can be found we refuse to build a config for a relay that
expects a password ({:error, :no_ca_store}) rather than silently falling back
to verify: :verify_none — an unauthenticated TLS peer can present any
certificate, terminate the connection and harvest the AUTH exchange. A relay
that takes no credentials has nothing to protect, so it degrades instead.
Summary
Functions
Returns {:ok, options} for gen_smtp/Swoosh.Adapters.SMTP, or {:error, reason}.
Same as config/1, with the trusted CA store supplied explicitly.
Functions
Returns {:ok, options} for gen_smtp/Swoosh.Adapters.SMTP, or {:error, reason}.
Reasons: {:invalid_smtp_port, term}, :no_ca_store.
Same as config/1, with the trusted CA store supplied explicitly.
The options are a pure function of the credentials and the CA store; config/1
simply reads the store from the system. Passing it in makes the fail-closed
branch — no CA store, credentials on the wire — reachable from a test.