diff --git a/.github/workflows/bamboo_smtp.yml b/.github/workflows/bamboo_smtp.yml new file mode 100644 index 0000000..e202c09 --- /dev/null +++ b/.github/workflows/bamboo_smtp.yml @@ -0,0 +1,59 @@ +name: Bamboo SMTP + +on: push + +jobs: + test: + name: Test on Elixir ${{ matrix.elixir }} / OTP ${{ matrix.otp }} + runs-on: ubuntu-latest + strategy: + matrix: + elixir: [1.7.4, 1.8.2, 1.9.1, 1.10.3] + otp: [19.3, 20.3, 21.3, 22.0] + exclude: + - elixir: 1.8.2 + otp: 19.3 + - elixir: 1.9.1 + otp: 19.3 + - elixir: 1.10.3 + otp: 19.3 + - elixir: 1.10.3 + otp: 20.3 + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - uses: actions/cache@v2 + with: + path: deps + key: ${{ runner.os }}-mix-${{ hashFiles(format('{0}{1}', github.workspace, '/mix.lock')) }}-elixir-${{ matrix.elixir }}-otp-${{ matrix.otp }} + + - uses: actions/setup-elixir@v1 + with: + otp-version: ${{ matrix.otp }} + elixir-version: ${{ matrix.elixir }} + + - run: mix deps.get + - run: mix compile --warnings-as-errors + - run: mix credo --strict + - run: mix test + + doc: + name: Generate inch report + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - uses: actions/cache@v2 + with: + path: deps + key: ${{ runner.os }}-mix-${{ hashFiles(format('{0}{1}', github.workspace, '/mix.lock')) }} + + - uses: actions/setup-elixir@v1 + with: + otp-version: 22.0 + elixir-version: 1.10.3 + + - run: mix deps.get --only docs + - run: MIX_ENV=docs mix inch.report diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 7c66d9d..0000000 --- a/.travis.yml +++ /dev/null @@ -1,37 +0,0 @@ -sudo: required -language: elixir -elixir: - - 1.7.4 - - 1.8.2 - - 1.9.1 - - 1.10.3 -otp_release: - - 19.3 - - 20.3 - - 21.3 - - 22.0 -matrix: - exclude: - - elixir: 1.8.2 - otp_release: 19.3 - - elixir: 1.9.1 - otp_release: 19.3 - - elixir: 1.10.3 - otp_release: 19.3 - - elixir: 1.10.3 - otp_release: 20.3 - -cache: - directories: - - _build - - deps -script: - - mix compile --warnings-as-errors - - mix test - - mix credo --strict -after_script: - - mix deps.get --only docs - - MIX_ENV=docs mix inch.report -notifications: - slack: - secure: IOIWS7VDdEx/JyxyZ6DJ5b7qYLTYPdaiVkhN/1VL3jjXAl+IHBd5gHfi2VgozGCQ9TvLAYvxsAVbywHXQQk4fk4Tm69Y5fiN+BGQkL22Mseo1kwwEllRzLNPCRiNNiRzDOMT8If9VeaYTTyh2hecRBsTkMeH9Htewf8EI2uIF07xRZtYqGcgnbg8+kTMhAvnhx1jHxDJQ0qYaHn5FWwy8SKocAzgYbU13xvGeCkomwkoUCxHj3B/+NWavBw1XObx1f75ZO8WeFub5wVbqgoAhJ/sygGiTTdFzg2OTCx4hmrBbxI1q6o4uLa9tmIuxAWpOfxVotd8/90ansDAk2epJBY31sPBYqQZgCdxI1JNAWpLQYPRrrQkQ1Vo1ofznHaiJtNC9sherjio/360ZdpNScUmPZAAzcjmsAv3vIqmYIBCPL0w53hGgG/1cGS/AvtDXrP5k66jGDG9Absy6d4DmeHgn95RatFs2SAKuObPbIbVfHbA2l/Debz+qSqu5GD0I47tbMTer9/BB9pvwLQ4b7q58S3RthDEVl5Y44grN1dj3Y/B3vnRqMsm2ftonlPMGDTjBLW80xBBF/qi6HtZmRpGsVrE+pUVCZ4KwFUW/e/lhDyrrXmwmSlESOj9nyXdoqmMQlOE+3IDfZa9n/k0aMoeeWtTG3kPDNVQvTbDZIQ= diff --git a/CHANGELOG.md b/CHANGELOG.md index 10fb8ef..df67fa2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,27 @@ # CHANGELOG +## 3.1.0 - 2020-11-23 + +- Fix for using custom config with `response: true` by bumping `bamboo` version to `~> 1.6` ([#150]) +- Implement our custom test adapter ([#151]) +- Fix CI random failure by attaching FakeGenSMTP Server process to Test supervision tree.([#153]) +- Add Content-ID header when needed([#154]) +- Base 64 encode the headers only when the content contains non-ASCII characters.([#155]) +- Handle `:permanent_failure` exception and re-raising it as a `SMTPError`.([#156]) +- After bumping the dependencies, the project requires([#149]): + - credo `~> 1.4.1` + - bamboo `~> 1.6` + - excoveralls `~> 0.13.3` + - gen_smtp `~> 1.0.1` + +[#149]: https://github.com/fewlinesco/bamboo_smtp/pull/149 +[#150]: https://github.com/fewlinesco/bamboo_smtp/pull/150 +[#151]: https://github.com/fewlinesco/bamboo_smtp/pull/151 +[#153]: https://github.com/fewlinesco/bamboo_smtp/pull/153 +[#154]: https://github.com/fewlinesco/bamboo_smtp/pull/154 +[#155]: https://github.com/fewlinesco/bamboo_smtp/pull/155 +[#156]: https://github.com/fewlinesco/bamboo_smtp/pull/156 + ## 3.0.0 - 2020-09-10 - Fix eml attachment ([#137]). diff --git a/README.md b/README.md index 0a4c9ee..eac6856 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ [![Hex pm](https://img.shields.io/hexpm/v/bamboo_smtp.svg)](https://hex.pm/packages/bamboo_smtp) -[![Build Status](https://travis-ci.org/fewlinesco/bamboo_smtp.svg?branch=master)](https://travis-ci.org/fewlinesco/bamboo_smtp) +[![Build Status](https://github.com/fewlinesco/bamboo_smtp/workflows/Bamboo%20SMTP/badge.svg)](https://github.com/fewlinesco/bamboo_smtp/actions) [![Inline docs](http://inch-ci.org/github/fewlinesco/bamboo_smtp.svg)](http://inch-ci.org/github/fewlinesco/bamboo_smtp) # Bamboo.SMTPAdapter @@ -14,7 +14,7 @@ The package can be installed as: ```elixir def deps do - [{:bamboo_smtp, "~> 3.0.0"}] + [{:bamboo_smtp, "~> 3.1.0"}] end ``` @@ -52,6 +52,14 @@ The *hostname* option sets the FQDN to the header of your emails, its optional, 4. Follow Bamboo [Getting Started Guide](https://github.com/thoughtbot/bamboo#getting-started) +5. **Optional** Set `BambooSMTP.TestAdapter` as your test adapter: + + ```elixir + # In your config/config.exs file + if Mix.env() == :test do + config :my_app, MyApp.Mailer, adapter: MyApp.SMTPTestAdapter + end + ``` ## Usage You can find more information about advanced features in the [Wiki](https://github.com/fewlinesco/bamboo_smtp/wiki). diff --git a/lib/bamboo/adapters/smtp_adapter.ex b/lib/bamboo/adapters/smtp_adapter.ex index 665a0c4..20e0b2e 100644 --- a/lib/bamboo/adapters/smtp_adapter.ex +++ b/lib/bamboo/adapters/smtp_adapter.ex @@ -71,11 +71,18 @@ defmodule Bamboo.SMTPAdapter do config |> to_gen_smtp_server_config - email - |> Bamboo.Mailer.normalize_addresses() - |> to_gen_smtp_message - |> config[:transport].send_blocking(gen_smtp_config) - |> handle_response + response = + try do + email + |> Bamboo.Mailer.normalize_addresses() + |> to_gen_smtp_message + |> config[:transport].send_blocking(gen_smtp_config) + catch + e -> + raise SMTPError, {:not_specified, e} + end + + handle_response(response) end @doc false @@ -96,6 +103,10 @@ defmodule Bamboo.SMTPAdapter do raise SMTPError, {reason, detail} end + defp handle_response({:error, detail}) do + raise SMTPError, {:not_specified, detail} + end + defp handle_response(response) do {:ok, response} end @@ -191,7 +202,17 @@ defmodule Bamboo.SMTPAdapter do end defp rfc822_encode(content) do - "=?UTF-8?B?#{Base.encode64(content)}?=" + if contains_only_ascii_characters?(content) do + "=?UTF-8?B?#{content}?=" + else + "=?UTF-8?B?#{Base.encode64(content)}?=" + end + end + + defp contains_only_ascii_characters?(content) do + content + |> String.to_charlist() + |> List.ascii_printable?() end def base64_and_split(data) do @@ -215,7 +236,19 @@ defmodule Bamboo.SMTPAdapter do |> add_smtp_line(text_body) end - defp add_attachment_header(body, %{content_type: content_type} = attachment) + defp add_attachment_header(body, attachment) do + case attachment.content_id do + nil -> + add_common_attachment_header(body, attachment) + + cid -> + body + |> add_common_attachment_header(attachment) + |> add_smtp_line("Content-ID: <#{cid}>") + end + end + + defp add_common_attachment_header(body, %{content_type: content_type} = attachment) when content_type == "message/rfc822" do <> = :crypto.strong_rand_bytes(4) @@ -225,7 +258,7 @@ defmodule Bamboo.SMTPAdapter do |> add_smtp_line("X-Attachment-Id: #{random}") end - defp add_attachment_header(body, attachment) do + defp add_common_attachment_header(body, attachment) do <> = :crypto.strong_rand_bytes(4) body diff --git a/lib/bamboo/adapters/test_adapter.ex b/lib/bamboo/adapters/test_adapter.ex new file mode 100644 index 0000000..5c99743 --- /dev/null +++ b/lib/bamboo/adapters/test_adapter.ex @@ -0,0 +1,56 @@ +defmodule BambooSMTP.TestAdapter do + @moduledoc """ + Based on `Bamboo.TestAdapter`, this module can be used for testing email delivery. + + The `deliver/2` function will provide a response that follow the format of a SMTP server raw response. + + No emails are sent, instead it sends back `{%Bamboo.Email{...}, {:ok,""}}` + for success and raise an exception on error. + + ## Example config + + # Typically done in config/test.exs + config :my_app, MyApp.Mailer, + adapter: BambooSMTP.TestAdapter + + # Define a Mailer. Typically in lib/my_app/mailer.ex + defmodule MyApp.Mailer do + use Bamboo.Mailer, otp_app: :my_app + end + """ + + @behaviour Bamboo.Adapter + + @doc false + def deliver(_email, _config) do + send(test_process(), {:ok, "Ok #{Enum.random(100_000_000..999_999_999)}"}) + end + + defp test_process do + Application.get_env(:bamboo, :shared_test_process) || self() + end + + def handle_config(config) do + case config[:deliver_later_strategy] do + nil -> + Map.put(config, :deliver_later_strategy, Bamboo.ImmediateDeliveryStrategy) + + Bamboo.ImmediateDeliveryStrategy -> + config + + _ -> + raise ArgumentError, """ + BambooSMTP.TestAdapter requires that the deliver_later_strategy is + Bamboo.ImmediateDeliveryStrategy + + Instead it got: #{inspect(config[:deliver_later_strategy])} + + Please remove the deliver_later_strategy from your config options, or + set it to Bamboo.ImmediateDeliveryStrategy. + """ + end + end + + @doc false + def supports_attachments?, do: true +end diff --git a/mix.exs b/mix.exs index 4a3ca8a..6478a13 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule BambooSmtp.Mixfile do def project do [ app: :bamboo_smtp, - version: "3.0.0", + version: "3.1.0", elixir: "~> 1.7", source_url: @project_url, homepage_url: @project_url, @@ -27,19 +27,19 @@ defmodule BambooSmtp.Mixfile do defp deps do [ - {:bamboo, "~> 1.2"}, - {:credo, "~> 1.4.0", only: [:dev, :test]}, + {:bamboo, "~> 1.6"}, + {:credo, "~> 1.4.1", only: [:dev, :test]}, {:earmark, ">= 1.3.2", only: :docs}, - {:excoveralls, "~> 0.12.0", only: :test}, + {:excoveralls, "~> 0.13.3", only: :test}, {:ex_doc, ex_doc_version(), only: :docs}, - {:gen_smtp, "~> 0.15.0"}, + {:gen_smtp, "~> 1.0.1"}, {:inch_ex, "~> 2.0.0", only: :docs} ] end defp ex_doc_version do if Version.match?(System.version(), "~> 1.7") do - "~> 0.21.2" + "~> 0.23.0" else "~> 0.18.4" end diff --git a/mix.lock b/mix.lock index 5b5d399..2c6734a 100644 --- a/mix.lock +++ b/mix.lock @@ -1,30 +1,29 @@ %{ - "bamboo": {:hex, :bamboo, "1.5.0", "1926107d58adba6620450f254dfe8a3686637a291851fba125686fa8574842af", [:mix], [{:hackney, ">= 1.13.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "d5f3d04d154e80176fd685e2531e73870d8700679f14d25a567e448abce6298d"}, + "bamboo": {:hex, :bamboo, "1.6.0", "adfb583bef028923aae6f22deaea6667290561da1246058556ecaeb0fec5a175", [:mix], [{:hackney, ">= 1.13.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "454e67feacbc9b6e00553ce1d2fba003c861e0035600d59b09d6159985b17f9b"}, "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, - "certifi": {:hex, :certifi, "2.5.1", "867ce347f7c7d78563450a18a6a28a8090331e77fa02380b4a21962a65d36ee5", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm", "805abd97539caf89ec6d4732c91e62ba9da0cda51ac462380bbd28ee697a8c42"}, - "credo": {:hex, :credo, "1.4.0", "92339d4cbadd1e88b5ee43d427b639b68a11071b6f73854e33638e30a0ea11f5", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "1fd3b70dce216574ce3c18bdf510b57e7c4c85c2ec9cad4bff854abaf7e58658"}, - "dogma": {:hex, :dogma, "0.1.13", "7b6c6ad2b3ee6501eda3bd39e197dd5198be8d520d1c175c7f713803683cf27a", [:mix], [{:poison, ">= 2.0.0", [hex: :poison, optional: false]}]}, - "earmark": {:hex, :earmark, "1.4.4", "4821b8d05cda507189d51f2caeef370cf1e18ca5d7dfb7d31e9cafe6688106a4", [:mix], [], "hexpm", "1f93aba7340574847c0f609da787f0d79efcab51b044bb6e242cae5aca9d264d"}, - "ex_doc": {:hex, :ex_doc, "0.21.3", "857ec876b35a587c5d9148a2512e952e24c24345552259464b98bfbb883c7b42", [:mix], [{:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "0db1ee8d1547ab4877c5b5dffc6604ef9454e189928d5ba8967d4a58a801f161"}, - "excoveralls": {:hex, :excoveralls, "0.12.3", "2142be7cb978a3ae78385487edda6d1aff0e482ffc6123877bb7270a8ffbcfe0", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "568a3e616c264283f5dea5b020783ae40eef3f7ee2163f7a67cbd7b35bcadada"}, - "exjsx": {:hex, :exjsx, "4.0.0", "60548841e0212df401e38e63c0078ec57b33e7ea49b032c796ccad8cde794b5c", [:mix], [{:jsx, "~> 2.8.0", [hex: :jsx, repo: "hexpm", optional: false]}], "hexpm"}, - "gen_smtp": {:hex, :gen_smtp, "0.15.0", "9f51960c17769b26833b50df0b96123605a8024738b62db747fece14eb2fbfcc", [:rebar3], [], "hexpm", "29bd14a88030980849c7ed2447b8db6d6c9278a28b11a44cafe41b791205440f"}, - "hackney": {:hex, :hackney, "1.15.2", "07e33c794f8f8964ee86cebec1a8ed88db5070e52e904b8f12209773c1036085", [:rebar3], [{:certifi, "2.5.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.5", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "e0100f8ef7d1124222c11ad362c857d3df7cb5f4204054f9f0f4a728666591fc"}, - "httpoison": {:hex, :httpoison, "0.9.0", "68187a2daddfabbe7ca8f7d75ef227f89f0e1507f7eecb67e4536b3c516faddb", [:mix], [{:hackney, "~> 1.6.0", [hex: :hackney, optional: false]}]}, - "idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "4bdd305eb64e18b0273864920695cb18d7a2021f31a11b9c5fbcd9a253f936e2"}, + "certifi": {:hex, :certifi, "2.5.2", "b7cfeae9d2ed395695dd8201c57a2d019c0c43ecaf8b8bcb9320b40d6662f340", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm", "3b3b5f36493004ac3455966991eaf6e768ce9884693d9968055aeeeb1e575040"}, + "credo": {:hex, :credo, "1.4.1", "16392f1edd2cdb1de9fe4004f5ab0ae612c92e230433968eab00aafd976282fc", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "155f8a2989ad77504de5d8291fa0d41320fdcaa6a1030472e9967f285f8c7692"}, + "earmark": {:hex, :earmark, "1.4.10", "bddce5e8ea37712a5bfb01541be8ba57d3b171d3fa4f80a0be9bcf1db417bcaf", [:mix], [{:earmark_parser, ">= 1.4.10", [hex: :earmark_parser, repo: "hexpm", optional: false]}], "hexpm", "12dbfa80810478e521d3ffb941ad9fbfcbbd7debe94e1341b4c4a1b2411c1c27"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.10", "6603d7a603b9c18d3d20db69921527f82ef09990885ed7525003c7fe7dc86c56", [:mix], [], "hexpm", "8e2d5370b732385db2c9b22215c3f59c84ac7dda7ed7e544d7c459496ae519c0"}, + "ex_doc": {:hex, :ex_doc, "0.23.0", "a069bc9b0bf8efe323ecde8c0d62afc13d308b1fa3d228b65bca5cf8703a529d", [:mix], [{:earmark_parser, "~> 1.4.0", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "f5e2c4702468b2fd11b10d39416ddadd2fcdd173ba2a0285ebd92c39827a5a16"}, + "excoveralls": {:hex, :excoveralls, "0.13.3", "edc5f69218f84c2bf61b3609a22ddf1cec0fbf7d1ba79e59f4c16d42ea4347ed", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cc26f48d2f68666380b83d8aafda0fffc65dafcc8d8650358e0b61f6a99b1154"}, + "gen_smtp": {:hex, :gen_smtp, "1.0.1", "cc3de2898d9136ff02dc78fc8193d4fc997824fc793f5bbbada79ccde94581be", [:rebar3], [{:hut, "1.3.0", [hex: :hut, repo: "hexpm", optional: false]}, {:ranch, "1.6.2", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "341a98dc4dafad9cc1e4426e4988e45b31d3f235fa637408f7579a9b598c0ab6"}, + "hackney": {:hex, :hackney, "1.16.0", "5096ac8e823e3a441477b2d187e30dd3fff1a82991a806b2003845ce72ce2d84", [:rebar3], [{:certifi, "2.5.2", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.1", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.0", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.6", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "3bf0bebbd5d3092a3543b783bf065165fa5d3ad4b899b836810e513064134e18"}, + "hut": {:hex, :hut, "1.3.0", "71f2f054e657c03f959cf1acc43f436ea87580696528ca2a55c8afb1b06c85e7", [:"erlang.mk", :rebar, :rebar3], [], "hexpm", "7e15d28555d8a1f2b5a3a931ec120af0753e4853a4c66053db354f35bf9ab563"}, + "idna": {:hex, :idna, "6.0.1", "1d038fb2e7668ce41fbf681d2c45902e52b3cb9e9c77b55334353b222c2ee50c", [:rebar3], [{:unicode_util_compat, "0.5.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "a02c8a1c4fd601215bb0b0324c8a6986749f807ce35f25449ec9e69758708122"}, "inch_ex": {:hex, :inch_ex, "2.0.0", "24268a9284a1751f2ceda569cd978e1fa394c977c45c331bb52a405de544f4de", [:mix], [{:bunt, "~> 0.2", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "96d0ec5ecac8cf63142d02f16b7ab7152cf0f0f1a185a80161b758383c9399a8"}, - "jason": {:hex, :jason, "1.2.1", "12b22825e22f468c02eb3e4b9985f3d0cb8dc40b9bd704730efa11abd2708c44", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "b659b8571deedf60f79c5a608e15414085fa141344e2716fbd6988a084b5f993"}, - "jsx": {:hex, :jsx, "2.8.2", "7acc7d785b5abe8a6e9adbde926a24e481f29956dd8b4df49e3e4e7bcc92a018", [:mix, :rebar3], [], "hexpm"}, - "makeup": {:hex, :makeup, "1.0.1", "82f332e461dc6c79dbd82fbe2a9c10d48ed07146f0a478286e590c83c52010b5", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "49736fe5b66a08d8575bf5321d716bac5da20c8e6b97714fec2bcd6febcfa1f8"}, - "makeup_elixir": {:hex, :makeup_elixir, "0.14.0", "cf8b7c66ad1cff4c14679698d532f0b5d45a3968ffbcbfd590339cb57742f1ae", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "d4b316c7222a85bbaa2fd7c6e90e37e953257ad196dc229505137c5e505e9eff"}, + "jason": {:hex, :jason, "1.2.2", "ba43e3f2709fd1aa1dce90aaabfd039d000469c05c56f0b8e31978e03fa39052", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "18a228f5f0058ee183f29f9eae0805c6e59d61c3b006760668d8d18ff0d12179"}, + "makeup": {:hex, :makeup, "1.0.5", "d5a830bc42c9800ce07dd97fa94669dfb93d3bf5fcf6ea7a0c67b2e0e4a7f26c", [:mix], [{:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cfa158c02d3f5c0c665d0af11512fed3fba0144cf1aadee0f2ce17747fba2ca9"}, + "makeup_elixir": {:hex, :makeup_elixir, "0.15.0", "98312c9f0d3730fde4049985a1105da5155bfe5c11e47bdc7406d88e01e4219b", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.1", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "75ffa34ab1056b7e24844c90bfc62aaf6f3a37a15faa76b07bc5eba27e4a8b4a"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, - "mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm", "6cbe761d6a0ca5a31a0931bf4c63204bceb64538e664a8ecf784a9a6f3b875f1"}, + "mime": {:hex, :mime, "1.4.0", "5066f14944b470286146047d2f73518cf5cca82f8e4815cf35d196b58cf07c47", [:mix], [], "hexpm", "75fa42c4228ea9a23f70f123c74ba7cece6a03b1fd474fe13f6a7a85c6ea4ff6"}, "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, - "nimble_parsec": {:hex, :nimble_parsec, "0.5.3", "def21c10a9ed70ce22754fdeea0810dafd53c2db3219a0cd54cf5526377af1c6", [:mix], [], "hexpm", "589b5af56f4afca65217a1f3eb3fee7e79b09c40c742fddc1c312b3ac0b3399f"}, + "nimble_parsec": {:hex, :nimble_parsec, "1.1.0", "3a6fca1550363552e54c216debb6a9e95bd8d32348938e13de5eda962c0d7f89", [:mix], [], "hexpm", "08eb32d66b706e913ff748f11694b17981c0b04a33ef470e33e11b3d3ac8f54b"}, "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm", "17ef63abde837ad30680ea7f857dd9e7ced9476cdd7b0394432af4bfc241b960"}, - "plug": {:hex, :plug, "1.10.0", "6508295cbeb4c654860845fb95260737e4a8838d34d115ad76cd487584e2fc4d", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "422a9727e667be1bf5ab1de03be6fa0ad67b775b2d84ed908f3264415ef29d4a"}, - "plug_crypto": {:hex, :plug_crypto, "1.1.2", "bdd187572cc26dbd95b87136290425f2b580a116d3fb1f564216918c9730d227", [:mix], [], "hexpm", "6b8b608f895b6ffcfad49c37c7883e8df98ae19c6a28113b02aa1e9c5b22d6b5"}, - "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"}, - "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.5", "6eaf7ad16cb568bb01753dbbd7a95ff8b91c7979482b95f38443fe2c8852a79b", [:make, :mix, :rebar3], [], "hexpm", "13104d7897e38ed7f044c4de953a6c28597d1c952075eb2e328bc6d6f2bfc496"}, - "unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm", "1d1848c40487cdb0b30e8ed975e34e025860c02e419cb615d255849f3427439d"}, + "plug": {:hex, :plug, "1.11.0", "f17217525597628298998bc3baed9f8ea1fa3f1160aa9871aee6df47a6e4d38e", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2d9c633f0499f9dc5c2fd069161af4e2e7756890b81adcbb2ceaa074e8308876"}, + "plug_crypto": {:hex, :plug_crypto, "1.2.0", "1cb20793aa63a6c619dd18bb33d7a3aa94818e5fd39ad357051a67f26dfa2df6", [:mix], [], "hexpm", "a48b538ae8bf381ffac344520755f3007cc10bd8e90b240af98ea29b69683fc2"}, + "ranch": {:hex, :ranch, "1.6.2", "6db93c78f411ee033dbb18ba8234c5574883acb9a75af0fb90a9b82ea46afa00", [:rebar3], [], "hexpm", "0b65d1de7f1ebe96feb57803c8d698f7776b663ba10b6dcff34527e4a4f8ff43"}, + "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"}, + "telemetry": {:hex, :telemetry, "0.4.2", "2808c992455e08d6177322f14d3bdb6b625fbcfd233a73505870d8738a2f4599", [:rebar3], [], "hexpm", "2d1419bd9dda6a206d7b5852179511722e2b18812310d304620c7bd92a13fcef"}, + "unicode_util_compat": {:hex, :unicode_util_compat, "0.5.0", "8516502659002cec19e244ebd90d312183064be95025a319a6c7e89f4bccd65b", [:rebar3], [], "hexpm", "d48d002e15f5cc105a696cf2f1bbb3fc72b4b770a184d8420c8db20da2674b38"}, } diff --git a/test/attachments/attachment_one.txt b/test/attachments/attachment_one.txt new file mode 100644 index 0000000..3e2caaf --- /dev/null +++ b/test/attachments/attachment_one.txt @@ -0,0 +1 @@ +Attachment One. diff --git a/test/attachments/attachment_two.txt b/test/attachments/attachment_two.txt new file mode 100644 index 0000000..e4888d3 --- /dev/null +++ b/test/attachments/attachment_two.txt @@ -0,0 +1 @@ +Attachment Two. diff --git a/test/lib/bamboo/adapters/smtp_adapter_test.exs b/test/lib/bamboo/adapters/smtp_adapter_test.exs index 3d70020..08b9ac9 100644 --- a/test/lib/bamboo/adapters/smtp_adapter_test.exs +++ b/test/lib/bamboo/adapters/smtp_adapter_test.exs @@ -12,7 +12,7 @@ defmodule Bamboo.SMTPAdapterTest do {:ok, args} end - def start_link do + def start_link(_) do GenServer.start_link(__MODULE__, [], name: __MODULE__) end @@ -115,7 +115,7 @@ defmodule Bamboo.SMTPAdapterTest do ] setup do - FakeGenSMTP.start_link() + start_supervised!(FakeGenSMTP) :ok end @@ -434,7 +434,7 @@ defmodule Bamboo.SMTPAdapterTest do assert format_email_as_string(bamboo_email.from, false) == from assert format_email(bamboo_email.to ++ bamboo_email.cc ++ bamboo_email.bcc, false) == to - rfc822_subject = "Subject: =?UTF-8?B?SGVsbG8gZnJvbSBCYW1ib28=?=\r\n" + rfc822_subject = "Subject: =?UTF-8?B?Hello from Bamboo?=\r\n" assert String.contains?(raw_email, rfc822_subject) assert String.contains?(raw_email, "From: #{format_email_as_string(bamboo_email.from)}\r\n") @@ -499,7 +499,7 @@ defmodule Bamboo.SMTPAdapterTest do assert format_email_as_string(bamboo_email.from, false) == from assert format_email(bamboo_email.to ++ bamboo_email.cc ++ bamboo_email.bcc, false) == to - rfc822_subject = "Subject: =?UTF-8?B?SGVsbG8gZnJvbSBCYW1ib28=?=\r\n" + rfc822_subject = "Subject: =?UTF-8?B?Hello from Bamboo?=\r\n" assert String.contains?(raw_email, rfc822_subject) assert String.contains?(raw_email, "From: #{format_email_as_string(bamboo_email.from)}\r\n") @@ -547,7 +547,7 @@ defmodule Bamboo.SMTPAdapterTest do assert format_email_as_string(bamboo_email.from, false) == from assert format_email(bamboo_email.to ++ bamboo_email.cc ++ bamboo_email.bcc, false) == to - rfc822_subject = "Subject: =?UTF-8?B?SGVsbG8gZnJvbSBCYW1ib28=?=\r\n" + rfc822_subject = "Subject: =?UTF-8?B?Hello from Bamboo?=\r\n" assert String.contains?(raw_email, rfc822_subject) assert String.contains?(raw_email, "From: #{format_email_as_string(bamboo_email.from)}\r\n") @@ -597,7 +597,7 @@ defmodule Bamboo.SMTPAdapterTest do assert format_email_as_string(bamboo_email.from, false) == from assert format_email(bamboo_email.to ++ bamboo_email.cc ++ bamboo_email.bcc, false) == to - rfc822_subject = "Subject: =?UTF-8?B?SGVsbG8gZnJvbSBCYW1ib28=?=\r\n" + rfc822_subject = "Subject: =?UTF-8?B?Hello from Bamboo?=\r\n" assert String.contains?(raw_email, rfc822_subject) assert String.contains?(raw_email, "From: #{format_email_as_string(bamboo_email.from)}\r\n") @@ -646,7 +646,7 @@ defmodule Bamboo.SMTPAdapterTest do assert format_email_as_string(bamboo_email.from, false) == from assert format_email(bamboo_email.to ++ bamboo_email.cc ++ bamboo_email.bcc, false) == to - rfc822_subject = "Subject: =?UTF-8?B?SGVsbG8gZnJvbSBCYW1ib28=?=\r\n" + rfc822_subject = "Subject: =?UTF-8?B?Hello from Bamboo?=\r\n" assert String.contains?(raw_email, rfc822_subject) assert String.contains?(raw_email, "From: #{format_email_as_string(bamboo_email.from)}\r\n") @@ -675,6 +675,83 @@ defmodule Bamboo.SMTPAdapterTest do assert_configuration(bamboo_config, gen_smtp_config) end + test "email looks fine when they have non-ASCII characters in subject, from and to" do + bamboo_email = + new_email( + from: {"Awesome Person 😎", "awesome@person.local"}, + to: {"Person Awesome 🤩", "person@awesome.local"}, + subject: "Hello! 👋" + ) + + bamboo_config = configuration() + + {:ok, "200 Ok 1234567890"} = SMTPAdapter.deliver(bamboo_email, bamboo_config) + + assert 1 = length(FakeGenSMTP.fetch_sent_emails()) + + [{{from, to, raw_email}, gen_smtp_config}] = FakeGenSMTP.fetch_sent_emails() + + [multipart_header] = + Regex.run( + ~r{Content-Type: multipart/alternative; boundary="([^"]+)"\r\n}, + raw_email, + capture: :all_but_first + ) + + assert format_email_as_string(bamboo_email.from, false) == from + assert format_email(bamboo_email.to ++ bamboo_email.cc ++ bamboo_email.bcc, false) == to + + rfc822_subject = "Subject: =?UTF-8?B?SGVsbG8hIPCfkYs=?=\r\n" + assert String.contains?(raw_email, rfc822_subject) + + assert String.contains?(raw_email, "From: #{format_email_as_string(bamboo_email.from)}\r\n") + assert String.contains?(raw_email, "To: #{format_email_as_string(bamboo_email.to)}\r\n") + assert String.contains?(raw_email, "Cc: #{format_email_as_string(bamboo_email.cc)}\r\n") + assert String.contains?(raw_email, "Bcc: #{format_email_as_string(bamboo_email.bcc)}\r\n") + assert String.contains?(raw_email, "Reply-To: reply@doe.com\r\n") + assert String.contains?(raw_email, "MIME-Version: 1.0\r\n") + + assert String.contains?( + raw_email, + "--#{multipart_header}\r\n" <> + "Content-Type: text/html;charset=UTF-8\r\n" <> + "Content-Transfer-Encoding: base64\r\n" <> + "\r\n" <> + "#{SMTPAdapter.base64_and_split(bamboo_email.html_body)}\r\n" + ) + + assert String.contains?( + raw_email, + "--#{multipart_header}\r\n" <> + "Content-Type: text/plain;charset=UTF-8\r\n" <> + "\r\n" + ) + + assert_configuration(bamboo_config, gen_smtp_config) + end + + test "email have a Content-ID properly set when attaching files with content_id" do + bamboo_email = + new_email() + |> Bamboo.Email.put_attachment(Path.absname("test/attachments/attachment_one.txt"), + content_id: "12345" + ) + |> Bamboo.Email.put_attachment(Path.absname("test/attachments/attachment_two.txt"), + content_id: "54321" + ) + + bamboo_config = configuration() + + {:ok, "200 Ok 1234567890"} = SMTPAdapter.deliver(bamboo_email, bamboo_config) + + assert 1 = length(FakeGenSMTP.fetch_sent_emails()) + + [{{_from, _to, raw_email}, _gen_smtp_config}] = FakeGenSMTP.fetch_sent_emails() + + assert Regex.run(~r{Content-ID: <12345>\r\n}, raw_email, capture: :all_but_first) + assert Regex.run(~r{Content-ID: <54321>\r\n}, raw_email, capture: :all_but_first) + end + test "check rfc822 encoding for subject" do bamboo_email = @email_in_utf8 @@ -709,7 +786,17 @@ defmodule Bamboo.SMTPAdapterTest do end defp rfc822_encode(content) do - "=?UTF-8?B?#{Base.encode64(content)}?=" + if contains_only_ascii_characters?(content) do + "=?UTF-8?B?#{content}?=" + else + "=?UTF-8?B?#{Base.encode64(content)}?=" + end + end + + defp contains_only_ascii_characters?(content) do + content + |> String.to_charlist() + |> List.ascii_printable?() end defp assert_configuration(bamboo_config, gen_smtp_config) do