Gate UniSender unsubscribe override by configuration
Some checks are pending
Quality / full-local-gates (push) Waiting to run
Some checks are pending
Quality / full-local-gates (push) Waiting to run
This commit is contained in:
parent
8baa9abc13
commit
d1d64afe2b
|
|
@ -140,6 +140,8 @@ SMTP_TLS=never
|
|||
SMTP_SSL=false
|
||||
UNISENDER_GO_API_KEY=
|
||||
UNISENDER_GO_BASE_URL=https://goapi.unisender.ru/ru/transactional/api/v1
|
||||
# Keep false until UniSender Go support enables this account-level capability.
|
||||
UNISENDER_GO_SKIP_UNSUBSCRIBE=false
|
||||
# Optional. When empty, the Req/Finch library defaults are used.
|
||||
EMAIL_HTTP_CONNECT_TIMEOUT_MS=
|
||||
EMAIL_HTTP_RECEIVE_TIMEOUT_MS=
|
||||
|
|
|
|||
|
|
@ -500,6 +500,18 @@ if config_env() == :prod do
|
|||
raise "UNISENDER_GO_BASE_URL must be an HTTPS origin and path without credentials, query, or fragment."
|
||||
end
|
||||
|
||||
skip_unsubscribe =
|
||||
case System.get_env("UNISENDER_GO_SKIP_UNSUBSCRIBE", "false") do
|
||||
"true" ->
|
||||
true
|
||||
|
||||
"false" ->
|
||||
false
|
||||
|
||||
other ->
|
||||
raise "UNISENDER_GO_SKIP_UNSUBSCRIBE must be true or false; got #{inspect(other)}"
|
||||
end
|
||||
|
||||
client_options =
|
||||
[]
|
||||
|> then(fn options ->
|
||||
|
|
@ -519,6 +531,7 @@ if config_env() == :prod do
|
|||
adapter: WhoNeedHelp.Email.UnisenderGoAdapter,
|
||||
api_key: api_key,
|
||||
base_url: base_url,
|
||||
skip_unsubscribe: skip_unsubscribe,
|
||||
client_options: client_options
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -388,8 +388,14 @@ uses only HTTPS. The provider documents the endpoint, `X-API-KEY` header,
|
|||
response shape, and one-minute/64-character idempotence-key behavior in its
|
||||
[Web API reference](https://godocs.unisender.ru/web-api-ref).
|
||||
Every request explicitly sets `track_links` and `track_read` to `0`; UniSender
|
||||
Go otherwise enables both by default. The provider account must be allowed to
|
||||
honour those values, so verify a delivered message's raw HTML before launch.
|
||||
Go otherwise enables both by default and may require account-level approval
|
||||
before it honors those values.
|
||||
`UNISENDER_GO_SKIP_UNSUBSCRIBE` defaults to `false` and must remain disabled
|
||||
until UniSender Go support explicitly enables that transactional capability for
|
||||
the account. After approval, set it to `true` to add `skip_unsubscribe=1` to the
|
||||
provider request.
|
||||
Verify a delivered message's raw HTML after the provider confirms the account
|
||||
change and before launch.
|
||||
|
||||
## Isolated restore drill
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ defmodule WhoNeedHelp.Email.UnisenderGoAdapter do
|
|||
def validate_config(config) do
|
||||
validate_api_key!(Keyword.get(config, :api_key))
|
||||
validate_base_url!(Keyword.get(config, :base_url, @default_base_url))
|
||||
validate_skip_unsubscribe!(Keyword.get(config, :skip_unsubscribe, false))
|
||||
:ok
|
||||
end
|
||||
|
||||
|
|
@ -32,7 +33,7 @@ defmodule WhoNeedHelp.Email.UnisenderGoAdapter do
|
|||
@impl true
|
||||
def deliver(%Email{} = email, config) do
|
||||
with :ok <- validate_supported_email(email),
|
||||
{:ok, body} <- encode_payload(email),
|
||||
{:ok, body} <- encode_payload(email, config),
|
||||
{:ok, status, _headers, response_body} <-
|
||||
Swoosh.ApiClient.post(
|
||||
endpoint(config),
|
||||
|
|
@ -83,7 +84,7 @@ defmodule WhoNeedHelp.Email.UnisenderGoAdapter do
|
|||
|
||||
defp validate_supported_email(%Email{}), do: :ok
|
||||
|
||||
defp encode_payload(%Email{} = email) do
|
||||
defp encode_payload(%Email{} = email, config) do
|
||||
payload =
|
||||
%{
|
||||
"message" =>
|
||||
|
|
@ -97,6 +98,7 @@ defmodule WhoNeedHelp.Email.UnisenderGoAdapter do
|
|||
"track_read" => 0,
|
||||
"idempotence_key" => idempotence_key(email)
|
||||
}
|
||||
|> maybe_put("skip_unsubscribe", 1, Keyword.get(config, :skip_unsubscribe, false))
|
||||
|> maybe_put_reply_to(email.reply_to)
|
||||
|> maybe_put_headers(email.headers)
|
||||
}
|
||||
|
|
@ -227,4 +229,10 @@ defmodule WhoNeedHelp.Email.UnisenderGoAdapter do
|
|||
defp validate_base_url!(_base_url) do
|
||||
raise ArgumentError, "UniSender Go base URL must be an HTTPS URL without credentials"
|
||||
end
|
||||
|
||||
defp validate_skip_unsubscribe!(value) when is_boolean(value), do: :ok
|
||||
|
||||
defp validate_skip_unsubscribe!(_value) do
|
||||
raise ArgumentError, "UniSender Go skip_unsubscribe must be a boolean"
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -125,6 +125,7 @@ smtp_tls=${PRODUCTION_SMTP_TLS:-always}
|
|||
smtp_ssl=${PRODUCTION_SMTP_SSL:-false}
|
||||
unisender_go_api_key=${PRODUCTION_UNISENDER_GO_API_KEY:-}
|
||||
unisender_go_base_url=${PRODUCTION_UNISENDER_GO_BASE_URL:-https://goapi.unisender.ru/ru/transactional/api/v1}
|
||||
unisender_go_skip_unsubscribe=${PRODUCTION_UNISENDER_GO_SKIP_UNSUBSCRIBE:-false}
|
||||
email_http_connect_timeout_ms=${PRODUCTION_EMAIL_HTTP_CONNECT_TIMEOUT_MS:-}
|
||||
email_http_receive_timeout_ms=${PRODUCTION_EMAIL_HTTP_RECEIVE_TIMEOUT_MS:-}
|
||||
email_from_address=${PRODUCTION_EMAIL_FROM_ADDRESS:-"contact@$domain"}
|
||||
|
|
@ -144,6 +145,11 @@ case "$email_delivery_provider" in
|
|||
;;
|
||||
esac
|
||||
|
||||
case "$unisender_go_skip_unsubscribe" in
|
||||
true | false) ;;
|
||||
*) echo "PRODUCTION_UNISENDER_GO_SKIP_UNSUBSCRIBE must be true or false." >&2; exit 1 ;;
|
||||
esac
|
||||
|
||||
tmp=$(mktemp "$target_dir/.production-env.XXXXXX")
|
||||
trap 'rm -f "$tmp"' EXIT HUP INT TERM
|
||||
chmod 600 "$tmp"
|
||||
|
|
@ -176,6 +182,7 @@ SMTP_TLS_VALUE=$smtp_tls \
|
|||
SMTP_SSL_VALUE=$smtp_ssl \
|
||||
UNISENDER_GO_API_KEY_VALUE=$unisender_go_api_key \
|
||||
UNISENDER_GO_BASE_URL_VALUE=$unisender_go_base_url \
|
||||
UNISENDER_GO_SKIP_UNSUBSCRIBE_VALUE=$unisender_go_skip_unsubscribe \
|
||||
EMAIL_HTTP_CONNECT_TIMEOUT_MS_VALUE=$email_http_connect_timeout_ms \
|
||||
EMAIL_HTTP_RECEIVE_TIMEOUT_MS_VALUE=$email_http_receive_timeout_ms \
|
||||
EMAIL_FROM_ADDRESS_VALUE=$email_from_address \
|
||||
|
|
@ -219,6 +226,7 @@ CODEX_SESSION_ID_VALUE=$codex_session_id \
|
|||
replacement["SMTP_SSL"] = ENVIRON["SMTP_SSL_VALUE"]
|
||||
replacement["UNISENDER_GO_API_KEY"] = ENVIRON["UNISENDER_GO_API_KEY_VALUE"]
|
||||
replacement["UNISENDER_GO_BASE_URL"] = ENVIRON["UNISENDER_GO_BASE_URL_VALUE"]
|
||||
replacement["UNISENDER_GO_SKIP_UNSUBSCRIBE"] = ENVIRON["UNISENDER_GO_SKIP_UNSUBSCRIBE_VALUE"]
|
||||
replacement["EMAIL_HTTP_CONNECT_TIMEOUT_MS"] = ENVIRON["EMAIL_HTTP_CONNECT_TIMEOUT_MS_VALUE"]
|
||||
replacement["EMAIL_HTTP_RECEIVE_TIMEOUT_MS"] = ENVIRON["EMAIL_HTTP_RECEIVE_TIMEOUT_MS_VALUE"]
|
||||
replacement["EMAIL_FROM_ADDRESS"] = ENVIRON["EMAIL_FROM_ADDRESS_VALUE"]
|
||||
|
|
|
|||
|
|
@ -103,6 +103,7 @@ smtp_tls=$(optional_value SMTP_TLS)
|
|||
smtp_ssl=$(optional_value SMTP_SSL)
|
||||
unisender_go_api_key=$(optional_value UNISENDER_GO_API_KEY)
|
||||
unisender_go_base_url=$(optional_value UNISENDER_GO_BASE_URL)
|
||||
unisender_go_skip_unsubscribe=$(optional_value UNISENDER_GO_SKIP_UNSUBSCRIBE)
|
||||
email_http_connect_timeout_ms=$(optional_value EMAIL_HTTP_CONNECT_TIMEOUT_MS)
|
||||
email_http_receive_timeout_ms=$(optional_value EMAIL_HTTP_RECEIVE_TIMEOUT_MS)
|
||||
email_from_address=$(require_value EMAIL_FROM_ADDRESS)
|
||||
|
|
@ -275,6 +276,10 @@ case "$email_delivery_provider" in
|
|||
exit 1
|
||||
}
|
||||
reject_marker UNISENDER_GO_BASE_URL "$unisender_go_base_url"
|
||||
[[ "$unisender_go_skip_unsubscribe" =~ ^(true|false)$ ]] || {
|
||||
echo "UNISENDER_GO_SKIP_UNSUBSCRIBE must be true or false." >&2
|
||||
exit 1
|
||||
}
|
||||
;;
|
||||
*)
|
||||
echo "EMAIL_DELIVERY_PROVIDER must be smtp or unisender_go." >&2
|
||||
|
|
|
|||
|
|
@ -99,6 +99,33 @@ defmodule WhoNeedHelp.Email.UnisenderGoAdapterTest do
|
|||
UnisenderGoAdapter.deliver(email(plug), api_key: "secret-api-key")
|
||||
end
|
||||
|
||||
test "adds skip_unsubscribe only after the provider capability is explicitly enabled" do
|
||||
owner = self()
|
||||
|
||||
plug = fn conn ->
|
||||
{:ok, request_body, conn} = Plug.Conn.read_body(conn)
|
||||
send(owner, {:request, Jason.decode!(request_body)})
|
||||
|
||||
Plug.Conn.send_resp(
|
||||
conn,
|
||||
200,
|
||||
Jason.encode!(%{
|
||||
"status" => "success",
|
||||
"job_id" => "job-123",
|
||||
"emails" => ["helper@example.com"]
|
||||
})
|
||||
)
|
||||
end
|
||||
|
||||
assert {:ok, _response} =
|
||||
UnisenderGoAdapter.deliver(email(plug),
|
||||
api_key: "secret-api-key",
|
||||
skip_unsubscribe: true
|
||||
)
|
||||
|
||||
assert_receive {:request, %{"message" => %{"skip_unsubscribe" => 1}}}
|
||||
end
|
||||
|
||||
test "returns structured API and invalid-response failures" do
|
||||
error_plug = fn conn ->
|
||||
Plug.Conn.send_resp(
|
||||
|
|
@ -174,6 +201,13 @@ defmodule WhoNeedHelp.Email.UnisenderGoAdapterTest do
|
|||
base_url: "http://secret@example.test/api?key=secret"
|
||||
)
|
||||
end
|
||||
|
||||
assert_raise ArgumentError, "UniSender Go skip_unsubscribe must be a boolean", fn ->
|
||||
UnisenderGoAdapter.validate_config(
|
||||
api_key: "secret-api-key",
|
||||
skip_unsubscribe: "true"
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
defp email(plug) do
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user