PhoenixKit.Mailer.SmtpTransport (phoenix_kit v1.7.205)

Copy Markdown View Source

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

config(creds)

@spec config(map()) :: {:ok, keyword()} | {:error, term()}

Returns {:ok, options} for gen_smtp/Swoosh.Adapters.SMTP, or {:error, reason}.

Reasons: {:invalid_smtp_port, term}, :no_ca_store.

config(creds, cacerts)

@spec config(map(), [binary()] | [tuple()]) :: {:ok, keyword()} | {:error, term()}

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.