Issue with Bamboo Phoenix put_text_layout/put_layout API after upgrading to Phoenix 1.6

Noticed emails are not being sent while testing phx 1.6 update. Oban is reporting this error(truncated). It dies in (phoenix_view 1.0.0) lib/phoenix/view.ex:442: Phoenix.View.render_to_string/3

Oban:

{"{\"at\": \"2021-09-27T23:36:00.880981Z\", \"error\": \"** (ArgumentError) errors were found at the given arguments:\\n\\n * 1st argument: not an iodata term\\n\\n :erlang.iolist_to_binary(%Phoenix.LiveView.Rendered{dynamic: #Function<5.50417823/1 in FaithfulWordWeb.LayoutView.\\\"email.text\\\"/1>, fingerprint: 260608936060549421614240750281229497011, root: false, static: [\\\"\\\", \\\"\\\"]})\\n (phoenix_view 1.0.0) lib/phoenix/view.ex:442: Phoenix.View.render_to_string/3\\n (bamboo_phoenix 1.0.0) lib/bamboo_phoenix.ex:283: Bamboo.Phoenix.render_html_and_text_emails/1\\n (faithful_word 0.1.0) lib/faithful_word/emails.ex:63: FaithfulWord.Emails.one_time_password_request_email/1\\n (oban 2.7.0) lib/oban/queue/executor.ex:209: Oban.Queue.Executor.perform_inline/1\\n (oban 2.7.0) lib/oban/queue/executor.ex:197: Oban.Queue.Executor.perform_inline/1\\n (oban 2.7.0) lib/oban/queue/executor.ex:82: Oban.Queue.Executor.call/1\\n (elixir 1.12.2) lib/task/supervised.ex:90: Task.Supervised.invoke_mfa/2\\n (elixir 1.12.2) lib/task/supervised.ex:35: Task.Supervised.reply/5\\n (stdlib 3.15.1) proc_lib.erl:226: :proc_lib.init_p_do_apply/3\\n\", \"attempt\": 1}"

dependencies:

  bamboo 2.2.0
  bamboo_phoenix 1.0.0
  oban 2.7.0
  phoenix 1.6.0
  phoenix_ecto 4.3.0
  phoenix_html 3.0.0 RETIRED!
    (security) The :class attribute in content_tag and in class={@class} for HEEx is not escaped against XSS
  phoenix_live_dashboard 0.5.2
  phoenix_live_reload 1.3.3
  phoenix_live_session 0.1.3
  phoenix_live_view 0.16.4
  phoenix_pubsub 2.0.0
  phoenix_view 1.0.0

This is the line where it trips-up:

  defp base_email do
    new_email()
    |> from(@sender_no_reply)
    # Set default layout
    |> put_html_layout({FaithfulWordWeb.LayoutView, "email.html"})
    # Set default text layout
    |> put_text_layout({FaithfulWordWeb.LayoutView, "email.text"}) ## here
  end

checked the docs here: https://hexdocs.pm/bamboo_phoenix/Bamboo.Phoenix.html

and there is another API put_layout()

so I tried it but same issue … any suggestions?

  defp base_email do
    new_email()
    |> from(@sender_no_reply)
    |> put_layout({FaithfulWordWeb.LayoutView, :email})
  end

What extension does email.html has, .eex, .leex, or .heex? I think it needs to be .eex to be rendered directly to iodata or string, the other two would be rendered to Phoenix.LiveView.Rendered.t which seems to be the cause of the problem.

Yeah thanks that is a good catch. I changed the extensions for both those files to .heex but I didn’t convert the contents from legacy eex to heex when I did that.

I reverted the names to

email.html.eex
email.text.eex

but strangely I’m still getting the same error.

email.html.eex:

...
              <tr>
                <td class="content-wrap">
                  <%= @inner_content %>
                </td>
              </tr>
...

email.text.eex:

<%= @inner_content %>

EOF

Seems like bamboo phoenix is not compatible with heex templates.

I had a similar thing occur when updating my emails being sent with Bamboo to .heex. But I don’t believe it’s anything too wrong with Bamboo. I have email.html.heex set and you need to leave the email.text.eex as is.

Then your corresponding templates for html emails are heex syntax and your text emails are as is. I use the put_layout(AppWeb.LayoutView, :email).

I should also mention I use the render function too. On my phone so I’m not sure of the arity/specifics, but you call that with the atom for your email templates (it renders both the html and text).

@maz - here’s an example of how I’m doing it with Bamboo and .heex:

# email.html.heex
<html>
  <head>
    ...
  </head>
  <body>
    <%= @inner_content %>
  </body>
</html>
# email.text.eex
<%= @inner_content %>
# templates/email/example_email_instructions.html.heex
<a href={@url} target="_blank" rel="_noopener"><%= @url %></a>
# templates/email/example_email_instructions.text.eex
<%= @url %>
# mailer.ex
defmodule YourApp.Mailer do
  use Bamboo.Mailer, otp_app: :your_app
end
# your_app/example_emails/example_emails_notifier.ex
defmodule YourApp.ExampleEmails.ExampleEmailsNotifier do
  use Bamboo.Phoenix, view: YourAppWeb.EmailView
  alias YourApp.Mailer

  @from_address "hello@example.com"
  @reply_to_address "reply@example.com"

  def deliver_example_email_instructions(example, url) do
    new_email()
    |> put_layout({YourAppWeb.LayoutView, :email})
    |> to(example.email)
    |> from(@from_address)
    |> put_header("Reply-To", @reply_to_address)
    |> subject("Please confirm your email")
    |> render(:example_email_instructions, %{url: url})
    |> Mailer.deliver_later()
  end
end

And everything works again with heex syntax in my html email templates. :slight_smile:

Thanks for the detailed reply! Emails are working again but I left everything as .eex.

When I made email.html.eexemail.html.heex and applied changes similar to yours, I started going down a really bad rabbit hole when I started changing the *.html.eex*.html.heex.

The Eex parser would not tell me which file there was a failure in(“nofile”):

== Compilation error in file lib/faithful_word_web/views/email_view.ex ==
** (Phoenix.LiveView.HTMLTokenizer.ParseError) nofile:5:10: expected closing `"` for attribute value
    (phoenix_live_view 0.16.0) lib/phoenix_live_view/html_tokenizer.ex:360: Phoenix.LiveView.HTMLTokenizer.handle_attr_value_double_quote/6
    (phoenix_live_view 0.16.0) lib/phoenix_live_view/html_engine.ex:90: Phoenix.LiveView.HTMLEngine.handle_text/3
    (eex 1.12.2) lib/eex/compiler.ex:48: EEx.Compiler.generate_buffer/4
    (phoenix_view 1.0.0) lib/phoenix/template.ex:371: Phoenix.Template.compile/3
    (phoenix_view 1.0.0) lib/phoenix/template.ex:165: anonymous fn/4 in Phoenix.Template."MACRO-__before_compile__"/2
    (elixir 1.12.2) lib/enum.ex:2385: Enum."-reduce/3-lists^foldl/2-0-"/3
    (phoenix_view 1.0.0) expanding macro: Phoenix.Template.__before_compile__/1
    lib/faithful_word_web/views/email_view.ex:1: FaithfulWordWeb.EmailView (module)
    (elixir 1.12.2) lib/kernel/parallel_compiler.ex:319: anonymous fn/4 in Kernel.ParallelCompiler.spawn_workers/7

I have a large number of templates so there was no easy way for me to determine which file and which specific char was failing. It was like trying to find a needle in a haystack.

So unless there is another solution, I will let sleeping dogs lie and leave everything as .eex

Screen Shot 2021-09-28 at 8.16.47 PM

Ah yes I definitely understand that!

Looking at what you shared, I would probably go 1 file at a time. So, change for instance the password_reset.en.html.eex to .heex and update it’s html syntax and see if that works, that way I know which one I’m working on.

Then, from what you shared, my guess is you have another template that’s being used in the file that you might be working on, like _user_menu.html.eex for example. What that means is then that if that “shared template” has improper .heex format it will throw an error because it’s being rendered now in your .heex that you specifically changed.

So, you would need to make sure you updated all of those to fit your .heex too. That would be my approach and guess based on what you just shared.

Edit: sorry again on my phone. Looking again at the template section, I think I might be misreading it. But the gist of my thought I think still applies just maybe not the example with the _user_menu.html.eex.

Edit: one last thought is checking too that your purge (if using something like Tailwind) is updated to match the new file endings in .heex.

:heart: