Repository: swoosh/swoosh
Pull request #850 (2024-01-24 16:23:29)

Feat mailgun multiple reply to
feature addition for https://github.com/swoosh/swoosh/issues/849

Added `[mailbox]` to `reply_to` types and handle `is_list` recipients in `emails.ex` and mailgun adapter

![swoosh-feat](https://github.com/swoosh/swoosh/assets/20119474/c43010e3-0928-4b90-b0f3-ceba25fced15)

now we are getting consistent reply-to

princemaple: I simplified `reply_to` to only overwrite. Please take a look and let me know if you are happy for this to go in as it is now.
ghostdsb: This looks good.

Patch:
diff --git a/lib/swoosh/adapters/mailgun.ex b/lib/swoosh/adapters/mailgun.ex
index 14bdbaf..7faf0a9 100644
--- a/lib/swoosh/adapters/mailgun.ex
+++ b/lib/swoosh/adapters/mailgun.ex
@@ -202,6 +202,15 @@ defmodule Swoosh.Adapters.Mailgun do
   defp prepare_reply_to(body, %{reply_to: {_name, address}}),
     do: Map.put(body, "h:Reply-To", address)
 
+  defp prepare_reply_to(body, %{reply_to: reply_tos}) when is_list(reply_tos) do
+    addresses =
+      reply_tos
+      |> Enum.map(fn {_name, address} -> address end)
+      |> Enum.join(", ")
+
+    Map.put(body, "h:Reply-To", addresses)
+  end
+
   defp prepare_cc(body, %{cc: []}), do: body
   defp prepare_cc(body, %{cc: cc}), do: Map.put(body, :cc, render_recipient(cc))
 
diff --git a/lib/swoosh/email.ex b/lib/swoosh/email.ex
index 9c389d6..5bcb49d 100644
--- a/lib/swoosh/email.ex
+++ b/lib/swoosh/email.ex
@@ -85,7 +85,7 @@ defmodule Swoosh.Email do
           bcc: [mailbox] | [],
           text_body: text_body | nil,
           html_body: html_body | nil,
-          reply_to: mailbox | nil,
+          reply_to: [mailbox] | mailbox | nil,
           headers: map,
           private: map,
           assigns: map,
@@ -211,7 +211,8 @@ defmodule Swoosh.Email do
   end
 
   @doc """
-  Sets a recipient in the `reply_to` field.
+  Sets a recipient in the `reply_to` field. May also set a list of recipients as `reply_to`, but the
+  support for it on adapters is on case-by-case basis.
 
   ## Examples
 
@@ -224,8 +225,18 @@ defmodule Swoosh.Email do
       %Swoosh.Email{assigns: %{}, attachments: [], bcc: [], cc: [], from: nil,
        headers: %{}, html_body: nil, private: %{}, provider_options: %{},
        reply_to: {"", "steve.rogers@example.com"}, subject: "", text_body: nil, to: []}
+
+      iex> new() |> reply_to([{"Steve Rogers", "steve.rogers@example.com"}, "bucky.barnes@example.com"])
+      %Swoosh.Email{assigns: %{}, attachments: [], bcc: [], cc: [], from: nil,
+       headers: %{}, html_body: nil, private: %{}, provider_options: %{},
+       reply_to: [{"Steve Rogers", "steve.rogers@example.com"}, {"", "bucky.barnes@example.com"}],
+       subject: "", text_body: nil, to: []}
   """
-  @spec reply_to(t, Recipient.t()) :: t
+  @spec reply_to(t, Recipient.t() | [Recipient.t()]) :: t
+  def reply_to(%__MODULE__{} = email, reply_to) when is_list(reply_to) do
+    %{email | reply_to: Enum.map(reply_to, &Recipient.format(&1))}
+  end
+
   def reply_to(%__MODULE__{} = email, reply_to) do
     %{email | reply_to: Recipient.format(reply_to)}
   end


Test patch:
diff --git a/test/integration/adapters/mailtrap_test.exs b/test/integration/adapters/mailtrap_test.exs
index d0f6180..02eea40 100644
--- a/test/integration/adapters/mailtrap_test.exs
+++ b/test/integration/adapters/mailtrap_test.exs
@@ -44,7 +44,6 @@ defmodule Swoosh.Integration.Adapters.MailtrapTest do
 
     sandbox_config = [sandbox_inbox_id: System.get_env("MAILTRAP_INBOX")]
 
-    assert {:ok, _response} =
-             Swoosh.Adapters.Mailtrap.deliver(email, config ++ sandbox_config)
+    assert {:ok, _response} = Swoosh.Adapters.Mailtrap.deliver(email, config ++ sandbox_config)
   end
 end
diff --git a/test/swoosh/adapters/mailgun_test.exs b/test/swoosh/adapters/mailgun_test.exs
index 8627689..1a22ea3 100644
--- a/test/swoosh/adapters/mailgun_test.exs
+++ b/test/swoosh/adapters/mailgun_test.exs
@@ -133,6 +133,38 @@ defmodule Swoosh.Adapters.MailgunTest do
              {:ok, %{id: "<20111114174239.25659.5817@samples.mailgun.org>"}}
   end
 
+  test "deliver/1 with multiple reply_to returns :ok", %{bypass: bypass, config: config} do
+    email =
+      new()
+      |> from({"T Stark", "tony.stark@example.com"})
+      |> to({"Steve Rogers", "steve.rogers@example.com"})
+      |> reply_to(["office.avengers@example.com", "javis.gpt@example.com"])
+      |> subject("Hello, Avengers!")
+      |> html_body("<h1>Hello</h1>")
+
+    Bypass.expect(bypass, fn conn ->
+      conn = parse(conn)
+      expected_path = "/" <> config[:domain] <> "/messages"
+
+      body_params = %{
+        "subject" => "Hello, Avengers!",
+        "to" => ~s("Steve Rogers" <steve.rogers@example.com>),
+        "h:Reply-To" => "office.avengers@example.com, javis.gpt@example.com",
+        "from" => ~s("T Stark" <tony.stark@example.com>),
+        "html" => "<h1>Hello</h1>"
+      }
+
+      assert body_params == conn.body_params
+      assert expected_path == conn.request_path
+      assert "POST" == conn.method
+
+      Plug.Conn.resp(conn, 200, @success_response)
+    end)
+
+    assert Mailgun.deliver(email, config) ==
+             {:ok, %{id: "<20111114174239.25659.5817@samples.mailgun.org>"}}
+  end
+
   test "deliver/1 with sending options returns :ok", %{bypass: bypass, config: config} do
     email =
       new()
diff --git a/test/swoosh/adapters/mailtrap_test.exs b/test/swoosh/adapters/mailtrap_test.exs
index 3d5ca1e..ad9447c 100644
--- a/test/swoosh/adapters/mailtrap_test.exs
+++ b/test/swoosh/adapters/mailtrap_test.exs
@@ -398,8 +398,7 @@ defmodule Swoosh.Adapters.MailtrapTest do
 
     Bypass.expect(bypass, &Plug.Conn.resp(&1, 400, errors))
 
-    response =
-      {:error, {400, %{"errors" => ["bla bla"], "success" => false}}}
+    response = {:error, {400, %{"errors" => ["bla bla"], "success" => false}}}
 
     assert Mailtrap.deliver(email, config) == response
   end
@@ -409,8 +408,7 @@ defmodule Swoosh.Adapters.MailtrapTest do
 
     Bypass.expect(bypass, &Plug.Conn.resp(&1, 400, errors))
 
-    response =
-      {:error, {400, %{"errors" => ["bla bla"], "success" => false}}}
+    response = {:error, {400, %{"errors" => ["bla bla"], "success" => false}}}
 
     assert Mailtrap.deliver(email, config) == response
   end