email fuckery
0d14c0d · gmorell · 2026-05-15 02:51
Files changed
modified
config/runtime.exs
+40
−11
@@ -167,13 +167,25 @@ if config_env() == :prod do
| 167 | 167 | end |
| 168 | 168 | end |
| 169 | 169 | |
| 170 | − # CA bundle for TLS verification. `:public_key.cacerts_get/0` | |
| 171 | − # returns `:undefined` on some OTP/OS combos that haven't been pre- | |
| 172 | − # loaded; pointing `ssl` at the file directly via `cacertfile` is | |
| 173 | − # robust across debian-slim, alpine, etc. Override with | |
| 174 | − # `SMTP_CACERTFILE` if your image puts the bundle elsewhere; set | |
| 175 | − # `SMTP_TLS_VERIFY=verify_none` for self-signed / private relays | |
| 176 | − # where you'd rather skip cert validation. | |
| 170 | + # Eager-load the OS CA bundle into OTP's `public_key` cache. Without | |
| 171 | + # this, `:public_key.cacerts_get/0` returns `:undefined`, and | |
| 172 | + # `gen_smtp`'s internal default `tls_options` injects | |
| 173 | + # `cacerts: :undefined` — which `:ssl` then rejects as incompatible | |
| 174 | + # with `verify: :verify_peer`, clobbering our explicit `cacertfile`. | |
| 175 | + case :public_key.cacerts_load() do | |
| 176 | + :ok -> | |
| 177 | + :ok | |
| 178 | + | |
| 179 | + {:error, reason} -> | |
| 180 | + require Logger | |
| 181 | + Logger.warning("public_key:cacerts_load failed: #{inspect(reason)} — TLS verify may fail") | |
| 182 | + end | |
| 183 | + | |
| 184 | + # CA bundle for TLS verification. We pass both `cacertfile` (path) | |
| 185 | + # and rely on the cacerts_load above for `cacerts_get/0` to work, | |
| 186 | + # belt-and-braces. Override `SMTP_CACERTFILE` if your image puts the | |
| 187 | + # bundle elsewhere; set `SMTP_TLS_VERIFY=verify_none` for self-signed | |
| 188 | + # / private relays where you'd rather skip cert validation. | |
| 177 | 189 | smtp_cacertfile = |
| 178 | 190 | System.get_env("SMTP_CACERTFILE", "/etc/ssl/certs/ca-certificates.crt") |
| 179 | 191 |
@@ -183,6 +195,18 @@ if config_env() == :prod do
| 183 | 195 | other -> raise "SMTP_TLS_VERIFY must be verify_peer or verify_none, got: #{inspect(other)}" |
| 184 | 196 | end |
| 185 | 197 | |
| 198 | + # Resolve the CA store NOW (after `cacerts_load`) rather than letting | |
| 199 | + # the downstream stack call `cacerts_get/0` lazily. gen_smtp / ssl | |
| 200 | + # has a habit of evaluating it in a context where it still returns | |
| 201 | + # `:undefined`, which then collides with our `cacertfile`. Passing | |
| 202 | + # `cacerts: <list>` explicitly is unambiguous — `:ssl` honors it | |
| 203 | + # and never re-derives. | |
| 204 | + smtp_cacerts = | |
| 205 | + case :public_key.cacerts_get() do | |
| 206 | + certs when is_list(certs) and certs != [] -> certs | |
| 207 | + _ -> nil | |
| 208 | + end | |
| 209 | + | |
| 186 | 210 | mailer_opts = |
| 187 | 211 | if relay = System.get_env("SMTP_RELAY") do |
| 188 | 212 | tls_options = |
@@ -191,10 +215,15 @@ if config_env() == :prod do
| 191 | 215 | server_name_indication: String.to_charlist(relay), |
| 192 | 216 | depth: 99 |
| 193 | 217 | ] ++ |
| 194 | − if smtp_verify == :verify_peer do | |
| 195 | − [cacertfile: smtp_cacertfile] | |
| 196 | − else | |
| 197 | − [] | |
| 218 | + cond do | |
| 219 | + smtp_verify != :verify_peer -> | |
| 220 | + [] | |
| 221 | + | |
| 222 | + is_list(smtp_cacerts) -> | |
| 223 | + [cacerts: smtp_cacerts] | |
| 224 | + | |
| 225 | + true -> | |
| 226 | + [cacertfile: smtp_cacertfile] | |
| 198 | 227 | end |
| 199 | 228 | |
| 200 | 229 | [ |
Parents: 4ac908f