From 60c5799065e7f7a965de353ecbf4fec8d41a6fec Mon Sep 17 00:00:00 2001 From: StephaneRob Date: Tue, 16 Mar 2021 13:09:12 +0100 Subject: [PATCH 1/7] feat: add ability to define interceptors --- README.md | 32 ++++++++++++++++++++++++++ lib/bamboo/interceptor.ex | 10 ++++++++ lib/bamboo/mailer.ex | 15 ++++++++++++ mix.exs | 3 ++- mix.lock | 2 ++ test/lib/bamboo/mailer_test.exs | 26 +++++++++++++++++++++ test/support/black_list_interceptor.ex | 13 +++++++++++ test/support/env_interceptor.ex | 9 ++++++++ 8 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 lib/bamboo/interceptor.ex create mode 100644 test/support/black_list_interceptor.ex create mode 100644 test/support/env_interceptor.ex diff --git a/README.md b/README.md index da32d62d..c02ef5ec 100644 --- a/README.md +++ b/README.md @@ -260,6 +260,38 @@ struct directly to Bamboo anywhere it expects an address. See the [`Bamboo.Email`] and [`Bamboo.Formatter`] docs for more information and examples. +## Interceptors + +It's possible to configure per Mailer interceptors. Interceptors allow +to modify / intercept (block) email on the fly. + +```elixir +# some/path/within/your/app/mailer.ex +defmodule MyApp.Mailer do + use Bamboo.Mailer, otp_app: :my_app, interceptors: [MyApp.BlackListInterceptor] +end +``` + +An interceptor must implement the `Bamboo.Interceptor` behaviour. + +```elixir +# some/path/within/your/app/black_list_interceptor.ex +defmodule MyApp.BlackListInterceptor do + use Bamboo.Interceptor + + @black_list ["bar@foo.com"] + + def call(email) do + if email.to in @black_list do + :intercepted + else + email + end + end +end +``` + + ## Using Phoenix Views and Layouts Phoenix is not required to use Bamboo. But if you want to use Phoenix's views diff --git a/lib/bamboo/interceptor.ex b/lib/bamboo/interceptor.ex new file mode 100644 index 00000000..fb8e195f --- /dev/null +++ b/lib/bamboo/interceptor.ex @@ -0,0 +1,10 @@ +defmodule Bamboo.Interceptor do + @callback call(email :: Bamboo.Email.t()) :: Bamboo.Email.t() | :intercepted + + defmacro __using__(_) do + quote do + @behaviour Bamboo.Interceptor + def call(:intercepted), do: :intercepted + end + end +end diff --git a/lib/bamboo/mailer.ex b/lib/bamboo/mailer.ex index 9083585f..70730219 100644 --- a/lib/bamboo/mailer.ex +++ b/lib/bamboo/mailer.ex @@ -67,9 +67,13 @@ defmodule Bamboo.Mailer do {:ok, Bamboo.Email.t()} | {:ok, Bamboo.Email.t(), any} | {:error, Exception.t() | String.t()} + + interceptors = Keyword.get(opts, :interceptors, []) + def deliver_now(email, opts \\ []) do {config, opts} = Keyword.split(opts, [:config]) config = build_config(config) + email = Bamboo.Mailer.intercept(email, unquote(interceptors)) Bamboo.Mailer.deliver_now(config.adapter, email, config, opts) end @@ -77,6 +81,7 @@ defmodule Bamboo.Mailer do def deliver_now!(email, opts \\ []) do {config, opts} = Keyword.split(opts, [:config]) config = build_config(config) + email = Bamboo.Mailer.intercept(email, unquote(interceptors)) Bamboo.Mailer.deliver_now!(config.adapter, email, config, opts) end @@ -84,12 +89,14 @@ defmodule Bamboo.Mailer do {:ok, Bamboo.Email.t()} | {:error, Exception.t() | String.t()} def deliver_later(email, opts \\ []) do config = build_config(opts) + email = Bamboo.Mailer.intercept(email, unquote(interceptors)) Bamboo.Mailer.deliver_later(config.adapter, email, config) end @spec deliver_later!(Bamboo.Email.t()) :: Bamboo.Email.t() def deliver_later!(email, opts \\ []) do config = build_config(opts) + email = Bamboo.Mailer.intercept(email, unquote(interceptors)) Bamboo.Mailer.deliver_later!(config.adapter, email, config) end @@ -194,6 +201,8 @@ defmodule Bamboo.Mailer do end @doc false + def deliver_now(_, :intercepted, _, _), do: nil + def deliver_now(adapter, email, config, opts) do with {:ok, email} <- validate_and_normalize(email, adapter) do if empty_recipients?(email) do @@ -211,6 +220,12 @@ defmodule Bamboo.Mailer do end end + def intercept(email, interceptors) do + Enum.reduce(interceptors, email, fn interceptor, email -> + apply(interceptor, :call, [email]) + end) + end + defp format_response(email, response, opts) do put_response = Keyword.get(opts, :response, false) diff --git a/mix.exs b/mix.exs index 0d3f4653..2a6568d0 100644 --- a/mix.exs +++ b/mix.exs @@ -67,7 +67,8 @@ defmodule Bamboo.Mixfile do {:floki, "~> 0.29", only: :test}, {:ex_doc, "~> 0.23", only: :dev}, {:hackney, ">= 1.15.2"}, - {:jason, "~> 1.0", optional: true} + {:jason, "~> 1.0", optional: true}, + {:mix_test_watch, "~> 1.0", only: :dev, runtime: false} ] end end diff --git a/mix.lock b/mix.lock index 65dd2dc4..0d411188 100644 --- a/mix.lock +++ b/mix.lock @@ -7,6 +7,7 @@ "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"}, "ex_machina": {:hex, :ex_machina, "2.4.0", "09a34c5d371bfb5f78399029194a8ff67aff340ebe8ba19040181af35315eabb", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm", "a20bc9ddc721b33ea913b93666c5d0bdca5cbad7a67540784ae277228832d72c"}, "excoveralls": {:hex, :excoveralls, "0.13.2", "5ca05099750c086f144fcf75842c363fc15d7d9c6faa7ad323d010294ced685e", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "1e7ed75c158808a5a8f019d3ad63a5efe482994f2f8336c0a8c77d2f0ab152ce"}, + "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, "floki": {:hex, :floki, "0.29.0", "b1710d8c93a2f860dc2d7adc390dd808dc2fb8f78ee562304457b75f4c640881", [:mix], [{:html_entities, "~> 0.5.0", [hex: :html_entities, repo: "hexpm", optional: false]}], "hexpm", "008585ce64b9f74c07d32958ec9866f4b8a124bf4da1e2941b28e41384edaaad"}, "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"}, "html_entities": {:hex, :html_entities, "0.5.1", "1c9715058b42c35a2ab65edc5b36d0ea66dd083767bef6e3edb57870ef556549", [:mix], [], "hexpm", "30efab070904eb897ff05cd52fa61c1025d7f8ef3a9ca250bc4e6513d16c32de"}, @@ -17,6 +18,7 @@ "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, "mime": {:hex, :mime, "1.4.0", "5066f14944b470286146047d2f73518cf5cca82f8e4815cf35d196b58cf07c47", [:mix], [], "hexpm", "75fa42c4228ea9a23f70f123c74ba7cece6a03b1fd474fe13f6a7a85c6ea4ff6"}, "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, + "mix_test_watch": {:hex, :mix_test_watch, "1.0.2", "34900184cbbbc6b6ed616ed3a8ea9b791f9fd2088419352a6d3200525637f785", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}], "hexpm", "47ac558d8b06f684773972c6d04fcc15590abdb97aeb7666da19fcbfdc441a07"}, "mochiweb": {:hex, :mochiweb, "2.15.0", "e1daac474df07651e5d17cc1e642c4069c7850dc4508d3db7263a0651330aacc", [:rebar3], [], "hexpm", "b960d1cbcf40a30963eeee90ab7aeae074cbfa9a238561fb4434add1afc3075c"}, "nimble_parsec": {:hex, :nimble_parsec, "1.1.0", "3a6fca1550363552e54c216debb6a9e95bd8d32348938e13de5eda962c0d7f89", [:mix], [], "hexpm", "08eb32d66b706e913ff748f11694b17981c0b04a33ef470e33e11b3d3ac8f54b"}, "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm", "17ef63abde837ad30680ea7f857dd9e7ced9476cdd7b0394432af4bfc241b960"}, diff --git a/test/lib/bamboo/mailer_test.exs b/test/lib/bamboo/mailer_test.exs index cb7412f5..72ef1536 100644 --- a/test/lib/bamboo/mailer_test.exs +++ b/test/lib/bamboo/mailer_test.exs @@ -19,6 +19,12 @@ defmodule Bamboo.MailerTest do defmodule(Mailer, do: use(Bamboo.Mailer, otp_app: :bamboo)) + defmodule MailerWithInterceptors do + use Bamboo.Mailer, + otp_app: :bamboo, + interceptors: [Bamboo.BlackListInterceptor, Bamboo.EnvInterceptor] + end + defmodule DefaultAdapter do def deliver(email, config) do send(:mailer_test, {:deliver, email, config}) @@ -447,6 +453,26 @@ defmodule Bamboo.MailerTest do end end + describe "interceptors" do + setup do + Application.put_env(:bamboo, __MODULE__.MailerWithInterceptors, @mailer_config) + end + + test "must apply interceptor and send email if not intercepted" do + email = new_email(to: "foo@bar.com") + MailerWithInterceptors.deliver_now(email) + + assert_receive {:deliver, %Bamboo.Email{to: [{nil, "foo@bar.com"}], subject: "test - "}, + _config} + end + + test "must apply interceptor and block email if intercepted" do + email = new_email(to: "bar@foo.com") + MailerWithInterceptors.deliver_now(email) + refute_receive {:deliver, %Bamboo.Email{to: [{nil, "bar@foo.com"}]}, _config} + end + end + defp new_email(attrs \\ []) do attrs = Keyword.merge([from: "foo@bar.com", to: "foo@bar.com"], attrs) Email.new_email(attrs) diff --git a/test/support/black_list_interceptor.ex b/test/support/black_list_interceptor.ex new file mode 100644 index 00000000..cf3c7d58 --- /dev/null +++ b/test/support/black_list_interceptor.ex @@ -0,0 +1,13 @@ +defmodule Bamboo.BlackListInterceptor do + use Bamboo.Interceptor + + @black_list ["bar@foo.com"] + + def call(email) do + if email.to in @black_list do + :intercepted + else + email + end + end +end diff --git a/test/support/env_interceptor.ex b/test/support/env_interceptor.ex new file mode 100644 index 00000000..62cdda27 --- /dev/null +++ b/test/support/env_interceptor.ex @@ -0,0 +1,9 @@ +defmodule Bamboo.EnvInterceptor do + use Bamboo.Interceptor + + @env Mix.env() + + def call(email) do + %{email | subject: "#{@env} - #{email.subject}"} + end +end From e1a05921e88499e606a3aa6e869c80c52f2495d4 Mon Sep 17 00:00:00 2001 From: StephaneRob Date: Fri, 26 Mar 2021 21:53:35 +0100 Subject: [PATCH 2/7] Rework interceptors for all deliver methods --- README.md | 23 ++++++----- lib/bamboo/email.ex | 10 ++++- lib/bamboo/interceptor.ex | 30 +++++++++++---- lib/bamboo/mailer.ex | 30 +++++++-------- mix.exs | 3 +- mix.lock | 1 - test/lib/bamboo/email_test.exs | 6 +++ test/lib/bamboo/mailer_test.exs | 53 +++++++++++++------------- test/support/black_list_interceptor.ex | 13 ------- test/support/deny_list_interceptor.ex | 13 +++++++ test/support/env_interceptor.ex | 2 +- 11 files changed, 102 insertions(+), 82 deletions(-) delete mode 100644 test/support/black_list_interceptor.ex create mode 100644 test/support/deny_list_interceptor.ex diff --git a/README.md b/README.md index c02ef5ec..46fa972d 100644 --- a/README.md +++ b/README.md @@ -266,24 +266,24 @@ It's possible to configure per Mailer interceptors. Interceptors allow to modify / intercept (block) email on the fly. ```elixir -# some/path/within/your/app/mailer.ex -defmodule MyApp.Mailer do - use Bamboo.Mailer, otp_app: :my_app, interceptors: [MyApp.BlackListInterceptor] +# config/config.exs +config :my_app, MyApp.Mailer, + adapter: Bamboo.MandrillAdapter, + interceptors: [MyApp.DenyListInterceptor] end ``` -An interceptor must implement the `Bamboo.Interceptor` behaviour. +An interceptor must implement the `Bamboo.Interceptor` behaviour. To prevent email being sent, you can intercept it with `Bamboo.Email.intercept/1` ```elixir -# some/path/within/your/app/black_list_interceptor.ex -defmodule MyApp.BlackListInterceptor do - use Bamboo.Interceptor - - @black_list ["bar@foo.com"] +# some/path/within/your/app/deny_list_interceptor.ex +defmodule MyApp.DenyListInterceptor do + @behaviour Bamboo.Interceptor + @deny_list ["bar@foo.com"] def call(email) do - if email.to in @black_list do - :intercepted + if email.to in @deny_list do + Bamboo.Email.intercept(email) else email end @@ -291,7 +291,6 @@ defmodule MyApp.BlackListInterceptor do end ``` - ## Using Phoenix Views and Layouts Phoenix is not required to use Bamboo. But if you want to use Phoenix's views diff --git a/lib/bamboo/email.ex b/lib/bamboo/email.ex index d36b7a3c..7e3913b7 100644 --- a/lib/bamboo/email.ex +++ b/lib/bamboo/email.ex @@ -83,7 +83,8 @@ defmodule Bamboo.Email do text_body: nil | String.t(), headers: %{String.t() => String.t()}, assigns: %{atom => any}, - private: %{atom => any} + private: %{atom => any}, + intercepted: boolean() } defstruct from: nil, @@ -96,7 +97,8 @@ defmodule Bamboo.Email do headers: %{}, attachments: [], assigns: %{}, - private: %{} + private: %{}, + intercepted: false alias Bamboo.{Email, Attachment} @@ -263,4 +265,8 @@ defmodule Bamboo.Email do def put_attachment(%__MODULE__{attachments: attachments} = email, path, opts \\ []) do %{email | attachments: [Bamboo.Attachment.new(path, opts) | attachments]} end + + def intercept(email) do + %{email | intercepted: true} + end end diff --git a/lib/bamboo/interceptor.ex b/lib/bamboo/interceptor.ex index fb8e195f..0f1ca737 100644 --- a/lib/bamboo/interceptor.ex +++ b/lib/bamboo/interceptor.ex @@ -1,10 +1,24 @@ defmodule Bamboo.Interceptor do - @callback call(email :: Bamboo.Email.t()) :: Bamboo.Email.t() | :intercepted - - defmacro __using__(_) do - quote do - @behaviour Bamboo.Interceptor - def call(:intercepted), do: :intercepted - end - end + @moduledoc ~S""" + Behaviour for creating an Interceptor. + + An interceptor allow to modify / block an email before it is sent. To block an email, it must be marked as intercepted with `Bamboo.Email.intercept/1`. + + ## Example + + defmodule Bamboo.DenyListInterceptor do + @behaviour Bamboo.Interceptor + @deny_list ["bar@foo.com"] + + def call(email) do + if email.to in @deny_list do + Bamboo.Email.intercept(email) + else + email + end + end + end + """ + + @callback call(email :: Bamboo.Email.t()) :: Bamboo.Email.t() end diff --git a/lib/bamboo/mailer.ex b/lib/bamboo/mailer.ex index 70730219..627710c5 100644 --- a/lib/bamboo/mailer.ex +++ b/lib/bamboo/mailer.ex @@ -68,12 +68,9 @@ defmodule Bamboo.Mailer do | {:ok, Bamboo.Email.t(), any} | {:error, Exception.t() | String.t()} - interceptors = Keyword.get(opts, :interceptors, []) - def deliver_now(email, opts \\ []) do {config, opts} = Keyword.split(opts, [:config]) config = build_config(config) - email = Bamboo.Mailer.intercept(email, unquote(interceptors)) Bamboo.Mailer.deliver_now(config.adapter, email, config, opts) end @@ -81,7 +78,6 @@ defmodule Bamboo.Mailer do def deliver_now!(email, opts \\ []) do {config, opts} = Keyword.split(opts, [:config]) config = build_config(config) - email = Bamboo.Mailer.intercept(email, unquote(interceptors)) Bamboo.Mailer.deliver_now!(config.adapter, email, config, opts) end @@ -89,14 +85,12 @@ defmodule Bamboo.Mailer do {:ok, Bamboo.Email.t()} | {:error, Exception.t() | String.t()} def deliver_later(email, opts \\ []) do config = build_config(opts) - email = Bamboo.Mailer.intercept(email, unquote(interceptors)) Bamboo.Mailer.deliver_later(config.adapter, email, config) end @spec deliver_later!(Bamboo.Email.t()) :: Bamboo.Email.t() def deliver_later!(email, opts \\ []) do config = build_config(opts) - email = Bamboo.Mailer.intercept(email, unquote(interceptors)) Bamboo.Mailer.deliver_later!(config.adapter, email, config) end @@ -201,10 +195,9 @@ defmodule Bamboo.Mailer do end @doc false - def deliver_now(_, :intercepted, _, _), do: nil - def deliver_now(adapter, email, config, opts) do - with {:ok, email} <- validate_and_normalize(email, adapter) do + with %Bamboo.Email{intercepted: false} = email <- apply_interceptors(email, config), + {:ok, email} <- validate_and_normalize(email, adapter) do if empty_recipients?(email) do debug_unsent(email) @@ -220,12 +213,6 @@ defmodule Bamboo.Mailer do end end - def intercept(email, interceptors) do - Enum.reduce(interceptors, email, fn interceptor, email -> - apply(interceptor, :call, [email]) - end) - end - defp format_response(email, response, opts) do put_response = Keyword.get(opts, :response, false) @@ -242,12 +229,14 @@ defmodule Bamboo.Mailer do {:ok, email, response} -> {email, response} {:ok, email} -> email {:error, error} -> raise error + %Bamboo.Email{intercepted: true} = email -> email end end @doc false def deliver_later(adapter, email, config) do - with {:ok, email} <- validate_and_normalize(email, adapter) do + with %Bamboo.Email{intercepted: false} = email <- apply_interceptors(email, config), + {:ok, email} <- validate_and_normalize(email, adapter) do if empty_recipients?(email) do debug_unsent(email) else @@ -264,6 +253,7 @@ defmodule Bamboo.Mailer do case deliver_later(adapter, email, config) do {:ok, email} -> email {:error, error} -> raise error + %Bamboo.Email{intercepted: true} = email -> email end end @@ -348,6 +338,14 @@ defmodule Bamboo.Mailer do defp is_nil_recipient?(_), do: false + defp apply_interceptors(email, config) do + interceptors = config[:interceptors] || [] + + Enum.reduce(interceptors, email, fn interceptor, email -> + apply(interceptor, :call, [email]) + end) + end + @doc """ Wraps to, cc and bcc addresses in a list and normalizes email addresses. diff --git a/mix.exs b/mix.exs index 2a6568d0..0d3f4653 100644 --- a/mix.exs +++ b/mix.exs @@ -67,8 +67,7 @@ defmodule Bamboo.Mixfile do {:floki, "~> 0.29", only: :test}, {:ex_doc, "~> 0.23", only: :dev}, {:hackney, ">= 1.15.2"}, - {:jason, "~> 1.0", optional: true}, - {:mix_test_watch, "~> 1.0", only: :dev, runtime: false} + {:jason, "~> 1.0", optional: true} ] end end diff --git a/mix.lock b/mix.lock index 0d411188..2b5ec519 100644 --- a/mix.lock +++ b/mix.lock @@ -18,7 +18,6 @@ "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, "mime": {:hex, :mime, "1.4.0", "5066f14944b470286146047d2f73518cf5cca82f8e4815cf35d196b58cf07c47", [:mix], [], "hexpm", "75fa42c4228ea9a23f70f123c74ba7cece6a03b1fd474fe13f6a7a85c6ea4ff6"}, "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, - "mix_test_watch": {:hex, :mix_test_watch, "1.0.2", "34900184cbbbc6b6ed616ed3a8ea9b791f9fd2088419352a6d3200525637f785", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}], "hexpm", "47ac558d8b06f684773972c6d04fcc15590abdb97aeb7666da19fcbfdc441a07"}, "mochiweb": {:hex, :mochiweb, "2.15.0", "e1daac474df07651e5d17cc1e642c4069c7850dc4508d3db7263a0651330aacc", [:rebar3], [], "hexpm", "b960d1cbcf40a30963eeee90ab7aeae074cbfa9a238561fb4434add1afc3075c"}, "nimble_parsec": {:hex, :nimble_parsec, "1.1.0", "3a6fca1550363552e54c216debb6a9e95bd8d32348938e13de5eda962c0d7f89", [:mix], [], "hexpm", "08eb32d66b706e913ff748f11694b17981c0b04a33ef470e33e11b3d3ac8f54b"}, "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm", "17ef63abde837ad30680ea7f857dd9e7ced9476cdd7b0394432af4bfc241b960"}, diff --git a/test/lib/bamboo/email_test.exs b/test/lib/bamboo/email_test.exs index fbdfdbd6..04e873d9 100644 --- a/test/lib/bamboo/email_test.exs +++ b/test/lib/bamboo/email_test.exs @@ -116,4 +116,10 @@ defmodule Bamboo.EmailTest do assert [%Bamboo.Attachment{filename: "attachment.docx"}] = email.attachments end + + test "intercept/1 mark email as intercepted" do + email = new_email() + refute email.intercepted + assert %Bamboo.Email{intercepted: true} = intercept(email) + end end diff --git a/test/lib/bamboo/mailer_test.exs b/test/lib/bamboo/mailer_test.exs index 72ef1536..48443421 100644 --- a/test/lib/bamboo/mailer_test.exs +++ b/test/lib/bamboo/mailer_test.exs @@ -2,14 +2,18 @@ defmodule Bamboo.MailerTest do use ExUnit.Case alias Bamboo.Email - @mailer_config adapter: __MODULE__.DefaultAdapter, foo: :bar + @mailer_config adapter: __MODULE__.DefaultAdapter, foo: :bar, interceptors: nil setup context do config = - Keyword.merge(@mailer_config, [adapter: context[:adapter]], fn - _key, default, nil -> default - _key, _default, override -> override - end) + Keyword.merge( + @mailer_config, + [adapter: context[:adapter], interceptors: context[:interceptors]], + fn + _key, default, nil -> default + _key, _default, override -> override + end + ) Application.put_env(:bamboo, __MODULE__.Mailer, config) Process.register(self(), :mailer_test) @@ -19,12 +23,6 @@ defmodule Bamboo.MailerTest do defmodule(Mailer, do: use(Bamboo.Mailer, otp_app: :bamboo)) - defmodule MailerWithInterceptors do - use Bamboo.Mailer, - otp_app: :bamboo, - interceptors: [Bamboo.BlackListInterceptor, Bamboo.EnvInterceptor] - end - defmodule DefaultAdapter do def deliver(email, config) do send(:mailer_test, {:deliver, email, config}) @@ -454,23 +452,24 @@ defmodule Bamboo.MailerTest do end describe "interceptors" do - setup do - Application.put_env(:bamboo, __MODULE__.MailerWithInterceptors, @mailer_config) - end - - test "must apply interceptor and send email if not intercepted" do - email = new_email(to: "foo@bar.com") - MailerWithInterceptors.deliver_now(email) - - assert_receive {:deliver, %Bamboo.Email{to: [{nil, "foo@bar.com"}], subject: "test - "}, - _config} - end + [:deliver_now, :deliver_now!, :deliver_later, :deliver_later!] + |> Enum.each(fn method -> + @tag interceptors: [Bamboo.DenyListInterceptor, Bamboo.EnvInterceptor] + test "#{method}/1 &must apply interceptor and send email if not intercepted" do + email = new_email(to: "foo@bar.com") + apply(Mailer, unquote(method), [email]) + + assert_receive {:deliver, %Bamboo.Email{to: [{nil, "foo@bar.com"}], subject: "test - "}, + _config} + end - test "must apply interceptor and block email if intercepted" do - email = new_email(to: "bar@foo.com") - MailerWithInterceptors.deliver_now(email) - refute_receive {:deliver, %Bamboo.Email{to: [{nil, "bar@foo.com"}]}, _config} - end + @tag interceptors: [Bamboo.DenyListInterceptor, Bamboo.EnvInterceptor] + test "#{method}/1 &must apply interceptor and block email if intercepted" do + email = new_email(to: "blocked@blocked.com") + %Bamboo.Email{intercepted: true} = apply(Mailer, unquote(method), [email]) + refute_receive {:deliver, %Bamboo.Email{to: [{nil, "blocked@blocked.com"}]}, _config} + end + end) end defp new_email(attrs \\ []) do diff --git a/test/support/black_list_interceptor.ex b/test/support/black_list_interceptor.ex deleted file mode 100644 index cf3c7d58..00000000 --- a/test/support/black_list_interceptor.ex +++ /dev/null @@ -1,13 +0,0 @@ -defmodule Bamboo.BlackListInterceptor do - use Bamboo.Interceptor - - @black_list ["bar@foo.com"] - - def call(email) do - if email.to in @black_list do - :intercepted - else - email - end - end -end diff --git a/test/support/deny_list_interceptor.ex b/test/support/deny_list_interceptor.ex new file mode 100644 index 00000000..c54aee79 --- /dev/null +++ b/test/support/deny_list_interceptor.ex @@ -0,0 +1,13 @@ +defmodule Bamboo.DenyListInterceptor do + @behaviour Bamboo.Interceptor + + @deny_list ["blocked@blocked.com"] + + def call(email) do + if email.to in @deny_list do + Bamboo.Email.intercept(email) + else + email + end + end +end diff --git a/test/support/env_interceptor.ex b/test/support/env_interceptor.ex index 62cdda27..632933d9 100644 --- a/test/support/env_interceptor.ex +++ b/test/support/env_interceptor.ex @@ -1,5 +1,5 @@ defmodule Bamboo.EnvInterceptor do - use Bamboo.Interceptor + @behaviour Bamboo.Interceptor @env Mix.env() From 5f3eb73cc1e32c60fee700fd1e2c36ee9a7e29e5 Mon Sep 17 00:00:00 2001 From: StephaneRob Date: Sat, 27 Mar 2021 09:50:37 +0100 Subject: [PATCH 3/7] clean mix lock MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Robino --- mix.lock | 1 - 1 file changed, 1 deletion(-) diff --git a/mix.lock b/mix.lock index 2b5ec519..65dd2dc4 100644 --- a/mix.lock +++ b/mix.lock @@ -7,7 +7,6 @@ "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"}, "ex_machina": {:hex, :ex_machina, "2.4.0", "09a34c5d371bfb5f78399029194a8ff67aff340ebe8ba19040181af35315eabb", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm", "a20bc9ddc721b33ea913b93666c5d0bdca5cbad7a67540784ae277228832d72c"}, "excoveralls": {:hex, :excoveralls, "0.13.2", "5ca05099750c086f144fcf75842c363fc15d7d9c6faa7ad323d010294ced685e", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "1e7ed75c158808a5a8f019d3ad63a5efe482994f2f8336c0a8c77d2f0ab152ce"}, - "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, "floki": {:hex, :floki, "0.29.0", "b1710d8c93a2f860dc2d7adc390dd808dc2fb8f78ee562304457b75f4c640881", [:mix], [{:html_entities, "~> 0.5.0", [hex: :html_entities, repo: "hexpm", optional: false]}], "hexpm", "008585ce64b9f74c07d32958ec9866f4b8a124bf4da1e2941b28e41384edaaad"}, "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"}, "html_entities": {:hex, :html_entities, "0.5.1", "1c9715058b42c35a2ab65edc5b36d0ea66dd083767bef6e3edb57870ef556549", [:mix], [], "hexpm", "30efab070904eb897ff05cd52fa61c1025d7f8ef3a9ca250bc4e6513d16c32de"}, From 8322137f796dfe9ce114b1b35b5a7099fd0d966e Mon Sep 17 00:00:00 2001 From: StephaneRob Date: Sat, 27 Mar 2021 15:19:53 +0100 Subject: [PATCH 4/7] validate and normalize before applying interceptors --- lib/bamboo/mailer.ex | 8 ++++---- test/support/deny_list_interceptor.ex | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/bamboo/mailer.ex b/lib/bamboo/mailer.ex index 627710c5..2e0ac856 100644 --- a/lib/bamboo/mailer.ex +++ b/lib/bamboo/mailer.ex @@ -196,8 +196,8 @@ defmodule Bamboo.Mailer do @doc false def deliver_now(adapter, email, config, opts) do - with %Bamboo.Email{intercepted: false} = email <- apply_interceptors(email, config), - {:ok, email} <- validate_and_normalize(email, adapter) do + with {:ok, email} <- validate_and_normalize(email, adapter), + %Bamboo.Email{intercepted: false} = email <- apply_interceptors(email, config) do if empty_recipients?(email) do debug_unsent(email) @@ -235,8 +235,8 @@ defmodule Bamboo.Mailer do @doc false def deliver_later(adapter, email, config) do - with %Bamboo.Email{intercepted: false} = email <- apply_interceptors(email, config), - {:ok, email} <- validate_and_normalize(email, adapter) do + with {:ok, email} <- validate_and_normalize(email, adapter), + %Bamboo.Email{intercepted: false} = email <- apply_interceptors(email, config) do if empty_recipients?(email) do debug_unsent(email) else diff --git a/test/support/deny_list_interceptor.ex b/test/support/deny_list_interceptor.ex index c54aee79..29185e13 100644 --- a/test/support/deny_list_interceptor.ex +++ b/test/support/deny_list_interceptor.ex @@ -4,7 +4,7 @@ defmodule Bamboo.DenyListInterceptor do @deny_list ["blocked@blocked.com"] def call(email) do - if email.to in @deny_list do + if Enum.any?(email.to, &(elem(&1, 1) in @deny_list)) do Bamboo.Email.intercept(email) else email From 596d99915328ebb1a01216a9072e21f0f1f84085 Mon Sep 17 00:00:00 2001 From: StephaneRob Date: Sat, 3 Apr 2021 15:06:23 +0200 Subject: [PATCH 5/7] Cover all deliver method, use bloked instead of intercepted --- README.md | 6 +- lib/bamboo/email.ex | 8 +-- lib/bamboo/mailer.ex | 12 ++-- test/lib/bamboo/email_test.exs | 6 +- test/lib/bamboo/mailer_test.exs | 83 +++++++++++++++++++++------ test/support/deny_list_interceptor.ex | 2 +- 6 files changed, 85 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 46fa972d..4ddb12be 100644 --- a/README.md +++ b/README.md @@ -273,7 +273,7 @@ config :my_app, MyApp.Mailer, end ``` -An interceptor must implement the `Bamboo.Interceptor` behaviour. To prevent email being sent, you can intercept it with `Bamboo.Email.intercept/1` +An interceptor must implement the `Bamboo.Interceptor` behaviour. To prevent email being sent, you can block it with `Bamboo.Email.block/1`. ```elixir # some/path/within/your/app/deny_list_interceptor.ex @@ -283,7 +283,7 @@ defmodule MyApp.DenyListInterceptor do def call(email) do if email.to in @deny_list do - Bamboo.Email.intercept(email) + Bamboo.Email.block(email) else email end @@ -297,7 +297,7 @@ Phoenix is not required to use Bamboo. But if you want to use Phoenix's views and layouts to render emails, see [`bamboo_phoenix`] and [`Bambooo.Phoenix`]. [`bamboo_phoenix`]: https://github.com/thoughtbot/bamboo_phoenix -[`Bambooo.Phoenix`]: https://hexdocs.pm/bamboo_phoenix/Bamboo.Phoenix.html +[`bambooo.phoenix`]: https://hexdocs.pm/bamboo_phoenix/Bamboo.Phoenix.html ## Viewing Sent Emails diff --git a/lib/bamboo/email.ex b/lib/bamboo/email.ex index 7e3913b7..b880faf5 100644 --- a/lib/bamboo/email.ex +++ b/lib/bamboo/email.ex @@ -84,7 +84,7 @@ defmodule Bamboo.Email do headers: %{String.t() => String.t()}, assigns: %{atom => any}, private: %{atom => any}, - intercepted: boolean() + blocked: boolean() } defstruct from: nil, @@ -98,7 +98,7 @@ defmodule Bamboo.Email do attachments: [], assigns: %{}, private: %{}, - intercepted: false + blocked: false alias Bamboo.{Email, Attachment} @@ -266,7 +266,7 @@ defmodule Bamboo.Email do %{email | attachments: [Bamboo.Attachment.new(path, opts) | attachments]} end - def intercept(email) do - %{email | intercepted: true} + def block(email) do + %{email | blocked: true} end end diff --git a/lib/bamboo/mailer.ex b/lib/bamboo/mailer.ex index 2e0ac856..ef14074d 100644 --- a/lib/bamboo/mailer.ex +++ b/lib/bamboo/mailer.ex @@ -197,7 +197,7 @@ defmodule Bamboo.Mailer do @doc false def deliver_now(adapter, email, config, opts) do with {:ok, email} <- validate_and_normalize(email, adapter), - %Bamboo.Email{intercepted: false} = email <- apply_interceptors(email, config) do + %Bamboo.Email{blocked: false} = email <- apply_interceptors(email, config) do if empty_recipients?(email) do debug_unsent(email) @@ -210,6 +210,9 @@ defmodule Bamboo.Mailer do {:error, _} = error -> error end end + else + %Bamboo.Email{blocked: true} = email -> {:ok, email} + response -> response end end @@ -229,14 +232,13 @@ defmodule Bamboo.Mailer do {:ok, email, response} -> {email, response} {:ok, email} -> email {:error, error} -> raise error - %Bamboo.Email{intercepted: true} = email -> email end end @doc false def deliver_later(adapter, email, config) do with {:ok, email} <- validate_and_normalize(email, adapter), - %Bamboo.Email{intercepted: false} = email <- apply_interceptors(email, config) do + %Bamboo.Email{blocked: false} = email <- apply_interceptors(email, config) do if empty_recipients?(email) do debug_unsent(email) else @@ -245,6 +247,9 @@ defmodule Bamboo.Mailer do end {:ok, email} + else + %Bamboo.Email{blocked: true} = email -> {:ok, email} + response -> response end end @@ -253,7 +258,6 @@ defmodule Bamboo.Mailer do case deliver_later(adapter, email, config) do {:ok, email} -> email {:error, error} -> raise error - %Bamboo.Email{intercepted: true} = email -> email end end diff --git a/test/lib/bamboo/email_test.exs b/test/lib/bamboo/email_test.exs index 04e873d9..601b635b 100644 --- a/test/lib/bamboo/email_test.exs +++ b/test/lib/bamboo/email_test.exs @@ -117,9 +117,9 @@ defmodule Bamboo.EmailTest do assert [%Bamboo.Attachment{filename: "attachment.docx"}] = email.attachments end - test "intercept/1 mark email as intercepted" do + test "block/1 mark email as intercepted" do email = new_email() - refute email.intercepted - assert %Bamboo.Email{intercepted: true} = intercept(email) + refute email.blocked + assert %Bamboo.Email{blocked: true} = block(email) end end diff --git a/test/lib/bamboo/mailer_test.exs b/test/lib/bamboo/mailer_test.exs index 48443421..f7c4b01a 100644 --- a/test/lib/bamboo/mailer_test.exs +++ b/test/lib/bamboo/mailer_test.exs @@ -452,24 +452,73 @@ defmodule Bamboo.MailerTest do end describe "interceptors" do - [:deliver_now, :deliver_now!, :deliver_later, :deliver_later!] - |> Enum.each(fn method -> - @tag interceptors: [Bamboo.DenyListInterceptor, Bamboo.EnvInterceptor] - test "#{method}/1 &must apply interceptor and send email if not intercepted" do - email = new_email(to: "foo@bar.com") - apply(Mailer, unquote(method), [email]) - - assert_receive {:deliver, %Bamboo.Email{to: [{nil, "foo@bar.com"}], subject: "test - "}, - _config} - end + @tag interceptors: [Bamboo.DenyListInterceptor, Bamboo.EnvInterceptor] + test "deliver_now/1 must apply interceptors and send email if not intercepted" do + email = new_email(to: "foo@bar.com") + assert {:ok, %Bamboo.Email{blocked: false}} = Mailer.deliver_now(email) - @tag interceptors: [Bamboo.DenyListInterceptor, Bamboo.EnvInterceptor] - test "#{method}/1 &must apply interceptor and block email if intercepted" do - email = new_email(to: "blocked@blocked.com") - %Bamboo.Email{intercepted: true} = apply(Mailer, unquote(method), [email]) - refute_receive {:deliver, %Bamboo.Email{to: [{nil, "blocked@blocked.com"}]}, _config} - end - end) + assert_receive {:deliver, %Bamboo.Email{to: [{nil, "foo@bar.com"}], subject: "test - "}, + _config} + end + + @tag interceptors: [Bamboo.DenyListInterceptor, Bamboo.EnvInterceptor] + test "deliver_now/1 must apply interceptors and block email if intercepted" do + email = new_email(to: "blocked@blocked.com") + assert {:ok, %Bamboo.Email{blocked: true}} = Mailer.deliver_now(email) + refute_receive {:deliver, %Bamboo.Email{to: [{nil, "blocked@blocked.com"}]}, _config} + end + + @tag interceptors: [Bamboo.DenyListInterceptor, Bamboo.EnvInterceptor] + test "deliver_now!/1 must apply interceptors and send email if not intercepted" do + email = new_email(to: "foo@bar.com") + assert %Bamboo.Email{blocked: false} = Mailer.deliver_now!(email) + + assert_receive {:deliver, %Bamboo.Email{to: [{nil, "foo@bar.com"}], subject: "test - "}, + _config} + end + + @tag interceptors: [Bamboo.DenyListInterceptor, Bamboo.EnvInterceptor] + test "deliver_now!/1 must apply interceptors and block email if intercepted" do + email = new_email(to: "blocked@blocked.com") + + assert %Bamboo.Email{blocked: true} = Mailer.deliver_now!(email) + + refute_receive {:deliver, %Bamboo.Email{to: [{nil, "blocked@blocked.com"}]}, _config} + end + + @tag interceptors: [Bamboo.DenyListInterceptor, Bamboo.EnvInterceptor] + test "deliver_later/1 must apply interceptors and send email if not intercepted" do + email = new_email(to: "foo@bar.com") + assert {:ok, %Bamboo.Email{blocked: false}} = Mailer.deliver_later(email) + + assert_receive {:deliver, %Bamboo.Email{to: [{nil, "foo@bar.com"}], subject: "test - "}, + _config} + end + + @tag interceptors: [Bamboo.DenyListInterceptor, Bamboo.EnvInterceptor] + test "deliver_later/1 must apply interceptors and block email if intercepted" do + email = new_email(to: "blocked@blocked.com") + + assert {:ok, %Bamboo.Email{blocked: true}} = Mailer.deliver_later(email) + + refute_receive {:deliver, %Bamboo.Email{to: [{nil, "blocked@blocked.com"}]}, _config} + end + + @tag interceptors: [Bamboo.DenyListInterceptor, Bamboo.EnvInterceptor] + test "deliver_later!/1 must apply interceptors and send email if not intercepted" do + email = new_email(to: "foo@bar.com") + assert %Bamboo.Email{blocked: false} = Mailer.deliver_later!(email) + + assert_receive {:deliver, %Bamboo.Email{to: [{nil, "foo@bar.com"}], subject: "test - "}, + _config} + end + + @tag interceptors: [Bamboo.DenyListInterceptor, Bamboo.EnvInterceptor] + test "deliver_later!/1 must apply interceptors and block email if intercepted" do + email = new_email(to: "blocked@blocked.com") + assert %Bamboo.Email{blocked: true} = Mailer.deliver_later!(email) + refute_receive {:deliver, %Bamboo.Email{to: [{nil, "blocked@blocked.com"}]}, _config} + end end defp new_email(attrs \\ []) do diff --git a/test/support/deny_list_interceptor.ex b/test/support/deny_list_interceptor.ex index 29185e13..9750657e 100644 --- a/test/support/deny_list_interceptor.ex +++ b/test/support/deny_list_interceptor.ex @@ -5,7 +5,7 @@ defmodule Bamboo.DenyListInterceptor do def call(email) do if Enum.any?(email.to, &(elem(&1, 1) in @deny_list)) do - Bamboo.Email.intercept(email) + Bamboo.Email.block(email) else email end From 6382d8bd0fa20bda5001b3b6047bdf4682a29e1e Mon Sep 17 00:00:00 2001 From: StephaneRob Date: Sat, 3 Apr 2021 15:08:30 +0200 Subject: [PATCH 6/7] remove intercepted wording --- test/lib/bamboo/email_test.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/lib/bamboo/email_test.exs b/test/lib/bamboo/email_test.exs index 601b635b..d13965d0 100644 --- a/test/lib/bamboo/email_test.exs +++ b/test/lib/bamboo/email_test.exs @@ -117,7 +117,7 @@ defmodule Bamboo.EmailTest do assert [%Bamboo.Attachment{filename: "attachment.docx"}] = email.attachments end - test "block/1 mark email as intercepted" do + test "block/1 mark email as blocked" do email = new_email() refute email.blocked assert %Bamboo.Email{blocked: true} = block(email) From 2f0a639d75b087d0c511c64f9db9df3654884e13 Mon Sep 17 00:00:00 2001 From: StephaneRob Date: Sun, 4 Apr 2021 16:05:55 +0200 Subject: [PATCH 7/7] fix README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4ddb12be..ac6d3110 100644 --- a/README.md +++ b/README.md @@ -297,7 +297,7 @@ Phoenix is not required to use Bamboo. But if you want to use Phoenix's views and layouts to render emails, see [`bamboo_phoenix`] and [`Bambooo.Phoenix`]. [`bamboo_phoenix`]: https://github.com/thoughtbot/bamboo_phoenix -[`bambooo.phoenix`]: https://hexdocs.pm/bamboo_phoenix/Bamboo.Phoenix.html +[`Bambooo.Phoenix`]: https://hexdocs.pm/bamboo_phoenix/Bamboo.Phoenix.html ## Viewing Sent Emails