defmodule Mv.Smtp.ConfigBuilder do @moduledoc """ Builds Swoosh/gen_smtp SMTP adapter options from connection parameters. Single source of truth for TLS/sockopts logic (port 587 vs 465): - Port 587 (STARTTLS): `gen_tcp` is used first; `sockopts` must NOT contain `:verify`. - Port 465 (implicit SSL): initial connection is `ssl:connect`; `sockopts` must contain `:verify`. Used by `config/runtime.exs` (boot-time ENV) and `Mv.Mailer.smtp_config/0` (Settings-only). """ @doc """ Builds the keyword list of Swoosh SMTP adapter options. Options (keyword list): - `:host` (required) — relay hostname - `:port` (required) — port number (e.g. 587 or 465) - `:ssl_mode` (required) — `"tls"` or `"ssl"` - `:verify_mode` (required) — `:verify_peer` or `:verify_none` - `:username` (optional) - `:password` (optional) Nil values are stripped from the result. """ @spec build_opts(keyword()) :: keyword() def build_opts(opts) do host = Keyword.fetch!(opts, :host) port = Keyword.fetch!(opts, :port) username = Keyword.get(opts, :username) password = Keyword.get(opts, :password) ssl_mode = Keyword.fetch!(opts, :ssl_mode) verify_mode = Keyword.fetch!(opts, :verify_mode) base_opts = [ adapter: Swoosh.Adapters.SMTP, relay: host, port: port, username: username, password: password, ssl: ssl_mode == "ssl", tls: if(ssl_mode == "tls", do: :always, else: :never), auth: :always, # tls_options: used for STARTTLS (587). For 465, gen_smtp uses sockopts for initial ssl:connect. tls_options: [verify: verify_mode] ] # Port 465: initial connection is ssl:connect; pass verify in sockopts. # Port 587: initial connection is gen_tcp; sockopts must NOT contain verify (gen_tcp rejects it). opts = if ssl_mode == "ssl" do Keyword.put(base_opts, :sockopts, verify: verify_mode) else base_opts end Enum.reject(opts, fn {_k, v} -> is_nil(v) end) end end