aprs.me/lib/aprsme/swoosh/resend_adapter.ex
Graham McIntire a377847122
chore: remove unused deps, replace resend with custom Swoosh adapter, fix test isolation
Remove faker, floki, igniter, ecto_psql_extras (zero usages), exvcr
(zero usages), and resend. Replace resend with Aprsme.Swoosh.ResendAdapter
using the already-present req dependency. Frees 23 packages total from
the lockfile including hackney, certifi, and the tesla dependency chain.

Fix two pre-existing test isolation bugs:
- signal_handler_test called the real ShutdownHandler.shutdown() via SIGTERM
  simulation without restoring state, causing page_controller_test to see
  shutting_down: true and return 503
- page_controller_test setup now resets ShutdownHandler state defensively
2026-04-24 13:04:18 -05:00

78 lines
2.2 KiB
Elixir

defmodule Aprsme.Swoosh.ResendAdapter do
@moduledoc false
@behaviour Swoosh.Adapter
@api_url "https://api.resend.com/emails"
@impl true
def deliver(%Swoosh.Email{} = email, config) do
api_key = Keyword.fetch!(config, :api_key)
body =
%{
subject: email.subject,
from: format_sender(email.from),
to: format_recipients(email.to),
bcc: format_recipients(email.bcc),
cc: format_recipients(email.cc),
reply_to: format_recipients(email.reply_to),
headers: email.headers,
html: email.html_body,
text: email.text_body
}
|> Enum.reject(fn {_k, v} -> is_nil(v) or v == [] or v == %{} end)
|> Map.new()
req_opts = maybe_put_plug([json: body, headers: [{"authorization", "Bearer #{api_key}"}]], config)
case Req.post(@api_url, req_opts) do
{:ok, %{status: status, body: %{"id" => id}}} when status in 200..299 ->
{:ok, %{id: id}}
{:ok, %{body: %{"name" => name, "message" => message}}} ->
{:error, {name, message}}
{:ok, %{body: body}} ->
{:error, body}
{:error, reason} ->
{:error, reason}
end
end
@impl true
def deliver_many(emails, config) do
Enum.reduce_while(emails, {:ok, []}, fn email, {:ok, acc} ->
case deliver(email, config) do
{:ok, result} -> {:cont, {:ok, acc ++ [result]}}
{:error, _} = error -> {:halt, error}
end
end)
end
@impl true
def validate_config(config) do
if Keyword.has_key?(config, :api_key) do
:ok
else
{:error, "Missing required :api_key configuration for Aprsme.Swoosh.ResendAdapter"}
end
end
defp maybe_put_plug(opts, config) do
case Keyword.get(config, :plug) do
nil -> opts
plug -> Keyword.put(opts, :plug, plug)
end
end
defp format_sender(nil), do: nil
defp format_sender(from) when is_binary(from), do: from
defp format_sender({"", from}), do: from
defp format_sender({name, from}), do: "#{name} <#{from}>"
defp format_recipients(nil), do: nil
defp format_recipients(r) when is_binary(r), do: r
defp format_recipients({_name, addr}), do: addr
defp format_recipients(list) when is_list(list), do: Enum.map(list, &format_recipients/1)
end