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).
Operator settings
Everything below is derived from the smtp provider's setup fields. All of
them are optional; left blank, the transport behaves exactly as it did when
the port was the only signal:
security—auto(default) |ssl|starttls|starttls_optional|none.autoreproduces the historical rule: port 465 means implicit TLS, anything else means STARTTLS — required when credentials are present, opportunistic when they are not.verify_cert—verify_peer(default) |verify_none.ca_cert— PEM bundle for a private/self-signed CA. Replaces the system store for this connection.auth—if_available(default) |always|never. Carried in the built options, so the Test Connection probe reads the operator's choice instead of guessing (it still upgradesif_available→alwaysfor the probe only — seePhoenixKit.Integrations.Validators.smtp/1— because a tolerated AUTH failure would let a wrong password pass the check).timeout— seconds; maps to gen_smtp's:timeout.
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.
An operator who chooses verify_none (or security: none) gets what they
asked for: the point of those settings is the internal relay with a
self-signed certificate, or none at all. They are never reached by auto.
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}, {:invalid_security, term},
{:invalid_verify_cert, term}, {:invalid_auth, term},
{:invalid_timeout, term}, :invalid_ca_cert, :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.