Repository: swoosh/swoosh
Pull request #779 (2023-06-19 22:56:25)

Fix BCC for adapters that depend on SMTP helper
@adamu @thiamsantos 

https://elixirforum.com/t/elixir-v1-15-0-released/56584/4


Patch:
diff --git a/README.md b/README.md
index 15a905f..4608cac 100644
--- a/README.md
+++ b/README.md
@@ -14,7 +14,7 @@ The complete documentation for Swoosh is [available online at HexDocs](https://h
 
 ## Requirements
 
-Elixir 1.11+ and Erlang OTP 23+
+Elixir 1.12+ and Erlang OTP 24+
 
 ## Getting started
 
diff --git a/lib/plug/mailbox_preview.ex b/lib/plug/mailbox_preview.ex
index 19d2955..b39c17f 100644
--- a/lib/plug/mailbox_preview.ex
+++ b/lib/plug/mailbox_preview.ex
@@ -46,7 +46,7 @@ if Code.ensure_loaded?(Plug) do
 
       conn
       |> put_resp_header("location", conn.assigns.base_path)
-      |> send_resp(302, '')
+      |> send_resp(302, ~c"")
     end
 
     get "/json" do
diff --git a/lib/swoosh/adapters/amazon_ses.ex b/lib/swoosh/adapters/amazon_ses.ex
index befdae0..32e5a9a 100644
--- a/lib/swoosh/adapters/amazon_ses.ex
+++ b/lib/swoosh/adapters/amazon_ses.ex
@@ -168,7 +168,7 @@ defmodule Swoosh.Adapters.AmazonSES do
 
   defp generate_raw_message_data(email, config) do
     email
-    |> SMTPHelper.body(config)
+    |> SMTPHelper.body([{:keep_bcc, true} | config])
     |> Base.encode64()
     |> URI.encode()
   end
diff --git a/lib/swoosh/adapters/sendmail.ex b/lib/swoosh/adapters/sendmail.ex
index ec79081..f3017a5 100644
--- a/lib/swoosh/adapters/sendmail.ex
+++ b/lib/swoosh/adapters/sendmail.ex
@@ -24,7 +24,7 @@ defmodule Swoosh.Adapters.Sendmail do
 
   @impl true
   def deliver(%Email{} = email, config) do
-    body = Helpers.body(email, config)
+    body = Helpers.body(email, [{:keep_bcc, true} | config])
     port = Port.open({:spawn, cmd(email, config)}, [:binary])
     Port.command(port, body)
     Port.close(port)
diff --git a/lib/swoosh/adapters/smtp/helpers.ex b/lib/swoosh/adapters/smtp/helpers.ex
index 8aa0628..8693366 100644
--- a/lib/swoosh/adapters/smtp/helpers.ex
+++ b/lib/swoosh/adapters/smtp/helpers.ex
@@ -12,7 +12,7 @@ defmodule Swoosh.Adapters.SMTP.Helpers do
 
   @doc false
   def body(email, config) do
-    {message_config, config} = Keyword.split(config, [:transfer_encoding])
+    {message_config, config} = Keyword.split(config, [:transfer_encoding, :keep_bcc])
     {type, subtype, headers, parts} = prepare_message(email, message_config)
     {encoding_config, _config} = Keyword.split(config, [:dkim])
     mime_encode(type, subtype, headers, parts, encoding_config)
@@ -41,18 +41,26 @@ defmodule Swoosh.Adapters.SMTP.Helpers do
   @doc false
   def prepare_message(email, config) do
     email
-    |> prepare_headers()
+    |> prepare_headers(config)
     |> prepare_parts(email, config)
   end
 
-  defp prepare_headers(email) do
+  defp prepare_headers(email, config) do
     []
     |> prepare_additional_headers(email)
     |> prepare_mime_version()
     |> prepare_reply_to(email)
     |> prepare_subject(email)
     |> prepare_cc(email)
-    # bcc is deliberately omitted: https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.3
+    |> then(fn headers ->
+      # bcc is deliberately omitted unless explicitly asked to keep, for service providers
+      # https://datatracker.ietf.org/doc/html/rfc5322#section-3.6.3
+      if config[:keep_bcc] do
+        prepare_bcc(headers, email)
+      else
+        headers
+      end
+    end)
     |> prepare_to(email)
     |> prepare_from(email)
   end
@@ -68,6 +76,9 @@ defmodule Swoosh.Adapters.SMTP.Helpers do
   defp prepare_cc(headers, %{cc: []}), do: headers
   defp prepare_cc(headers, %{cc: cc}), do: [{"Cc", render_recipient(cc)} | headers]
 
+  defp prepare_bcc(headers, %{bcc: []}), do: headers
+  defp prepare_bcc(headers, %{bcc: bcc}), do: [{"Bcc", render_recipient(bcc)} | headers]
+
   defp prepare_reply_to(headers, %{reply_to: nil}), do: headers
 
   defp prepare_reply_to(headers, %{reply_to: reply_to}),
diff --git a/mix.exs b/mix.exs
index ad8b1cd..957723e 100644
--- a/mix.exs
+++ b/mix.exs
@@ -8,7 +8,7 @@ defmodule Swoosh.Mixfile do
     [
       app: :swoosh,
       version: @version,
-      elixir: "~> 1.11",
+      elixir: "~> 1.12",
       elixirc_paths: elixirc_paths(Mix.env()),
       consolidate_protocols: Mix.env() != :test,
       build_embedded: Mix.env() == :prod,
@@ -73,6 +73,7 @@ defmodule Swoosh.Mixfile do
       {:cowboy, "~> 1.1 or ~> 2.4", optional: true},
       {:plug_cowboy, ">= 1.0.0", optional: true},
       {:ex_aws, "~> 2.1", optional: true},
+      {:ssl_verify_fun, ">= 0.0.0", only: :test, manager: :rebar3, override: true},
       {:bypass, "~> 2.1", only: :test},
       {:ex_doc, "~> 0.26", only: :docs, runtime: false}
     ]


Test patch:
diff --git a/test/swoosh/adapters/amazonses_test.exs b/test/swoosh/adapters/amazonses_test.exs
index d85e9c5..da1b8f6 100644
--- a/test/swoosh/adapters/amazonses_test.exs
+++ b/test/swoosh/adapters/amazonses_test.exs
@@ -143,6 +143,26 @@ defmodule Swoosh.Adapters.AmazonSESTest do
     assert AmazonSES.deliver(email, config) == {:ok, %{id: "messageId", request_id: "requestId"}}
   end
 
+  test "deliver/1 encodes bcc", %{bypass: bypass, config: config} do
+    email =
+      new()
+      |> from({"G Threepwood", "guybrush.threepwood@pirates.grog"})
+      |> bcc("stan@coolshirt.com")
+      |> text_body("Hello")
+
+    Bypass.expect(bypass, fn conn ->
+      conn = parse(conn)
+
+      {:ok, raw_message} = conn.body_params["RawMessage.Data"] |> URI.decode() |> Base.decode64()
+
+      assert String.contains?(raw_message, "Bcc: stan@coolshirt.com\r\n")
+
+      Plug.Conn.resp(conn, 200, @success_response)
+    end)
+
+    assert AmazonSES.deliver(email, config) == {:ok, %{id: "messageId", request_id: "requestId"}}
+  end
+
   test "a sent email that returns a api error parses correctly", %{
     bypass: bypass,
     config: config,
diff --git a/test/swoosh/adapters/mail_pace_test.exs b/test/swoosh/adapters/mail_pace_test.exs
index a5ad080..53f2c98 100644
--- a/test/swoosh/adapters/mail_pace_test.exs
+++ b/test/swoosh/adapters/mail_pace_test.exs
@@ -4,7 +4,7 @@ defmodule Swoosh.Adapters.MailPaceTest do
   import Swoosh.Email
   alias Swoosh.Adapters.MailPace
 
-  @success_response '{"id":404021,"status":"queued"}'
+  @success_response ~c"{\"id\":404021,\"status\":\"queued\"}"
 
   setup do
     bypass = Bypass.open()